001package gudusoft.gsqlparser.sqlenv;
002
003import java.util.Objects;
004
005/**
006 * 标识符规则四元组(per vendor, per object group)
007 *
008 * <p>定义数据库厂商的标识符大小写规则,区分 quoted 和 unquoted 标识符的处理方式。
009 *
010 * <p>设计来源:dbobject_search.md 资深设计师方案
011 *
012 * <p>使用示例:
013 * <pre>
014 * // Oracle: unquoted 折叠为大写且不敏感,quoted 保留原样且敏感
015 * IdentifierRules oracleRules = IdentifierRules.forOracle();
016 *
017 * // Couchbase: 全部大小写敏感
018 * IdentifierRules couchbaseRules = IdentifierRules.forCouchbase();
019 * </pre>
020 *
021 * @since 3.1.0.9
022 */
023public final class IdentifierRules {
024
025    // ===== 四元组定义 =====
026
027    /**
028     * Unquoted 标识符的大小写折叠规则
029     */
030    public final CaseFold unquotedFold;
031
032    /**
033     * Unquoted 标识符的大小写比较规则
034     */
035    public final CaseCompare unquotedCompare;
036
037    /**
038     * Quoted 标识符的大小写折叠规则(通常为 NONE,保留原样)
039     */
040    public final CaseFold quotedFold;
041
042    /**
043     * Quoted 标识符的大小写比较规则
044     */
045    public final CaseCompare quotedCompare;
046
047    /**
048     * How a branch of this cell that compares {@link CaseCompare#INSENSITIVE INSENSITIVE} but does
049     * not fold ({@link CaseFold#NONE}) decides equality: two names are equal when their images
050     * under this mapping are equal. {@link CaseCollapse#SIMPLE_FOLD} (Unicode simple case folding)
051     * for every cell built with the four-argument constructor. A branch that folds is compared by
052     * its folded text and never consults this.
053     *
054     * <p>Kept apart from the fold on purpose: the fold is the name a database STORES (what
055     * {@link IdentifierService#normalize} returns), the collapse is only how it COMPARES. MySQL
056     * keeps a column name as written but compares it by its simple lowercase mapping, so its
057     * column cell is {@code NONE} with collapse {@link CaseCollapse#LOWER_SIMPLE}.
058     */
059    public final CaseCollapse collapse;
060
061    /**
062     * The longest name the database keeps, in UTF-8 bytes; a longer name is cut to the longest
063     * prefix of whole characters that fits, for storing and looking up alike, quoted or not.
064     * {@code 0} means no truncation. PostgreSQL: 63 ({@code NAMEDATALEN - 1}).
065     */
066    public final int maxUtf8Bytes;
067
068    // ===== 枚举定义 =====
069
070    /**
071     * 大小写折叠规则(Case Folding)
072     *
073     * <p>决定如何规范化标识符的大小写
074     */
075    public enum CaseFold {
076        /**
077         * 不转换(保留原样)
078         * <p>例如:Couchbase unquoted, 所有 quoted identifiers
079         */
080        NONE,
081
082        /**
083         * 转大写
084         * <p>例如:Oracle unquoted, DB2 unquoted, Snowflake unquoted
085         */
086        UPPER,
087
088        /**
089         * 转小写
090         * <p>例如:PostgreSQL unquoted, Redshift unquoted, Greenplum unquoted
091         */
092        LOWER,
093
094        /**
095         * Simple uppercase mapping, one code point to one ({@code ß} is kept): each code point
096         * becomes its UnicodeData Simple_Uppercase_Mapping. SQL-LR {@code UPPER_SIMPLE}.
097         * <p>例如:Oracle unquoted ({@code straße} is stored as {@code STRAßE}, not
098         * {@code STRASSE}; verified on Oracle 23.26).
099         * @see gudusoft.gsqlparser.sqlenv.unicode.GspCaseData#toUpperSimple
100         */
101        UPPER_SIMPLE,
102
103        /**
104         * Simple lowercase mapping, one code point to one, no Final_Sigma context
105         * ({@code İ} becomes {@code i}). SQL-LR {@code LOWER_SIMPLE}.
106         * <p>例如:MySQL table names under {@code lower_case_table_names=1}
107         * (verified on MySQL 8.4).
108         * @see gudusoft.gsqlparser.sqlenv.unicode.GspCaseData#toLowerSimple
109         */
110        LOWER_SIMPLE,
111
112        /**
113         * Only {@code A}..{@code Z} are lowered; every other character is kept. SQL-LR
114         * {@code LOWER_ASCII}.
115         * <p>例如:PostgreSQL unquoted in a UTF8 database ({@code ÄBC} is stored as
116         * {@code Äbc}; verified on PostgreSQL 16).
117         * @see gudusoft.gsqlparser.sqlenv.unicode.GspCaseData#toLowerAscii
118         */
119        LOWER_ASCII
120    }
121
122    /**
123     * The mapping under which an {@link CaseCompare#INSENSITIVE INSENSITIVE}, non-folding branch
124     * compares: two names are one name when their images are equal. See {@link #collapse}.
125     */
126    public enum CaseCollapse {
127        /**
128         * Unicode simple case folding (CaseFolding.txt statuses C and S). The default, and the
129         * only behaviour before 2026-10: BigQuery columns, Hive / Teradata / DuckDB quoted names,
130         * every custom cell built with the four-argument constructor.
131         */
132        SIMPLE_FOLD,
133
134        /**
135         * Simple lowercase mapping ({@link CaseFold#LOWER_SIMPLE}). MySQL table names under
136         * {@code lower_case_table_names} 1 and 2, column and index names: {@code ſ} and {@code s},
137         * {@code ς} and {@code σ} are DIFFERENT columns, {@code İ} and {@code i} the same table.
138         */
139        LOWER_SIMPLE,
140
141        /**
142         * Simple uppercase mapping ({@link CaseFold#UPPER_SIMPLE}). MySQL routine and parameter
143         * names: {@code ı} and {@code i} are the same function.
144         */
145        UPPER_SIMPLE
146    }
147
148    /**
149     * 大小写比较规则(Case Comparison)
150     *
151     * <p>决定如何比较两个标识符是否相等
152     */
153    public enum CaseCompare {
154        /**
155         * 大小写敏感
156         * <p>例如:Couchbase 所有标识符, 所有 quoted identifiers
157         */
158        SENSITIVE,
159
160        /**
161         * 大小写不敏感
162         * <p>例如:Oracle unquoted, MySQL unquoted
163         */
164        INSENSITIVE,
165
166        /**
167         * 基于 collation(运行时决定)
168         * <p>例如:SQL Server, Azure SQL
169         * <p>需要使用 {@link java.text.Collator} 进行比较
170         */
171        COLLATION_BASED,
172
173        /**
174         * 与 unquoted 规则一致
175         * <p>例如:Presto quoted identifiers, Vertica quoted identifiers
176         * <p>在解析时需要回退到 unquotedCompare
177         */
178        SAME_AS_UNQUOTED
179    }
180
181    // ===== 构造函数 =====
182
183    /**
184     * 构造标识符规则
185     *
186     * @param unquotedFold unquoted 标识符的折叠规则
187     * @param unquotedCompare unquoted 标识符的比较规则
188     * @param quotedFold quoted 标识符的折叠规则
189     * @param quotedCompare quoted 标识符的比较规则
190     */
191    public IdentifierRules(CaseFold unquotedFold, CaseCompare unquotedCompare,
192                           CaseFold quotedFold, CaseCompare quotedCompare) {
193        this(unquotedFold, unquotedCompare, quotedFold, quotedCompare, CaseCollapse.SIMPLE_FOLD, 0);
194    }
195
196    /**
197     * 构造标识符规则(完整参数)
198     *
199     * @param unquotedFold unquoted 标识符的折叠规则
200     * @param unquotedCompare unquoted 标识符的比较规则
201     * @param quotedFold quoted 标识符的折叠规则
202     * @param quotedCompare quoted 标识符的比较规则
203     * @param collapse how an INSENSITIVE branch that does not fold compares ({@link #collapse})
204     * @param maxUtf8Bytes the length in UTF-8 bytes a name is cut to, {@code 0} for none
205     *                     ({@link #maxUtf8Bytes})
206     */
207    public IdentifierRules(CaseFold unquotedFold, CaseCompare unquotedCompare,
208                           CaseFold quotedFold, CaseCompare quotedCompare,
209                           CaseCollapse collapse, int maxUtf8Bytes) {
210        this.unquotedFold = Objects.requireNonNull(unquotedFold, "unquotedFold");
211        this.unquotedCompare = Objects.requireNonNull(unquotedCompare, "unquotedCompare");
212        this.quotedFold = Objects.requireNonNull(quotedFold, "quotedFold");
213        this.quotedCompare = Objects.requireNonNull(quotedCompare, "quotedCompare");
214        this.collapse = Objects.requireNonNull(collapse, "collapse");
215        if (maxUtf8Bytes < 0) {
216            throw new IllegalArgumentException("maxUtf8Bytes must be 0 (no limit) or positive: "
217                    + maxUtf8Bytes);
218        }
219        this.maxUtf8Bytes = maxUtf8Bytes;
220    }
221
222    // ===== 缓存的单例实例 =====
223
224    private static final IdentifierRules ORACLE = new IdentifierRules(
225        CaseFold.UPPER, CaseCompare.INSENSITIVE, CaseFold.NONE, CaseCompare.SENSITIVE);
226
227    private static final IdentifierRules POSTGRESQL = new IdentifierRules(
228        CaseFold.LOWER, CaseCompare.INSENSITIVE, CaseFold.NONE, CaseCompare.SENSITIVE);
229
230    private static final IdentifierRules COUCHBASE = new IdentifierRules(
231        CaseFold.NONE, CaseCompare.SENSITIVE, CaseFold.NONE, CaseCompare.SENSITIVE);
232
233    private static final IdentifierRules PRESTO = new IdentifierRules(
234        CaseFold.LOWER, CaseCompare.INSENSITIVE, CaseFold.NONE, CaseCompare.SAME_AS_UNQUOTED);
235
236    private static final IdentifierRules REDSHIFT = new IdentifierRules(
237        CaseFold.LOWER, CaseCompare.INSENSITIVE, CaseFold.LOWER, CaseCompare.INSENSITIVE);
238
239    private static final IdentifierRules HIVE = new IdentifierRules(
240        CaseFold.LOWER, CaseCompare.INSENSITIVE, CaseFold.NONE, CaseCompare.INSENSITIVE);
241
242    private static final IdentifierRules TERADATA = new IdentifierRules(
243        CaseFold.LOWER, CaseCompare.INSENSITIVE, CaseFold.NONE, CaseCompare.INSENSITIVE);
244
245    private static final IdentifierRules FLINK = new IdentifierRules(
246        CaseFold.NONE, CaseCompare.SENSITIVE, CaseFold.NONE, CaseCompare.SENSITIVE);
247
248    private static final IdentifierRules DUCKDB = new IdentifierRules(
249        CaseFold.LOWER, CaseCompare.INSENSITIVE, CaseFold.NONE, CaseCompare.INSENSITIVE);
250
251    private static final IdentifierRules SQLITE = new IdentifierRules(
252        CaseFold.LOWER, CaseCompare.INSENSITIVE, CaseFold.NONE, CaseCompare.INSENSITIVE);
253
254    private static final IdentifierRules SYBASE = new IdentifierRules(
255        CaseFold.LOWER, CaseCompare.INSENSITIVE, CaseFold.NONE, CaseCompare.INSENSITIVE);
256
257    private static final IdentifierRules SQL_ANYWHERE = new IdentifierRules(
258        CaseFold.LOWER, CaseCompare.INSENSITIVE, CaseFold.NONE, CaseCompare.INSENSITIVE);
259
260    private static final IdentifierRules ACCESS = new IdentifierRules(
261        CaseFold.LOWER, CaseCompare.INSENSITIVE, CaseFold.NONE, CaseCompare.INSENSITIVE);
262
263    private static final IdentifierRules DAX = new IdentifierRules(
264        CaseFold.LOWER, CaseCompare.INSENSITIVE, CaseFold.NONE, CaseCompare.INSENSITIVE);
265
266    private static final IdentifierRules MDX = new IdentifierRules(
267        CaseFold.LOWER, CaseCompare.INSENSITIVE, CaseFold.NONE, CaseCompare.INSENSITIVE);
268
269    private static final IdentifierRules POWER_QUERY = new IdentifierRules(
270        CaseFold.NONE, CaseCompare.SENSITIVE, CaseFold.NONE, CaseCompare.SENSITIVE);
271
272    private static final IdentifierRules ANSI = new IdentifierRules(
273        CaseFold.UPPER, CaseCompare.INSENSITIVE, CaseFold.NONE, CaseCompare.SENSITIVE);
274
275    private static final IdentifierRules BIGQUERY_TABLE = new IdentifierRules(
276        CaseFold.NONE, CaseCompare.SENSITIVE, CaseFold.NONE, CaseCompare.SENSITIVE);
277
278    private static final IdentifierRules BIGQUERY_COLUMN = new IdentifierRules(
279        CaseFold.NONE, CaseCompare.INSENSITIVE, CaseFold.NONE, CaseCompare.INSENSITIVE);
280
281    private static final IdentifierRules MYSQL_COLUMN = new IdentifierRules(
282        CaseFold.NONE, CaseCompare.INSENSITIVE, CaseFold.NONE, CaseCompare.INSENSITIVE);
283
284    // ----- The verified engines (2026-10-11). Each cell below was checked against a real server;
285    // the forks that used to share a cell with these vendors keep the old one (forOracle(),
286    // forPostgreSQL(), forMySQL(int), forMySQLColumn(), forMySQLRoutine()) until they are verified
287    // on their own engines.
288
289    private static final IdentifierRules ORACLE_NATIVE = new IdentifierRules(
290        CaseFold.UPPER_SIMPLE, CaseCompare.INSENSITIVE, CaseFold.NONE, CaseCompare.SENSITIVE);
291
292    /** PostgreSQL's NAMEDATALEN - 1 with the default build (NAMEDATALEN = 64). */
293    public static final int POSTGRESQL_MAX_IDENTIFIER_BYTES = 63;
294
295    private static final IdentifierRules POSTGRESQL_NATIVE = new IdentifierRules(
296        CaseFold.LOWER_ASCII, CaseCompare.INSENSITIVE, CaseFold.NONE, CaseCompare.SENSITIVE,
297        CaseCollapse.SIMPLE_FOLD, POSTGRESQL_MAX_IDENTIFIER_BYTES);
298
299    private static final IdentifierRules MYSQL_NATIVE_LCTN0 = new IdentifierRules(
300        CaseFold.NONE, CaseCompare.SENSITIVE, CaseFold.NONE, CaseCompare.SENSITIVE);
301
302    private static final IdentifierRules MYSQL_NATIVE_LCTN1 = new IdentifierRules(
303        CaseFold.LOWER_SIMPLE, CaseCompare.INSENSITIVE, CaseFold.NONE, CaseCompare.INSENSITIVE,
304        CaseCollapse.LOWER_SIMPLE, 0);
305
306    private static final IdentifierRules MYSQL_NATIVE_LCTN2 = new IdentifierRules(
307        CaseFold.NONE, CaseCompare.INSENSITIVE, CaseFold.NONE, CaseCompare.INSENSITIVE,
308        CaseCollapse.LOWER_SIMPLE, 0);
309
310    private static final IdentifierRules MYSQL_NATIVE_COLUMN = new IdentifierRules(
311        CaseFold.NONE, CaseCompare.INSENSITIVE, CaseFold.NONE, CaseCompare.INSENSITIVE,
312        CaseCollapse.LOWER_SIMPLE, 0);
313
314    private static final IdentifierRules MYSQL_NATIVE_ROUTINE = new IdentifierRules(
315        CaseFold.NONE, CaseCompare.INSENSITIVE, CaseFold.NONE, CaseCompare.INSENSITIVE,
316        CaseCollapse.UPPER_SIMPLE, 0);
317
318    // ===== 预设规则工厂方法 =====
319
320    /**
321     * Oracle 标识符规则
322     *
323     * <p><strong>实际数据库行为(Oracle 12c+):</strong>
324     * <ul>
325     * <li>Unquoted: 折叠为大写,比较不敏感 (CREATE TABLE foo → stored as FOO, foo=FOO=Foo)
326     * <li>Quoted: 保留原样,比较敏感 (CREATE TABLE "foo" → stored as foo, "foo"!="FOO")
327     * </ul>
328     *
329     * <p><strong>与 Legacy TSQLEnv 兼容性:</strong>
330     * <ul>
331     * <li>columnCollationCaseSensitive = {@code true} → ❌ <strong>INCOMPATIBLE</strong> (legacy preserved case, new folds to UPPER)
332     * <li>functionCollationCaseSensitive = {@code true} → ❌ <strong>INCOMPATIBLE</strong> (legacy preserved case, new folds to UPPER)
333     * <li>tableCollationCaseSensitive = {@code true} → ❌ <strong>INCOMPATIBLE</strong> (legacy preserved case, new folds to UPPER)
334     * <li>catalogCollationCaseSensitive = {@code false} → ✅ <strong>COMPATIBLE</strong> (both fold to UPPER)
335     * </ul>
336     *
337     * <p><strong>测试用例影响:</strong>
338     * <ul>
339     * <li>✅ 新规则正确:Oracle 确实将 unquoted identifiers 折叠为大写</li>
340     * <li>⚠️ 如果旧测试期望保留原始大小写 (如 "myTable" 保持为 "myTable"),则测试会失败</li>
341     * <li>⚠️ 应更新测试期望为大写 (如 "myTable" → "MYTABLE")</li>
342     * </ul>
343     *
344     * <p><strong>IdentifierRules 配置:</strong>
345     * <ul>
346     * <li>Unquoted: 折叠为大写 ({@link CaseFold#UPPER}), 比较不敏感 ({@link CaseCompare#INSENSITIVE})
347     * <li>Quoted: 保留原样 ({@link CaseFold#NONE}), 比较敏感 ({@link CaseCompare#SENSITIVE})
348     * </ul>
349     *
350     * <p><strong>Since 2026-10-11 this is the shape the Oracle-like FORKS share</strong> (DB2,
351     * Netezza, Exasol, HANA, Snowflake, Dameng, Firebird), none of which has been verified on its
352     * own engine. {@link gudusoft.gsqlparser.EDbVendor#dbvoracle} itself uses
353     * {@link #forOracleNative()}: Oracle upper-cases an unquoted name with the SIMPLE mapping, so
354     * {@code straße} is {@code STRAßE}, which this cell's full mapping turns into {@code STRASSE}.
355     */
356    public static IdentifierRules forOracle() {
357        return ORACLE;
358    }
359
360    /**
361     * Oracle 标识符规则,as verified on Oracle 23.26 (AL32UTF8), used by
362     * {@link gudusoft.gsqlparser.EDbVendor#dbvoracle}.
363     *
364     * <ul>
365     * <li>Unquoted: the simple uppercase mapping ({@link CaseFold#UPPER_SIMPLE}), case-insensitive.
366     *     {@code CREATE TABLE straße} is listed in {@code user_tables} as {@code STRAßE} (6
367     *     characters); {@code FROM "STRAßE"} finds it, {@code FROM STRASSE} raises ORA-00942.
368     * <li>Quoted: kept as written ({@link CaseFold#NONE}), case-sensitive.
369     * </ul>
370     *
371     * <p>SQL-LR {@code oracle/rules-1}, cell {@code U_UPPER_SIMPLE_CI__Q_NONE_CS}.
372     */
373    public static IdentifierRules forOracleNative() {
374        return ORACLE_NATIVE;
375    }
376
377    /**
378     * Dameng (达梦) 标识符规则(与 Oracle 相同)
379     *
380     * <p>Dameng follows Oracle identifier conventions:
381     * <ul>
382     * <li>Unquoted: 折叠为大写, 比较不敏感
383     * <li>Quoted: 保留原样, 比较敏感
384     * </ul>
385     */
386    public static IdentifierRules forDameng() {
387        return forOracle();
388    }
389
390    /**
391     * PostgreSQL / Redshift / Greenplum 标识符规则
392     *
393     * <p><strong>实际数据库行为(PostgreSQL 12+):</strong>
394     * <ul>
395     * <li>Unquoted: 折叠为小写,比较不敏感 (CREATE TABLE MyTable → stored as mytable, MyTable=mytable=MYTABLE)
396     * <li>Quoted: 保留原样,比较敏感 (CREATE TABLE "MyTable" → stored as MyTable, "MyTable"!="mytable")
397     * </ul>
398     *
399     * <p><strong>与 Legacy TSQLEnv 兼容性:</strong>
400     * <ul>
401     * <li>columnCollationCaseSensitive = {@code false} → ⚠️ <strong>PARTIAL</strong> (legacy folded to UPPER, new folds to LOWER)
402     * <li>functionCollationCaseSensitive = {@code true} → ❌ <strong>INCOMPATIBLE</strong> (legacy preserved case, new folds to LOWER)
403     * <li>tableCollationCaseSensitive = {@code true} → ❌ <strong>INCOMPATIBLE</strong> (legacy preserved case, new folds to LOWER)
404     * <li>catalogCollationCaseSensitive = {@code true} → ❌ <strong>INCOMPATIBLE</strong> (legacy preserved case, new folds to LOWER)
405     * </ul>
406     *
407     * <p><strong>测试用例影响:</strong>
408     * <ul>
409     * <li>✅ 新规则正确:PostgreSQL 确实将 unquoted identifiers 折叠为小写</li>
410     * <li>⚠️ 如果旧测试期望大写 (如 "MyTable" → "MYTABLE"),则测试会失败</li>
411     * <li>⚠️ 应更新测试期望为小写 (如 "MyTable" → "mytable")</li>
412     * <li>⚠️ 如果旧测试期望保留原始大小写,也会失败</li>
413     * </ul>
414     *
415     * <p><strong>IdentifierRules 配置:</strong>
416     * <ul>
417     * <li>Unquoted: 折叠为小写 ({@link CaseFold#LOWER}), 比较不敏感 ({@link CaseCompare#INSENSITIVE})
418     * <li>Quoted: 保留原样 ({@link CaseFold#NONE}), 比较敏感 ({@link CaseCompare#SENSITIVE})
419     * </ul>
420     *
421     * <p><strong>Since 2026-10-11 this is the shape the PostgreSQL-like FORKS share</strong>
422     * (Greenplum, GaussDB, EDB, and the fallback of Informix, OpenEdge and SOQL), none verified on
423     * its own engine. {@link gudusoft.gsqlparser.EDbVendor#dbvpostgresql} itself uses
424     * {@link #forPostgreSQLNative()}.
425     */
426    public static IdentifierRules forPostgreSQL() {
427        return POSTGRESQL;
428    }
429
430    /**
431     * PostgreSQL 标识符规则,as verified on PostgreSQL 16.15 with a UTF8 database, used by
432     * {@link gudusoft.gsqlparser.EDbVendor#dbvpostgresql}.
433     *
434     * <ul>
435     * <li>Unquoted: only {@code A}..{@code Z} are lowered ({@link CaseFold#LOWER_ASCII}) —
436     *     {@code downcase_identifier} lowers nothing else in a multibyte encoding. After
437     *     {@code CREATE TABLE "Äbc"}, {@code FROM ÄBC} finds it and {@code FROM "äbc"} does not.
438     * <li>Quoted: kept as written, case-sensitive.
439     * <li>Both: cut to {@value #POSTGRESQL_MAX_IDENTIFIER_BYTES} UTF-8 bytes on a whole character
440     *     ({@code truncate_identifier} / {@code pg_mbcliplen}), so two names that share their first
441     *     63 bytes are one name: {@code CREATE TABLE <63 a>x} is found by {@code FROM <63 a>y}.
442     * </ul>
443     *
444     * <p>SQL-LR {@code postgresql/rules-1}, cell {@code U_LOWER_ASCII_CI__Q_NONE_CS} with
445     * {@code truncation = UTF8_BYTES 63}.
446     */
447    public static IdentifierRules forPostgreSQLNative() {
448        return POSTGRESQL_NATIVE;
449    }
450
451    /**
452     * Amazon Redshift 标识符规则(默认配置,{@code enable_case_sensitive_identifier = false})
453     *
454     * <p><strong>实际数据库行为(AWS 文档 "Names and identifiers"):</strong>
455     * standard 与 delimited identifier 都不区分大小写,并折叠为小写存储;
456     * 只有打开 {@code enable_case_sensitive_identifier} 时,delimited identifier 才保留大小写。
457     * 因此 {@code "Orders"}、{@code "ORDERS"} 与 {@code orders} 是同一个对象。
458     *
459     * <p><strong>IdentifierRules 配置:</strong>
460     * <ul>
461     * <li>Unquoted: 折叠为小写 ({@link CaseFold#LOWER}), 比较不敏感 ({@link CaseCompare#INSENSITIVE})
462     * <li>Quoted: 折叠为小写 ({@link CaseFold#LOWER}), 比较不敏感 ({@link CaseCompare#INSENSITIVE})
463     * </ul>
464     */
465    public static IdentifierRules forRedshift() {
466        return REDSHIFT;
467    }
468
469    /**
470     * Couchbase N1QL 标识符规则
471     *
472     * <p><strong>实际数据库行为(Couchbase N1QL):</strong>
473     * <ul>
474     * <li>Unquoted: 保留原样,比较敏感
475     * <li>Quoted: 保留原样,比较敏感
476     * </ul>
477     *
478     * <p><strong>与 Legacy TSQLEnv 兼容性:</strong>
479     * <ul>
480     * <li>tableCollationCaseSensitive = {@code false} → ❌ <strong>INCOMPATIBLE</strong> (legacy folded to UPPER, new preserves case)
481     * </ul>
482     *
483     * <p><strong>IdentifierRules 配置:</strong>
484     * <ul>
485     * <li>Unquoted: 保留原样 ({@link CaseFold#NONE}), 比较敏感 ({@link CaseCompare#SENSITIVE})
486     * <li>Quoted: 保留原样 ({@link CaseFold#NONE}), 比较敏感 ({@link CaseCompare#SENSITIVE})
487     * </ul>
488     */
489    public static IdentifierRules forCouchbase() {
490        return COUCHBASE;
491    }
492
493    /**
494     * SQL Server / Azure SQL 标识符规则
495     *
496     * <p><strong>实际数据库行为(SQL Server 2019+):</strong>
497     * <ul>
498     * <li>Unquoted: 保留原样,比较由 collation 决定 (CREATE TABLE MyTable → stored as MyTable)
499     * <li>Quoted: 保留原样,比较由 collation 决定 (CREATE TABLE [MyTable] → stored as MyTable)
500     * <li>默认 collation (SQL_Latin1_General_CP1_CI_AS): 大小写不敏感 (MyTable=mytable=MYTABLE)
501     * <li>CS collation (SQL_Latin1_General_CP1_CS_AS): 大小写敏感 (MyTable!=mytable)
502     * </ul>
503     *
504     * <p><strong>与 Legacy TSQLEnv 兼容性:</strong>
505     * <ul>
506     * <li>columnCollationCaseSensitive = {@code false} → ❌ <strong>INCOMPATIBLE</strong> (legacy folded to UPPER, new preserves case)
507     * <li>functionCollationCaseSensitive = {@code false} → ❌ <strong>INCOMPATIBLE</strong> (legacy folded to UPPER, new preserves case)
508     * <li>tableCollationCaseSensitive = {@code false} → ❌ <strong>INCOMPATIBLE</strong> (legacy folded to UPPER, new preserves case)
509     * <li>catalogCollationCaseSensitive = {@code false} → ❌ <strong>INCOMPATIBLE</strong> (legacy folded to UPPER, new preserves case)
510     * </ul>
511     *
512     * <p><strong>测试用例影响:</strong>
513     * <ul>
514     * <li>✅ 新规则正确:SQL Server 保留标识符原始大小写,使用 collation 进行比较</li>
515     * <li>⚠️ 如果旧测试期望 "MyTable" → "MYTABLE",则测试会失败</li>
516     * <li>⚠️ 应更新测试期望为保留原样 (如 "MyTable" 保持为 "MyTable")</li>
517     * <li>⚠️ 这是导致 dataflow 测试 processId 变化的根本原因!</li>
518     * <li>📝 参考: investigation_findings_2025_10_20.md</li>
519     * </ul>
520     *
521     * <p><strong>IdentifierRules 配置:</strong>
522     * <ul>
523     * <li>Unquoted: 不折叠 ({@link CaseFold#NONE}), 基于 collation 比较 ({@link CaseCompare#COLLATION_BASED})
524     * <li>Quoted: 不折叠 ({@link CaseFold#NONE}), 基于 collation 比较 ({@link CaseCompare#COLLATION_BASED})
525     * <li>需要配合 {@link CollatorProvider} 使用,默认使用 SQL_Latin1_General_CP1_CI_AS (大小写不敏感)
526     * </ul>
527     *
528     * <p><strong>注意:</strong>SQL Server 的大小写行为完全由 collation 决定,无法简单折叠。
529     */
530    public static IdentifierRules forSQLServer() {
531        return forSQLServer(null);
532    }
533
534    /**
535     * SQL Server / Azure SQL 标识符规则(根据 collation 决定大小写比较)
536     *
537     * <p>SQL Server 的大小写行为完全由 collation 决定:
538     * <ul>
539     * <li>CI collation (如 SQL_Latin1_General_CP1_CI_AS): 大小写不敏感
540     * <li>CS collation (如 SQL_Latin1_General_CP1_CS_AS): 大小写敏感
541     * </ul>
542     *
543     * <p>Unquoted 和 quoted 标识符都保留原始大小写(CaseFold.NONE),
544     * 比较方式由 collation 名称中的 _CI_ 或 _CS_ 决定。
545     *
546     * <p>A case-sensitive collation is exact ({@link CaseCompare#SENSITIVE}), except
547     * {@code Latin1_General_CS_AI}, whose accent equivalence was measured on the engine
548     * ({@link CollatorProvider#hasMeasuredAccentFolding}): it compares through the collator
549     * ({@link CaseCompare#COLLATION_BASED}), which folds accents by the measured table and then
550     * compares exactly (verified on SQL Server 2022: under {@code Latin1_General_CS_AI} the table
551     * {@code café} is found by {@code cafe}, not by {@code Cafe}). Every other collation, a
552     * case-insensitive one or {@code _BIN}/{@code _BIN2}, compares through the collator. A
553     * {@code _CS_AI} collation that was not measured stays exact, as before.
554     *
555     * @param collation SQL Server collation 名称(如 "SQL_Latin1_General_CP1_CS_AS"),
556     *                  传 null 或空字符串使用默认(case-insensitive)
557     */
558    public static IdentifierRules forSQLServer(String collation) {
559        CaseCompare compare = isCaseSensitiveCollation(collation)
560            ? CaseCompare.SENSITIVE
561            : CaseCompare.COLLATION_BASED;
562        return new IdentifierRules(
563            CaseFold.NONE,   // unquoted 不折叠(保留原样)
564            compare,         // 基于 collation 决定比较方式
565            CaseFold.NONE,   // quoted 不折叠(保留原样)
566            compare          // quoted 与 unquoted 一致
567        );
568    }
569
570    /**
571     * 检查 SQL Server collation 名称是否为大小写敏感。
572     *
573     * <p>SQL Server collation 命名规则:名称中包含 "_CS_" 表示 case-sensitive,
574     * "_CI_" 表示 case-insensitive。
575     *
576     * @param collation collation 名称
577     * @return true 如果 collation 是 case-sensitive
578     */
579    private static boolean isCaseSensitiveCollation(String collation) {
580        if (collation == null || collation.isEmpty()) {
581            return false;  // 默认 case-insensitive
582        }
583        // SQL Server collation naming: _CS_ = case-sensitive, _CI_ = case-insensitive
584        String upper = collation.toUpperCase(java.util.Locale.ROOT);
585        boolean caseSensitive = upper.contains("_CS_") || upper.endsWith("_CS");
586        // A case-sensitive but ACCENT-INSENSITIVE collation (_CS_AI) is not exact: on SQL Server
587        // 2022 with Latin1_General_CS_AI, `FROM cafe` finds the table café and `CREATE TABLE cafe`
588        // then fails as a duplicate. Exact comparison cannot say that, so the collator decides
589        // (CollatorProvider: the measured accent table, then exact). Only for the collation whose
590        // accents were measured; any other _CS_AI collation stays exact, as it was before.
591        return caseSensitive && !CollatorProvider.hasMeasuredAccentFolding(collation);
592    }
593
594    /**
595     * MySQL 标识符规则(table/schema names)
596     *
597     * <p><strong>实际数据库行为(MySQL 8.0+):</strong>
598     * <p>根据 {@code lower_case_table_names} 系统变量决定:
599     * <ul>
600     * <li>0 (Unix/Linux): 大小写敏感,保留原样 (CREATE TABLE MyTable → stored as MyTable, MyTable!=mytable)
601     * <li>1 (Windows): 存储为小写,比较不敏感 (CREATE TABLE MyTable → stored as mytable, MyTable=mytable=MYTABLE)
602     * <li>2 (macOS): 存储保留原样,比较不敏感 (CREATE TABLE MyTable → stored as MyTable, MyTable=mytable=MYTABLE)
603     * </ul>
604     *
605     * <p><strong>与 Legacy TSQLEnv 兼容性:</strong>
606     * <ul>
607     * <li>columnCollationCaseSensitive = {@code false} → ⚠️ <strong>PARTIAL</strong> (legacy folded to UPPER, new doesn't fold or folds to LOWER)
608     * <li>functionCollationCaseSensitive = {@code false} → ⚠️ <strong>PARTIAL</strong> (legacy folded to UPPER, new doesn't fold)
609     * <li>tableCollationCaseSensitive = {@code false} → ⚠️ <strong>PARTIAL</strong> (legacy folded to UPPER, new behavior depends on lower_case_table_names)
610     * <li>catalogCollationCaseSensitive = {@code true} → ⚠️ <strong>PARTIAL</strong> (legacy preserved case, new is insensitive)
611     * </ul>
612     *
613     * <p><strong>测试用例影响:</strong>
614     * <ul>
615     * <li>✅ 新规则正确:MySQL 的行为确实依赖 lower_case_table_names 设置</li>
616     * <li>⚠️ 如果旧测试期望 "MyTable" → "MYTABLE",则在模式 1/2 下测试会失败</li>
617     * <li>⚠️ 模式 0: 应期望保留原样 (如 "MyTable" 保持为 "MyTable"),区分大小写</li>
618     * <li>⚠️ 模式 1: 应期望小写 (如 "MyTable" → "mytable"),不区分大小写</li>
619     * <li>⚠️ 模式 2: 应期望保留原样 (如 "MyTable" 保持为 "MyTable"),不区分大小写</li>
620     * </ul>
621     *
622     * <p><strong>IdentifierRules 配置:</strong>
623     * <ul>
624     * <li>模式 0: 不折叠 ({@link CaseFold#NONE}), 敏感 ({@link CaseCompare#SENSITIVE})
625     * <li>模式 1: 折叠为小写 ({@link CaseFold#LOWER}), 不敏感 ({@link CaseCompare#INSENSITIVE})
626     * <li>模式 2: 不折叠 ({@link CaseFold#NONE}), 不敏感 ({@link CaseCompare#INSENSITIVE})
627     * <li>Quoted: 保留原样,但也不敏感 (MySQL 特殊行为)
628     * </ul>
629     *
630     * <p><strong>Since 2026-10-11 this is the shape the MySQL-like FORKS share</strong> (OceanBase,
631     * Doris, StarRocks), none verified on its own engine.
632     * {@link gudusoft.gsqlparser.EDbVendor#dbvmysql} itself uses {@link #forMySQLNative(int)}.
633     *
634     * @param lowerCaseTableNames {@code lower_case_table_names} 值(0, 1, 2)
635     */
636    public static IdentifierRules forMySQL(int lowerCaseTableNames) {
637        if (lowerCaseTableNames == 0) {
638            // Unix/Linux: 大小写敏感
639            return new IdentifierRules(
640                CaseFold.NONE,
641                CaseCompare.SENSITIVE,
642                CaseFold.NONE,
643                CaseCompare.SENSITIVE
644            );
645        } else {
646            // Windows/macOS: 比较不敏感
647            CaseFold fold = (lowerCaseTableNames == 1) ? CaseFold.LOWER : CaseFold.NONE;
648            return new IdentifierRules(
649                fold,                     // 根据设置决定是否折叠
650                CaseCompare.INSENSITIVE,  // 比较不敏感
651                CaseFold.NONE,            // quoted 保留原样
652                CaseCompare.INSENSITIVE   // quoted 也不敏感(MySQL 特性)
653            );
654        }
655    }
656
657    /**
658     * MySQL table / schema / table-alias names, as verified on MySQL 8.4.10, used by
659     * {@link gudusoft.gsqlparser.EDbVendor#dbvmysql}.
660     *
661     * <ul>
662     * <li>{@code lower_case_table_names = 0}: stored as given, case-sensitive (unchanged).
663     * <li>{@code 1}: every name, quoted or not, is lowered with MySQL's one-to-one per-character
664     *     mapping ({@link CaseFold#LOWER_SIMPLE}); quoting does not create another name.
665     *     {@code CREATE TABLE İ} is stored as {@code i}, and {@code FROM İ}, {@code FROM `İ`} and
666     *     {@code FROM i} all find it. The quoted branch keeps its spelling as the stored form
667     *     GSP reports but compares by the same mapping ({@link CaseCollapse#LOWER_SIMPLE}).
668     * <li>{@code 2}: stored as given, compared by the same lowercase mapping.
669     * </ul>
670     *
671     * <p>SQL-LR {@code mysql/rules-1}: cells {@code U_LOWER_SIMPLE_CI__Q_LOWER_SIMPLE_CI} and,
672     * for {@code lowerCaseTableNames = 0}, {@code U_NONE_CS__Q_NONE_CS}.
673     *
674     * @param lowerCaseTableNames {@code lower_case_table_names} 值(0, 1, 2)
675     */
676    public static IdentifierRules forMySQLNative(int lowerCaseTableNames) {
677        if (lowerCaseTableNames == 0) {
678            return MYSQL_NATIVE_LCTN0;
679        }
680        return lowerCaseTableNames == 1 ? MYSQL_NATIVE_LCTN1 : MYSQL_NATIVE_LCTN2;
681    }
682
683    /**
684     * MySQL column, column-alias and index names, as verified on MySQL 8.4.10: kept as written and
685     * compared by the simple lowercase mapping ({@link CaseCollapse#LOWER_SIMPLE}), whatever
686     * {@code lower_case_table_names} is. {@code CREATE TABLE c1(`ſ` int, s int)} and
687     * {@code c2(`ς` int, `σ` int)} succeed: those are distinct columns, which Unicode case folding
688     * would merge. Index names follow the same rule ({@code INDEX Idx} then {@code ADD INDEX idx}
689     * fails with ERROR 1061 even under {@code lower_case_table_names = 0}).
690     */
691    public static IdentifierRules forMySQLNativeColumn() {
692        return MYSQL_NATIVE_COLUMN;
693    }
694
695    /**
696     * MySQL function, procedure and routine-parameter names, as verified on MySQL 8.4.10: kept as
697     * written; two names are the same when their simple UPPERCASE forms are equal
698     * ({@link CaseCollapse#UPPER_SIMPLE}), whatever {@code lower_case_table_names} is.
699     * {@code CREATE FUNCTION `ı`()} is called by {@code SELECT i()}, and a later
700     * {@code CREATE FUNCTION i()} fails as already existing; {@code CREATE PROCEDURE pr(P int, p int)}
701     * fails with ERROR 1330 under {@code lower_case_table_names = 0}.
702     */
703    public static IdentifierRules forMySQLNativeRoutine() {
704        return MYSQL_NATIVE_ROUTINE;
705    }
706
707    /**
708     * MySQL 列名规则(始终大小写不敏感)
709     *
710     * <p><strong>实际数据库行为(MySQL 8.0+):</strong>
711     * <ul>
712     * <li>列名始终大小写不敏感,不受 lower_case_table_names 影响
713     * <li>存储时保留原样,比较时不敏感 (SELECT MyColumn → stored as MyColumn, MyColumn=mycolumn)
714     * </ul>
715     *
716     * <p><strong>与 Legacy TSQLEnv 兼容性:</strong>
717     * <ul>
718     * <li>columnCollationCaseSensitive = {@code false} → ⚠️ <strong>PARTIAL</strong> (legacy folded to UPPER, new preserves case but is INSENSITIVE)
719     * </ul>
720     *
721     * <p>Since 2026-10-11 the shape of the forks only; MySQL itself uses
722     * {@link #forMySQLNativeColumn()}.
723     */
724    public static IdentifierRules forMySQLColumn() {
725        return MYSQL_COLUMN;
726    }
727
728    /**
729     * MySQL 函数名规则(始终大小写不敏感)
730     *
731     * <p><strong>实际数据库行为(MySQL 8.0+):</strong>
732     * <ul>
733     * <li>函数名/存储过程名始终大小写不敏感
734     * <li>存储时保留原样,比较时不敏感 (与列名相同)
735     * </ul>
736     *
737     * <p><strong>与 Legacy TSQLEnv 兼容性:</strong>
738     * <ul>
739     * <li>functionCollationCaseSensitive = {@code false} → ⚠️ <strong>PARTIAL</strong> (legacy folded to UPPER, new preserves case but is INSENSITIVE)
740     * </ul>
741     *
742     * <p>Since 2026-10-11 the shape of the forks only; MySQL itself uses
743     * {@link #forMySQLNativeRoutine()}.
744     */
745    public static IdentifierRules forMySQLRoutine() {
746        return forMySQLColumn();
747    }
748
749    /**
750     * BigQuery 表名规则(大小写敏感)
751     *
752     * <p><strong>实际数据库行为(BigQuery Standard SQL):</strong>
753     * <ul>
754     * <li>Unquoted: 保留原样,比较敏感 (CREATE TABLE MyTable → stored as MyTable, MyTable!=mytable)
755     * <li>Quoted: 保留原样,比较敏感 (CREATE TABLE `MyTable` → stored as MyTable, MyTable!=mytable)
756     * <li>表名/dataset名/project名都是大小写敏感的</li>
757     * </ul>
758     *
759     * <p><strong>与 Legacy TSQLEnv 兼容性:</strong>
760     * <ul>
761     * <li>tableCollationCaseSensitive = {@code false} → ❌ <strong>INCOMPATIBLE</strong> (legacy folded to UPPER, new preserves case and is SENSITIVE)
762     * <li>catalogCollationCaseSensitive = {@code false} → ❌ <strong>INCOMPATIBLE</strong> (legacy folded to UPPER, new preserves case and is SENSITIVE)
763     * </ul>
764     *
765     * <p><strong>测试用例影响:</strong>
766     * <ul>
767     * <li>✅ 新规则正确:BigQuery 表名确实是大小写敏感的</li>
768     * <li>⚠️ 如果旧测试期望 "MyTable" → "MYTABLE",则测试会失败</li>
769     * <li>⚠️ 应更新测试期望为保留原样 (如 "MyTable" 保持为 "MyTable")</li>
770     * <li>⚠️ 旧代码可能错误地匹配了不同大小写的表名,新代码会正确拒绝</li>
771     * </ul>
772     */
773    public static IdentifierRules forBigQueryTable() {
774        return BIGQUERY_TABLE;
775    }
776
777    /**
778     * BigQuery 列名规则(大小写不敏感)
779     *
780     * <p><strong>实际数据库行为(BigQuery Standard SQL):</strong>
781     * <ul>
782     * <li>Unquoted: 保留原样,比较不敏感 (SELECT MyColumn → stored as MyColumn, MyColumn=mycolumn=MYCOLUMN)
783     * <li>Quoted: 保留原样,比较不敏感 (SELECT `MyColumn` → MyColumn=mycolumn)
784     * <li>列名是大小写不敏感的(与表名不同)</li>
785     * </ul>
786     *
787     * <p><strong>与 Legacy TSQLEnv 兼容性:</strong>
788     * <ul>
789     * <li>columnCollationCaseSensitive = {@code false} → ⚠️ <strong>PARTIAL</strong> (legacy folded to UPPER, new preserves case but is INSENSITIVE)
790     * </ul>
791     *
792     * <p><strong>测试用例影响:</strong>
793     * <ul>
794     * <li>✅ 新规则正确:BigQuery 列名确实是大小写不敏感的</li>
795     * <li>⚠️ 如果旧测试期望 "MyColumn" → "MYCOLUMN",则测试会失败</li>
796     * <li>⚠️ 应更新测试期望为保留原样 (如 "MyColumn" 保持为 "MyColumn")</li>
797     * <li>✅ 但比较时应忽略大小写 (MyColumn = mycolumn = MYCOLUMN)</li>
798     * </ul>
799     */
800    public static IdentifierRules forBigQueryColumn() {
801        return BIGQUERY_COLUMN;
802    }
803
804    /**
805     * DB2 / Netezza / Exasol 标识符规则(与 Oracle 相同)
806     *
807     * <p><strong>实际数据库行为(DB2 11+):</strong>
808     * <ul>
809     * <li>Unquoted: 折叠为大写,比较不敏感 (与 Oracle 相同)
810     * <li>Quoted: 保留原样,比较敏感
811     * </ul>
812     *
813     * <p><strong>与 Legacy TSQLEnv 兼容性:</strong>
814     * <ul>
815     * <li>DB2 tableCollationCaseSensitive = {@code true} → ❌ <strong>INCOMPATIBLE</strong> (legacy preserved case, new folds to UPPER)
816     * <li>DB2 catalogCollationCaseSensitive = {@code true} → ❌ <strong>INCOMPATIBLE</strong> (legacy preserved case, new folds to UPPER)
817     * <li>参见 {@link #forOracle()} 的详细说明
818     * </ul>
819     */
820    public static IdentifierRules forDB2() {
821        return forOracle();
822    }
823
824    /**
825     * Snowflake 标识符规则(与 Oracle 相同)
826     *
827     * <p><strong>实际数据库行为(Snowflake):</strong>
828     * <ul>
829     * <li>Unquoted: 折叠为大写,比较不敏感 (与 Oracle 相同)
830     * <li>Quoted: 保留原样,比较敏感
831     * </ul>
832     *
833     * <p><strong>与 Legacy TSQLEnv 兼容性:</strong>
834     * <ul>
835     * <li>tableCollationCaseSensitive = {@code false} → ⚠️ <strong>PARTIAL</strong> (both fold to UPPER, COMPATIBLE)
836     * <li>columnCollationCaseSensitive = {@code false} → ⚠️ <strong>PARTIAL</strong> (both fold to UPPER, COMPATIBLE)
837     * <li>参见 {@link #forOracle()} 的详细说明
838     * </ul>
839     */
840    public static IdentifierRules forSnowflake() {
841        return forSnowflake(false);
842    }
843
844    /**
845     * Snowflake 标识符规则(根据 QUOTED_IDENTIFIERS_IGNORE_CASE 决定)
846     *
847     * <p>Snowflake 默认行为:
848     * <ul>
849     * <li>Unquoted: 折叠为大写,比较不敏感(与 Oracle 相同)
850     * <li>Quoted: 保留原样,比较敏感
851     * </ul>
852     *
853     * <p>当 QUOTED_IDENTIFIERS_IGNORE_CASE = TRUE 时:
854     * <ul>
855     * <li>Quoted 标识符也折叠为大写,比较不敏感
856     * </ul>
857     *
858     * @param quotedIdentifiersIgnoreCase Snowflake QUOTED_IDENTIFIERS_IGNORE_CASE 参数
859     */
860    public static IdentifierRules forSnowflake(boolean quotedIdentifiersIgnoreCase) {
861        if (quotedIdentifiersIgnoreCase) {
862            // Quoted identifiers also fold to upper and are case-insensitive
863            return new IdentifierRules(
864                CaseFold.UPPER,
865                CaseCompare.INSENSITIVE,
866                CaseFold.UPPER,           // quoted 也折叠为大写
867                CaseCompare.INSENSITIVE   // quoted 也不敏感
868            );
869        }
870        return forOracle();  // 默认:quoted 保留原样且敏感
871    }
872
873    /**
874     * SAP HANA 标识符规则(与 Oracle 相同)
875     *
876     * <p><strong>实际数据库行为(SAP HANA):</strong>
877     * <ul>
878     * <li>Unquoted: 折叠为大写,比较不敏感 (与 Oracle 相同)
879     * <li>Quoted: 保留原样,比较敏感
880     * </ul>
881     *
882     * <p><strong>与 Legacy TSQLEnv 兼容性:</strong>
883     * <ul>
884     * <li>tableCollationCaseSensitive = {@code false} → ⚠️ <strong>PARTIAL</strong> (both fold to UPPER, COMPATIBLE)
885     * <li>参见 {@link #forOracle()} 的详细说明
886     * </ul>
887     */
888    public static IdentifierRules forHANA() {
889        return forOracle();
890    }
891
892    /**
893     * Presto / Trino 标识符规则
894     *
895     * <p><strong>实际数据库行为(Presto/Trino):</strong>
896     * <ul>
897     * <li>Unquoted: 折叠为小写,比较不敏感 (CREATE TABLE MyTable → stored as mytable)
898     * <li>Quoted: 保留原样,但与 unquoted 规则一致(比较时仍不敏感)
899     * </ul>
900     *
901     * <p><strong>与 Legacy TSQLEnv 兼容性:</strong>
902     * <ul>
903     * <li>tableCollationCaseSensitive = {@code false} → ⚠️ <strong>PARTIAL</strong> (legacy folded to UPPER, new folds to LOWER)
904     * <li>columnCollationCaseSensitive = {@code false} → ⚠️ <strong>PARTIAL</strong> (legacy folded to UPPER, new folds to LOWER)
905     * </ul>
906     *
907     * <p><strong>测试用例影响:</strong>
908     * <ul>
909     * <li>✅ 新规则正确:Presto/Trino 折叠为小写,quoted 标识符与 unquoted 规则一致</li>
910     * <li>⚠️ 如果旧测试期望 "MyTable" → "MYTABLE",则测试会失败</li>
911     * <li>⚠️ 应更新测试期望为小写 (如 "MyTable" → "mytable")</li>
912     * </ul>
913     *
914     * <p><strong>IdentifierRules 配置:</strong>
915     * <ul>
916     * <li>Unquoted: 折叠为小写 ({@link CaseFold#LOWER}), 不敏感 ({@link CaseCompare#INSENSITIVE})
917     * <li>Quoted: 保留原样 ({@link CaseFold#NONE}), 与 unquoted 一致 ({@link CaseCompare#SAME_AS_UNQUOTED})
918     * </ul>
919     */
920    public static IdentifierRules forPresto() {
921        return PRESTO;
922    }
923
924    /**
925     * Vertica 标识符规则(与 Presto 相同)
926     *
927     * <p><strong>实际数据库行为(Vertica):</strong>
928     * <ul>
929     * <li>Unquoted: 折叠为小写,比较不敏感 (与 Presto 相同)
930     * <li>Quoted: 保留原样,但与 unquoted 规则一致
931     * </ul>
932     *
933     * <p><strong>与 Legacy TSQLEnv 兼容性:</strong>
934     * <ul>
935     * <li>tableCollationCaseSensitive = {@code false} → ⚠️ <strong>PARTIAL</strong> (legacy folded to UPPER, new folds to LOWER)
936     * <li>参见 {@link #forPresto()} 的详细说明
937     * </ul>
938     */
939    public static IdentifierRules forVertica() {
940        return forPresto();
941    }
942
943    /**
944     * Hive / SparkSQL / Impala / Databricks 标识符规则
945     *
946     * <p><strong>实际数据库行为:</strong>反引号只允许名字里出现特殊字符和保留字,不改变大小写规则,
947     * 所以 {@code `Orders`}、{@code `ORDERS`} 与 {@code orders} 是同一个对象:
948     * <ul>
949     * <li>Hive (LanguageManual DDL): "Table names and column names are case insensitive"
950     * <li>Spark SQL (Identifiers): "Both regular identifiers and delimited identifiers are case-insensitive."
951     * <li>Impala (Overview of Impala Identifiers): t1 and T1 "always refer to the same table,
952     *     regardless of quote characters"; names are folded to lowercase
953     * <li>Databricks (Identifiers): "Identifiers are case-insensitive when referenced."
954     * </ul>
955     *
956     * <p><strong>IdentifierRules 配置:</strong>
957     * <ul>
958     * <li>Unquoted: 折叠为小写 ({@link CaseFold#LOWER}), 比较不敏感 ({@link CaseCompare#INSENSITIVE})
959     * <li>Quoted: 保留原样 ({@link CaseFold#NONE}), 比较不敏感 ({@link CaseCompare#INSENSITIVE})
960     * </ul>
961     * Quoted 不折叠:Spark / Databricks 的列名保留大小写(元数据里的 {@code `Amount`} 显示为
962     * {@code Amount}),只是比较时不区分;折叠会把元数据的写法改成小写。
963     *
964     * <p><strong>与 Legacy TSQLEnv 兼容性:</strong>
965     * <ul>
966     * <li>tableCollationCaseSensitive = {@code false} → ⚠️ <strong>PARTIAL</strong> (legacy folded to UPPER, new folds to LOWER)
967     * </ul>
968     */
969    public static IdentifierRules forHive() {
970        return HIVE;
971    }
972
973    /**
974     * Teradata 标识符规则
975     *
976     * <p><strong>实际数据库行为:</strong>对象名不区分大小写,加不加双引号都一样;双引号只允许名字里
977     * 出现特殊字符和保留字。所以 {@code "ORDERS"}、{@code "Orders"} 与 {@code orders} 是同一个对象:
978     * <ul>
979     * <li>SAS/ACCESS Teradata naming conventions: "A name, even when enclosed in double quotation
980     *     marks, is not case sensitive. For example, CUSTOMER is the same as customer."
981     * <li>Exasol Teradata migration guide: object names in Teradata are case insensitive
982     * </ul>
983     * 这对元数据尤其要紧:sqlflow-exporter 导出的 Teradata 名字全部带双引号
984     * ({@code '"' || TRIM(TABLENAME) || '"'}),SQL 里不带引号的引用必须能找到它们。
985     *
986     * <p><strong>IdentifierRules 配置:</strong>
987     * <ul>
988     * <li>Unquoted: 折叠为小写 ({@link CaseFold#LOWER}), 比较不敏感 ({@link CaseCompare#INSENSITIVE})
989     * <li>Quoted: 保留原样 ({@link CaseFold#NONE}), 比较不敏感 ({@link CaseCompare#INSENSITIVE})
990     * </ul>
991     * Quoted 不折叠:Teradata 按创建时的大小写存名字(元数据里的 {@code "ORDERS"} 显示为
992     * {@code ORDERS}),只是比较时不区分。
993     *
994     * <p><strong>与 Legacy TSQLEnv 兼容性:</strong>
995     * <ul>
996     * <li>tableCollationCaseSensitive = {@code false} → ⚠️ <strong>PARTIAL</strong> (legacy folded to UPPER, new folds to LOWER)
997     * </ul>
998     */
999    public static IdentifierRules forTeradata() {
1000        return TERADATA;
1001    }
1002
1003    /**
1004     * Athena 标识符规则(与 Presto 相同)
1005     *
1006     * <p><strong>实际数据库行为(AWS Athena):</strong>
1007     * <ul>
1008     * <li>Unquoted: 折叠为小写,比较不敏感 (与 Presto 相同,基于 Trino/Presto)
1009     * <li>Quoted: 保留原样,但与 unquoted 规则一致
1010     * </ul>
1011     *
1012     * <p><strong>与 Legacy TSQLEnv 兼容性:</strong>
1013     * <ul>
1014     * <li>tableCollationCaseSensitive = {@code false} → ⚠️ <strong>PARTIAL</strong> (legacy folded to UPPER, new folds to LOWER)
1015     * <li>参见 {@link #forPresto()} 的详细说明
1016     * </ul>
1017     */
1018    public static IdentifierRules forAthena() {
1019        return forPresto();
1020    }
1021
1022    /**
1023     * GaussDB 标识符规则(与 PostgreSQL 相同)
1024     *
1025     * <p><strong>实际数据库行为(华为 GaussDB):</strong>
1026     * <ul>
1027     * <li>Unquoted: 折叠为小写,比较不敏感 (与 PostgreSQL 相同,基于 PostgreSQL)
1028     * <li>Quoted: 保留原样,比较敏感
1029     * </ul>
1030     *
1031     * <p><strong>与 Legacy TSQLEnv 兼容性:</strong>
1032     * <ul>
1033     * <li>tableCollationCaseSensitive = {@code false} → ⚠️ <strong>PARTIAL</strong> (legacy folded to UPPER, new folds to LOWER)
1034     * <li>参见 {@link #forPostgreSQL()} 的详细说明
1035     * </ul>
1036     */
1037    public static IdentifierRules forGaussDB() {
1038        return forPostgreSQL();
1039    }
1040
1041    /**
1042     * Databricks 标识符规则(与 Hive 相同)
1043     *
1044     * <p><strong>实际数据库行为(Databricks "Identifiers"):</strong>
1045     * "Identifiers are case-insensitive when referenced.",反引号标识符也一样,
1046     * 按 UTF8_LCASE collation 比较。
1047     *
1048     * <p><strong>IdentifierRules 配置:</strong>见 {@link #forHive()}
1049     * (unquoted 与 quoted 都折叠为小写,比较不敏感)。
1050     */
1051    public static IdentifierRules forDatabricks() {
1052        return forHive();
1053    }
1054
1055    /**
1056     * Apache Flink SQL 标识符规则
1057     *
1058     * <p><strong>实际数据库行为(Flink SQL Queries Overview):</strong>"Flink SQL uses a lexical
1059     * policy for identifier (table, attribute, function names) similar to Java: The case of
1060     * identifiers is preserved whether or not they are quoted. After which, identifiers are
1061     * matched case-sensitively." 反引号只允许名字里出现非字母数字字符,不改变大小写规则。
1062     * 所以 {@code `userId`} 与 {@code userId} 是同一个名字,而 {@code userId} 与 {@code userid}
1063     * 是两个名字。
1064     *
1065     * <p><strong>IdentifierRules 配置:</strong>
1066     * <ul>
1067     * <li>Unquoted: 保留原样 ({@link CaseFold#NONE}), 比较敏感 ({@link CaseCompare#SENSITIVE})
1068     * <li>Quoted: 保留原样 ({@link CaseFold#NONE}), 比较敏感 ({@link CaseCompare#SENSITIVE})
1069     * </ul>
1070     *
1071     * <p><strong>与 Legacy TSQLEnv 兼容性:</strong>
1072     * <ul>
1073     * <li>tableCollationCaseSensitive = {@code false} → ❌ <strong>INCOMPATIBLE</strong> (legacy compared case-insensitively)
1074     * </ul>
1075     */
1076    public static IdentifierRules forFlink() {
1077        return FLINK;
1078    }
1079
1080    /**
1081     * Apache Doris 标识符规则(与 MySQL 相同)
1082     *
1083     * <p><strong>行为说明:</strong>
1084     * <ul>
1085     * <li>Doris 是 MySQL 兼容的 OLAP 数据库
1086     * <li>标识符处理遵循 MySQL 的规则
1087     * <li>表名/数据库名大小写敏感性取决于操作系统(类似 MySQL lower_case_table_names)
1088     * <li>列名始终大小写不敏感
1089     * </ul>
1090     *
1091     * <p><strong>与 Legacy TSQLEnv 兼容性:</strong>
1092     * <ul>
1093     * <li>tableCollationCaseSensitive = {@code false} → ⚠️ <strong>PARTIAL</strong>
1094     * <li>catalogCollationCaseSensitive = {@code true} → ✅ <strong>COMPATIBLE</strong>
1095     * </ul>
1096     *
1097     * @param lowerCaseTableNames {@code lower_case_table_names} 值(0, 1, 2),默认使用 1
1098     */
1099    public static IdentifierRules forDoris(int lowerCaseTableNames) {
1100        return forMySQL(lowerCaseTableNames);
1101    }
1102
1103    /**
1104     * Apache Doris 标识符规则(默认配置,相当于 lower_case_table_names=1)
1105     *
1106     * <p><strong>行为说明:</strong>
1107     * <ul>
1108     * <li>Doris 是 MySQL 兼容的 OLAP 数据库
1109     * <li>默认情况下,表名折叠为小写,比较不敏感
1110     * </ul>
1111     */
1112    public static IdentifierRules forDoris() {
1113        return forMySQL(1);  // Default to lowercase folding, case-insensitive
1114    }
1115
1116    /**
1117     * Apache Doris 列名规则(与 MySQL 列名相同,始终大小写不敏感)
1118     */
1119    public static IdentifierRules forDorisColumn() {
1120        return forMySQLColumn();
1121    }
1122
1123    /**
1124     * StarRocks 标识符规则(带参数,相当于 MySQL 的 lower_case_table_names)
1125     *
1126     * <p><strong>行为说明:</strong>
1127     * <ul>
1128     * <li>StarRocks 是 Doris 的分支,MySQL 兼容的 OLAP 数据库
1129     * <li>与 Doris 和 MySQL 使用相同的标识符规则
1130     * </ul>
1131     *
1132     * @param lowerCaseTableNames {@code lower_case_table_names} 值(0, 1, 2),默认使用 1
1133     */
1134    public static IdentifierRules forStarrocks(int lowerCaseTableNames) {
1135        return forMySQL(lowerCaseTableNames);
1136    }
1137
1138    /**
1139     * StarRocks 标识符规则(默认配置,相当于 lower_case_table_names=1)
1140     *
1141     * <p><strong>行为说明:</strong>
1142     * <ul>
1143     * <li>StarRocks 是 Doris 的分支,MySQL 兼容的 OLAP 数据库
1144     * <li>默认情况下,表名折叠为小写,比较不敏感
1145     * </ul>
1146     */
1147    public static IdentifierRules forStarrocks() {
1148        return forMySQL(1);  // Default to lowercase folding, case-insensitive
1149    }
1150
1151    /**
1152     * StarRocks 列名规则(与 MySQL 列名相同,始终大小写不敏感)
1153     */
1154    public static IdentifierRules forStarrocksColumn() {
1155        return forMySQLColumn();
1156    }
1157
1158    /**
1159     * SQLite 标识符规则
1160     *
1161     * <p><strong>实际数据库行为(SQLite 3.45.1 实测):</strong>名字不区分大小写,加不加引号都一样,
1162     * 并保留定义时的大小写。{@code CREATE TABLE "Orders" ("Amount" INT)} 之后,
1163     * {@code SELECT amount FROM orders}、{@code SELECT "amount" FROM "orders"} 都能查到,
1164     * 再建 {@code "orders"} 会报 {@code table "orders" already exists}。
1165     *
1166     * <p><strong>IdentifierRules 配置:</strong>
1167     * <ul>
1168     * <li>Unquoted: 折叠为小写 ({@link CaseFold#LOWER}), 比较不敏感 ({@link CaseCompare#INSENSITIVE})
1169     * <li>Quoted: 保留原样 ({@link CaseFold#NONE}), 比较不敏感 ({@link CaseCompare#INSENSITIVE})
1170     * </ul>
1171     */
1172    public static IdentifierRules forSqlite() {
1173        return SQLITE;
1174    }
1175
1176    /**
1177     * Sybase ASE 标识符规则
1178     *
1179     * <p><strong>实际数据库行为(SAP IQ "Case-sensitivity of Identifiers" 对 ASE 的说明):</strong>
1180     * "In Adaptive Server Enterprise, the case-sensitivity of identifiers follows the
1181     * case-sensitivity of the data",即由服务器安装时的 sort order 决定;双引号只允许名字里出现
1182     * 特殊字符和保留字,不改变大小写规则。GSP 按大小写不敏感的服务器建模(见
1183     * {@code db_collation.md} §2.12,与旧 TSQLEnv 一致),所以带不带引号都不区分大小写。
1184     * binary sort order(区分大小写)的服务器请用
1185     * {@link IdentifierProfile#builder(gudusoft.gsqlparser.EDbVendor)} 配置 NONE/SENSITIVE。
1186     *
1187     * <p><strong>IdentifierRules 配置:</strong>
1188     * <ul>
1189     * <li>Unquoted: 折叠为小写 ({@link CaseFold#LOWER}), 比较不敏感 ({@link CaseCompare#INSENSITIVE})
1190     * <li>Quoted: 保留原样 ({@link CaseFold#NONE}), 比较不敏感 ({@link CaseCompare#INSENSITIVE})
1191     * </ul>
1192     */
1193    public static IdentifierRules forSybase() {
1194        return SYBASE;
1195    }
1196
1197    /**
1198     * SAP IQ / SQL Anywhere 标识符规则
1199     *
1200     * <p><strong>实际数据库行为:</strong>SAP IQ "Case-sensitivity of Identifiers":"SAP Sybase IQ does not
1201     * support case-sensitive identifiers.";SQL Anywhere "Case Sensitivity":SQL Anywhere does not
1202     * support case sensitive identifiers。加不加引号都不区分大小写,并保留定义时的大小写。
1203     *
1204     * <p><strong>IdentifierRules 配置:</strong>
1205     * <ul>
1206     * <li>Unquoted: 折叠为小写 ({@link CaseFold#LOWER}), 比较不敏感 ({@link CaseCompare#INSENSITIVE})
1207     * <li>Quoted: 保留原样 ({@link CaseFold#NONE}), 比较不敏感 ({@link CaseCompare#INSENSITIVE})
1208     * </ul>
1209     */
1210    public static IdentifierRules forSqlAnywhere() {
1211        return SQL_ANYWHERE;
1212    }
1213
1214    /**
1215     * Microsoft Access 标识符规则
1216     *
1217     * <p><strong>实际数据库行为:</strong>Access 的表名、字段名不区分大小写;方括号只用来引用含特殊字符
1218     * 或保留字的名字,不改变大小写规则,并保留定义时的大小写。
1219     *
1220     * <p><strong>IdentifierRules 配置:</strong>
1221     * <ul>
1222     * <li>Unquoted: 折叠为小写 ({@link CaseFold#LOWER}), 比较不敏感 ({@link CaseCompare#INSENSITIVE})
1223     * <li>Quoted: 保留原样 ({@link CaseFold#NONE}), 比较不敏感 ({@link CaseCompare#INSENSITIVE})
1224     * </ul>
1225     */
1226    public static IdentifierRules forAccess() {
1227        return ACCESS;
1228    }
1229
1230    /**
1231     * DAX 标识符规则
1232     *
1233     * <p><strong>实际行为(Microsoft Learn "DAX syntax"):</strong>"All object names are
1234     * case-insensitive; for example, the names SALES and Sales would represent the same table."
1235     * 单引号表名 {@code 'Sales'}、方括号列名 {@code [Amount]} 也一样。
1236     *
1237     * <p><strong>IdentifierRules 配置:</strong>
1238     * <ul>
1239     * <li>Unquoted: 折叠为小写 ({@link CaseFold#LOWER}), 比较不敏感 ({@link CaseCompare#INSENSITIVE})
1240     * <li>Quoted: 保留原样 ({@link CaseFold#NONE}), 比较不敏感 ({@link CaseCompare#INSENSITIVE})
1241     * </ul>
1242     */
1243    public static IdentifierRules forDax() {
1244        return DAX;
1245    }
1246
1247    /**
1248     * MDX 标识符规则
1249     *
1250     * <p><strong>实际行为:</strong>Analysis Services 默认不区分大小写(SQLBI "Letter case-sensitivity in
1251     * DAX, Power BI and Analysis Services":Power BI and Analysis Services are case-insensitive by
1252     * default),方括号界定的名字 {@code [Measures].[Sales]} 与 {@code [measures].[sales]} 相同。
1253     *
1254     * <p><strong>IdentifierRules 配置:</strong>
1255     * <ul>
1256     * <li>Unquoted: 折叠为小写 ({@link CaseFold#LOWER}), 比较不敏感 ({@link CaseCompare#INSENSITIVE})
1257     * <li>Quoted: 保留原样 ({@link CaseFold#NONE}), 比较不敏感 ({@link CaseCompare#INSENSITIVE})
1258     * </ul>
1259     */
1260    public static IdentifierRules forMdx() {
1261        return MDX;
1262    }
1263
1264    /**
1265     * Power Query M 标识符规则
1266     *
1267     * <p><strong>实际行为(Microsoft Learn "Power Query M formula language"):</strong>M 区分大小写,
1268     * 变量名、函数名都是("Because the Power Query M formula language is case sensitive, comparisons
1269     * between items that are identical but have different cases results in identifying the items
1270     * as being different")。{@code #"Name"} 形式只允许名字里出现空格等字符,不改变大小写规则。
1271     *
1272     * <p><strong>IdentifierRules 配置:</strong>
1273     * <ul>
1274     * <li>Unquoted: 保留原样 ({@link CaseFold#NONE}), 比较敏感 ({@link CaseCompare#SENSITIVE})
1275     * <li>Quoted: 保留原样 ({@link CaseFold#NONE}), 比较敏感 ({@link CaseCompare#SENSITIVE})
1276     * </ul>
1277     */
1278    public static IdentifierRules forPowerQuery() {
1279        return POWER_QUERY;
1280    }
1281
1282    /**
1283     * DuckDB 标识符规则
1284     *
1285     * <p><strong>实际数据库行为(DuckDB "Keywords and Identifiers"):</strong>"Identifiers in DuckDB are
1286     * always case-insensitive, similarly to PostgreSQL. However, unlike PostgreSQL (and some other
1287     * major SQL implementations), DuckDB also treats quoted identifiers as case-insensitive."
1288     * 同时保留定义时的大小写。
1289     *
1290     * <p><strong>IdentifierRules 配置:</strong>
1291     * <ul>
1292     * <li>Unquoted: 折叠为小写 ({@link CaseFold#LOWER}), 比较不敏感 ({@link CaseCompare#INSENSITIVE})
1293     * <li>Quoted: 保留原样 ({@link CaseFold#NONE}), 比较不敏感 ({@link CaseCompare#INSENSITIVE})
1294     * </ul>
1295     */
1296    public static IdentifierRules forDuckDB() {
1297        return DUCKDB;
1298    }
1299
1300    /**
1301     * Firebird 标识符规则(与 Oracle 相同)
1302     *
1303     * <p><strong>实际数据库行为(Firebird Language Reference, "Identifiers"):</strong>不带引号的名字
1304     * 按大写存储,带引号的名字按原样存储、区分大小写。所以 {@code "FULLNAME"} 与 {@code FULLNAME}、
1305     * {@code fullname}、{@code FullName} 是同一个名字,而 {@code "FullName"} 与 {@code FullName} 不是。
1306     *
1307     * <p><strong>IdentifierRules 配置:</strong>
1308     * <ul>
1309     * <li>Unquoted: 折叠为大写 ({@link CaseFold#UPPER}), 比较不敏感 ({@link CaseCompare#INSENSITIVE})
1310     * <li>Quoted: 保留原样 ({@link CaseFold#NONE}), 比较敏感 ({@link CaseCompare#SENSITIVE})
1311     * </ul>
1312     */
1313    public static IdentifierRules forFirebird() {
1314        return forOracle();
1315    }
1316
1317    /**
1318     * ANSI / ISO SQL 标识符规则(SQL-92 起的标准,与 Oracle 相同)
1319     *
1320     * <p><strong>标准行为:</strong>regular identifier 不区分大小写,按大写处理(折叠为大写);
1321     * delimited identifier 保留原样、区分大小写。所以 {@code "ORDERS"} 与 {@code orders} 是同一个名字,
1322     * 而 {@code "orders"} 与 {@code orders} 不是。
1323     *
1324     * <p><strong>IdentifierRules 配置:</strong>
1325     * <ul>
1326     * <li>Unquoted: 折叠为大写 ({@link CaseFold#UPPER}), 比较不敏感 ({@link CaseCompare#INSENSITIVE})
1327     * <li>Quoted: 保留原样 ({@link CaseFold#NONE}), 比较敏感 ({@link CaseCompare#SENSITIVE})
1328     * </ul>
1329     */
1330    public static IdentifierRules forAnsi() {
1331        return ANSI;
1332    }
1333
1334    /**
1335     * 通用规则({@code dbvgeneric}):没有具体方言时按 SQL 标准,见 {@link #forAnsi()}
1336     */
1337    public static IdentifierRules forGeneric() {
1338        return forAnsi();
1339    }
1340
1341    // ===== Value semantics =====
1342
1343    /**
1344     * This cell with {@link CaseCompare#SAME_AS_UNQUOTED} resolved — the form that identifies a
1345     * <em>rule domain</em>, so two cells that behave identically are not treated as two domains
1346     * merely because one spells its quoted compare as "same as unquoted".
1347     *
1348     * @return {@code this} when nothing needs resolving, else the resolved equivalent
1349     */
1350    public IdentifierRules resolvedCell() {
1351        if (quotedCompare != CaseCompare.SAME_AS_UNQUOTED
1352                || unquotedCompare == CaseCompare.SAME_AS_UNQUOTED) {
1353            // Nothing to resolve, or a degenerate cell whose unquoted branch is itself the
1354            // indirection: IdentifierService.effectiveCompare leaves that as written, so leave it.
1355            return this;
1356        }
1357        IdentifierRules cached = resolved;
1358        if (cached == null) {
1359            cached = new IdentifierRules(unquotedFold, unquotedCompare, quotedFold, unquotedCompare,
1360                    collapse, maxUtf8Bytes);
1361            resolved = cached;
1362        }
1363        return cached;
1364    }
1365
1366    private transient volatile IdentifierRules resolved;
1367
1368    @Override
1369    public boolean equals(Object o) {
1370        if (this == o) return true;
1371        if (o == null || getClass() != o.getClass()) return false;
1372        IdentifierRules that = (IdentifierRules) o;
1373        return unquotedFold == that.unquotedFold
1374            && unquotedCompare == that.unquotedCompare
1375            && quotedFold == that.quotedFold
1376            && quotedCompare == that.quotedCompare
1377            && collapse == that.collapse
1378            && maxUtf8Bytes == that.maxUtf8Bytes;
1379    }
1380
1381    @Override
1382    public int hashCode() {
1383        int h = Objects.hash(unquotedFold, unquotedCompare, quotedFold, quotedCompare);
1384        if (!hasDefaultExtensions()) {
1385            h = 31 * h + Objects.hash(collapse, maxUtf8Bytes);
1386        }
1387        return h;
1388    }
1389
1390    /** True when the cell uses neither a non-default {@link #collapse} nor truncation. */
1391    boolean hasDefaultExtensions() {
1392        return collapse == CaseCollapse.SIMPLE_FOLD && maxUtf8Bytes == 0;
1393    }
1394
1395    // ===== toString 方法(用于调试) =====
1396
1397    @Override
1398    public String toString() {
1399        if (hasDefaultExtensions()) {
1400            return String.format("IdentifierRules{unquoted=%s/%s, quoted=%s/%s}",
1401                unquotedFold, unquotedCompare, quotedFold, quotedCompare);
1402        }
1403        return String.format("IdentifierRules{unquoted=%s/%s, quoted=%s/%s, collapse=%s, maxUtf8Bytes=%d}",
1404            unquotedFold, unquotedCompare, quotedFold, quotedCompare, collapse, maxUtf8Bytes);
1405    }
1406}