001package gudusoft.gsqlparser.sqlenv;
002
003import gudusoft.gsqlparser.EDbVendor;
004
005import java.util.EnumMap;
006import java.util.Objects;
007
008/**
009 * 厂商标识符配置档案(Vendor Identifier Profile)
010 *
011 * <p>封装每个数据库厂商的完整标识符配置,包括:
012 * <ul>
013 * <li>按对象组(NAME_GROUP, COLUMN_GROUP, ROUTINE_GROUP)的标识符规则
014 * <li>Vendor-specific flags(如 MySQL lower_case_table_names, SQL Server collation)
015 * </li>
016 *
017 * <p>设计目标:
018 * <ul>
019 * <li>集中管理所有 vendor-specific 配置
020 * <li>统一注入路径,避免配置散落在各处
021 * <li>支持缓存失效(通过 fingerprint)
022 * </ul>
023 *
024 * <p>使用示例:
025 * <pre>
026 * // 创建 Oracle profile(使用默认 flags)
027 * IdentifierProfile oracleProfile = IdentifierProfile.forVendor(
028 *     EDbVendor.dbvoracle,
029 *     VendorFlags.defaults()
030 * );
031 *
032 * // 创建 MySQL profile(指定 lower_case_table_names)
033 * IdentifierProfile mysqlProfile = IdentifierProfile.forVendor(
034 *     EDbVendor.dbvmysql,
035 *     new VendorFlags(1, null, false, false)  // lower_case_table_names = 1
036 * );
037 *
038 * // 查询规则
039 * IdentifierRules tableRules = oracleProfile.getRules(ESQLDataObjectType.dotTable);
040 * </pre>
041 *
042 * @since 3.1.0.9
043 */
044public final class IdentifierProfile {
045
046    private final EDbVendor vendor;
047
048    // ===== 按对象组的规则 =====
049    private final EnumMap<ObjectGroup, IdentifierRules> rulesByGroup;
050
051    // ===== Vendor-specific flags =====
052    private final VendorFlags flags;
053
054    // ===== 对象组定义 =====
055
056    /**
057     * 对象组(用于区分不同对象类型的标识符规则)
058     *
059     * <p>某些数据库对不同对象类型使用不同的大小写规则:
060     * <ul>
061     * <li>BigQuery: 表名敏感,列名不敏感
062     * <li>MySQL: 表名根据 lower_case_table_names,列名始终不敏感
063     * </ul>
064     */
065    public enum ObjectGroup {
066        /**
067         * 名称组(catalog, schema, table, view, procedure, trigger)
068         */
069        NAME_GROUP,
070
071        /**
072         * 列组(column)
073         */
074        COLUMN_GROUP,
075
076        /**
077         * 函数组(function)
078         * <p>某些数据库函数名规则与表名不同
079         */
080        ROUTINE_GROUP
081    }
082
083    // ===== Vendor flags 封装 =====
084
085    /**
086     * Vendor-specific flags(厂商特定配置)
087     *
088     * <p>封装各数据库厂商的特殊配置参数
089     */
090    public static class VendorFlags {
091        /**
092         * MySQL: lower_case_table_names 系统变量(0, 1, 2)
093         * <ul>
094         * <li>0: 大小写敏感(Unix/Linux)
095         * <li>1: 存储为小写,比较不敏感(Windows)
096         * <li>2: 存储保留原样,比较不敏感(macOS)
097         * </ul>
098         */
099        public final int mysqlLowerCaseTableNames;
100
101        /**
102         * SQL Server / Azure SQL: 默认 collation 名称
103         * <p>例如: "SQL_Latin1_General_CP1_CI_AS"
104         */
105        public final String defaultCollation;
106
107        /**
108         * Redshift: enable_case_sensitive_identifier 参数
109         * <p>默认 false(与 PostgreSQL 一致)
110         */
111        public final boolean redshiftEnableCaseSensitive;
112
113        /**
114         * Snowflake: QUOTED_IDENTIFIERS_IGNORE_CASE 会话参数
115         * <p>默认 false(quoted 标识符大小写敏感)
116         */
117        public final boolean snowflakeQuotedIdentifiersIgnoreCase;
118
119        /**
120         * MySQL 家族: sql_mode 中的 ANSI_QUOTES 选项
121         *
122         * <p>为 true 时,双引号 {@code "} 界定标识符(而非字符串字面量)。
123         * 只影响 {@link IdentifierCodec} 的引号识别/编解码,不影响大小写
124         * 折叠/比较规则(fold/compare 与 ANSI_QUOTES 无关)。
125         * <p>默认 false(MySQL 出厂默认,{@code "} 是字符串定界符)。
126         */
127        public final boolean mysqlAnsiQuotes;
128
129        /**
130         * 构造 vendor flags(ansiQuotes 取默认值 false)
131         *
132         * @param mysqlLowerCaseTableNames MySQL lower_case_table_names (0, 1, 2)
133         * @param defaultCollation SQL Server collation 名称
134         * @param redshiftEnableCaseSensitive Redshift case sensitive 开关
135         * @param snowflakeQuotedIdentifiersIgnoreCase Snowflake quoted ignore case 开关
136         */
137        public VendorFlags(int mysqlLowerCaseTableNames,
138                          String defaultCollation,
139                          boolean redshiftEnableCaseSensitive,
140                          boolean snowflakeQuotedIdentifiersIgnoreCase) {
141            this(mysqlLowerCaseTableNames, defaultCollation, redshiftEnableCaseSensitive,
142                 snowflakeQuotedIdentifiersIgnoreCase, false);
143        }
144
145        /**
146         * 构造 vendor flags(完整参数)
147         *
148         * @param mysqlLowerCaseTableNames MySQL lower_case_table_names (0, 1, 2)
149         * @param defaultCollation SQL Server collation 名称
150         * @param redshiftEnableCaseSensitive Redshift case sensitive 开关
151         * @param snowflakeQuotedIdentifiersIgnoreCase Snowflake quoted ignore case 开关
152         * @param mysqlAnsiQuotes MySQL 家族 sql_mode ANSI_QUOTES 开关
153         */
154        public VendorFlags(int mysqlLowerCaseTableNames,
155                          String defaultCollation,
156                          boolean redshiftEnableCaseSensitive,
157                          boolean snowflakeQuotedIdentifiersIgnoreCase,
158                          boolean mysqlAnsiQuotes) {
159            this.mysqlLowerCaseTableNames = mysqlLowerCaseTableNames;
160            this.defaultCollation = defaultCollation;
161            this.redshiftEnableCaseSensitive = redshiftEnableCaseSensitive;
162            this.snowflakeQuotedIdentifiersIgnoreCase = snowflakeQuotedIdentifiersIgnoreCase;
163            this.mysqlAnsiQuotes = mysqlAnsiQuotes;
164        }
165
166        /**
167         * 默认 flags(用于大部分数据库)
168         */
169        public static VendorFlags defaults() {
170            return new VendorFlags(
171                // MySQL lower_case_table_names = 1 (P0d.2): the unconfigured default keeps
172                // the legacy case-INSENSITIVE table equality, and mode 1's LOWER fold keeps
173                // keyForMap bucket keys coherent with that relation (mode 2 would bucket
174                // case-variants apart while areEqual equates them). Mode 0 (Linux
175                // case-sensitive) remains available through explicit configuration.
176                1,
177                "SQL_Latin1_General_CP1_CI_AS",  // SQL Server 默认 collation
178                false,                        // Redshift case sensitive = false
179                false                         // Snowflake quoted ignore case = false
180            );
181        }
182
183        @Override
184        public boolean equals(Object o) {
185            if (this == o) return true;
186            if (o == null || getClass() != o.getClass()) return false;
187            VendorFlags that = (VendorFlags) o;
188            return mysqlLowerCaseTableNames == that.mysqlLowerCaseTableNames &&
189                   redshiftEnableCaseSensitive == that.redshiftEnableCaseSensitive &&
190                   snowflakeQuotedIdentifiersIgnoreCase == that.snowflakeQuotedIdentifiersIgnoreCase &&
191                   mysqlAnsiQuotes == that.mysqlAnsiQuotes &&
192                   Objects.equals(defaultCollation, that.defaultCollation);
193        }
194
195        @Override
196        public int hashCode() {
197            return Objects.hash(mysqlLowerCaseTableNames, defaultCollation,
198                              redshiftEnableCaseSensitive, snowflakeQuotedIdentifiersIgnoreCase,
199                              mysqlAnsiQuotes);
200        }
201    }
202
203    // ===== 构造函数 =====
204
205    /**
206     * 构造标识符配置档案
207     *
208     * @param vendor 数据库厂商
209     * @param rulesByGroup 按对象组的规则
210     * @param flags vendor-specific flags
211     */
212    private IdentifierProfile(EDbVendor vendor,
213                             EnumMap<ObjectGroup, IdentifierRules> rulesByGroup,
214                             VendorFlags flags) {
215        this.vendor = Objects.requireNonNull(vendor, "vendor");
216        this.rulesByGroup = new EnumMap<>(Objects.requireNonNull(rulesByGroup, "rulesByGroup"));
217        this.flags = Objects.requireNonNull(flags, "flags");
218        // Immutable → compute once; getFingerprint sits on the per-compare hot path
219        // since areEqual/canonKey stamp keys with it (P0d).
220        this.fingerprint = Objects.hash(
221            vendor,
222            flags,
223            this.rulesByGroup.get(ObjectGroup.NAME_GROUP),
224            this.rulesByGroup.get(ObjectGroup.COLUMN_GROUP),
225            this.rulesByGroup.get(ObjectGroup.ROUTINE_GROUP)
226        );
227    }
228
229    private final long fingerprint;
230
231    // ===== 工厂方法:根据厂商和 flags 创建 =====
232
233    /**
234     * 为指定厂商创建标识符配置档案
235     *
236     * @param vendor 数据库厂商
237     * @param flags vendor-specific flags
238     * @return 标识符配置档案
239     */
240    public static IdentifierProfile forVendor(EDbVendor vendor, VendorFlags flags) {
241        EnumMap<ObjectGroup, IdentifierRules> rules = defaultRulesFor(vendor, flags);
242        return new IdentifierProfile(vendor, rules, flags);
243    }
244
245    // ===== Builder entry points =====
246
247    /**
248     * Create a builder seeded from default rules for the given vendor
249     * with default VendorFlags.
250     *
251     * @param vendor database vendor
252     * @return a new Builder
253     */
254    public static Builder builder(EDbVendor vendor) {
255        return builder(vendor, VendorFlags.defaults());
256    }
257
258    /**
259     * Create a builder seeded from default rules for the given vendor
260     * and the specified VendorFlags.
261     *
262     * @param vendor database vendor
263     * @param flags vendor-specific flags
264     * @return a new Builder
265     */
266    public static Builder builder(EDbVendor vendor, VendorFlags flags) {
267        Objects.requireNonNull(vendor, "vendor");
268        Objects.requireNonNull(flags, "flags");
269        return new Builder(vendor, flags, defaultRulesFor(vendor, flags));
270    }
271
272    /**
273     * Create a builder pre-populated from this profile's current state.
274     * Enables deriving a variant without re-specifying everything.
275     *
276     * @return a new Builder seeded from this profile
277     */
278    public Builder toBuilder() {
279        return new Builder(this.vendor, this.flags, new EnumMap<>(this.rulesByGroup));
280    }
281
282    // ===== Internal: single source of truth for default rules =====
283
284    /**
285     * Compute the default per-group rules for the given vendor and flags.
286     * This is the sole source of truth for default rules -- both forVendor()
287     * and the Builder use this method.
288     *
289     * @param vendor database vendor
290     * @param flags vendor-specific flags
291     * @return mutable EnumMap with rules for all three groups
292     */
293    static EnumMap<ObjectGroup, IdentifierRules> defaultRulesFor(EDbVendor vendor, VendorFlags flags) {
294        EnumMap<ObjectGroup, IdentifierRules> rules = new EnumMap<>(ObjectGroup.class);
295
296        switch (vendor) {
297            case dbvoracle: {
298                IdentifierRules rule = IdentifierRules.forOracle();
299                rules.put(ObjectGroup.NAME_GROUP, rule);
300                rules.put(ObjectGroup.COLUMN_GROUP, rule);
301                rules.put(ObjectGroup.ROUTINE_GROUP, rule);
302                break;
303            }
304
305            case dbvdameng: {
306                IdentifierRules rule = IdentifierRules.forDameng();
307                rules.put(ObjectGroup.NAME_GROUP, rule);
308                rules.put(ObjectGroup.COLUMN_GROUP, rule);
309                rules.put(ObjectGroup.ROUTINE_GROUP, rule);
310                break;
311            }
312
313            case dbvpostgresql:
314            case dbvduckdb:
315            case dbvgreenplum:
316            case dbvgaussdb:
317            case dbvedb:
318            case dbvsqlite: {
319                IdentifierRules rule = IdentifierRules.forPostgreSQL();
320                rules.put(ObjectGroup.NAME_GROUP, rule);
321                rules.put(ObjectGroup.COLUMN_GROUP, rule);
322                rules.put(ObjectGroup.ROUTINE_GROUP, rule);
323                break;
324            }
325
326            case dbvredshift: {
327                IdentifierRules rule = flags.redshiftEnableCaseSensitive
328                    ? IdentifierRules.forCouchbase()
329                    : IdentifierRules.forPostgreSQL();
330                rules.put(ObjectGroup.NAME_GROUP, rule);
331                rules.put(ObjectGroup.COLUMN_GROUP, rule);
332                rules.put(ObjectGroup.ROUTINE_GROUP, rule);
333                break;
334            }
335
336            case dbvclickhouse: {
337                IdentifierRules rule = IdentifierRules.forCouchbase();
338                rules.put(ObjectGroup.NAME_GROUP, rule);
339                rules.put(ObjectGroup.COLUMN_GROUP, rule);
340                rules.put(ObjectGroup.ROUTINE_GROUP, rule);
341                break;
342            }
343
344            case dbvcouchbase: {
345                IdentifierRules rule = IdentifierRules.forCouchbase();
346                rules.put(ObjectGroup.NAME_GROUP, rule);
347                rules.put(ObjectGroup.COLUMN_GROUP, rule);
348                rules.put(ObjectGroup.ROUTINE_GROUP, rule);
349                break;
350            }
351
352            case dbvmssql:
353            case dbvazuresql: {
354                IdentifierRules rule = IdentifierRules.forSQLServer(flags.defaultCollation);
355                rules.put(ObjectGroup.NAME_GROUP, rule);
356                rules.put(ObjectGroup.COLUMN_GROUP, rule);
357                rules.put(ObjectGroup.ROUTINE_GROUP, rule);
358                break;
359            }
360
361            case dbvmysql: {
362                rules.put(ObjectGroup.NAME_GROUP,
363                         IdentifierRules.forMySQL(flags.mysqlLowerCaseTableNames));
364                rules.put(ObjectGroup.COLUMN_GROUP, IdentifierRules.forMySQLColumn());
365                rules.put(ObjectGroup.ROUTINE_GROUP, IdentifierRules.forMySQLRoutine());
366                break;
367            }
368
369            case dbvoceanbase: {
370                // Phase 1: identifier rules mirror MySQL because the default
371                // EOBTenantMode is MYSQL and the IdentifierProfile is built
372                // before the user has had a chance to call setOBTenantMode.
373                // TODO(oceanbase Phase 3): split this into MYSQL/ORACLE
374                // branches once the EOBTenantMode is queryable from
375                // IdentifierProfileFlags or wherever the profile is built.
376                rules.put(ObjectGroup.NAME_GROUP,
377                         IdentifierRules.forMySQL(flags.mysqlLowerCaseTableNames));
378                rules.put(ObjectGroup.COLUMN_GROUP, IdentifierRules.forMySQLColumn());
379                rules.put(ObjectGroup.ROUTINE_GROUP, IdentifierRules.forMySQLRoutine());
380                break;
381            }
382
383            case dbvdoris: {
384                rules.put(ObjectGroup.NAME_GROUP, IdentifierRules.forDoris());
385                rules.put(ObjectGroup.COLUMN_GROUP, IdentifierRules.forDorisColumn());
386                rules.put(ObjectGroup.ROUTINE_GROUP, IdentifierRules.forMySQLRoutine());
387                break;
388            }
389
390            case dbvstarrocks: {
391                rules.put(ObjectGroup.NAME_GROUP, IdentifierRules.forStarrocks());
392                rules.put(ObjectGroup.COLUMN_GROUP, IdentifierRules.forStarrocksColumn());
393                rules.put(ObjectGroup.ROUTINE_GROUP, IdentifierRules.forMySQLRoutine());
394                break;
395            }
396
397            case dbvbigquery: {
398                rules.put(ObjectGroup.NAME_GROUP, IdentifierRules.forBigQueryTable());
399                rules.put(ObjectGroup.COLUMN_GROUP, IdentifierRules.forBigQueryColumn());
400                rules.put(ObjectGroup.ROUTINE_GROUP, IdentifierRules.forBigQueryTable());
401                break;
402            }
403
404            case dbvdb2:
405            case dbvnetezza:
406            case dbvexasol: {
407                IdentifierRules rule = IdentifierRules.forDB2();
408                rules.put(ObjectGroup.NAME_GROUP, rule);
409                rules.put(ObjectGroup.COLUMN_GROUP, rule);
410                rules.put(ObjectGroup.ROUTINE_GROUP, rule);
411                break;
412            }
413
414            case dbvsnowflake: {
415                IdentifierRules rule = IdentifierRules.forSnowflake(
416                    flags.snowflakeQuotedIdentifiersIgnoreCase);
417                rules.put(ObjectGroup.NAME_GROUP, rule);
418                rules.put(ObjectGroup.COLUMN_GROUP, rule);
419                rules.put(ObjectGroup.ROUTINE_GROUP, rule);
420                break;
421            }
422
423            case dbvhana: {
424                IdentifierRules rule = IdentifierRules.forHANA();
425                rules.put(ObjectGroup.NAME_GROUP, rule);
426                rules.put(ObjectGroup.COLUMN_GROUP, rule);
427                rules.put(ObjectGroup.ROUTINE_GROUP, rule);
428                break;
429            }
430
431            case dbvpresto:
432            case dbvtrino: {
433                IdentifierRules rule = IdentifierRules.forPresto();
434                rules.put(ObjectGroup.NAME_GROUP, rule);
435                rules.put(ObjectGroup.COLUMN_GROUP, rule);
436                rules.put(ObjectGroup.ROUTINE_GROUP, rule);
437                break;
438            }
439
440            case dbvvertica: {
441                IdentifierRules rule = IdentifierRules.forVertica();
442                rules.put(ObjectGroup.NAME_GROUP, rule);
443                rules.put(ObjectGroup.COLUMN_GROUP, rule);
444                rules.put(ObjectGroup.ROUTINE_GROUP, rule);
445                break;
446            }
447
448            case dbvhive:
449            case dbvsparksql:
450            case dbvflink:
451            case dbvimpala:
452            case dbvdatabricks: {
453                IdentifierRules rule = IdentifierRules.forHive();
454                rules.put(ObjectGroup.NAME_GROUP, rule);
455                rules.put(ObjectGroup.COLUMN_GROUP, rule);
456                rules.put(ObjectGroup.ROUTINE_GROUP, rule);
457                break;
458            }
459
460            case dbvteradata: {
461                IdentifierRules rule = IdentifierRules.forTeradata();
462                rules.put(ObjectGroup.NAME_GROUP, rule);
463                rules.put(ObjectGroup.COLUMN_GROUP, rule);
464                rules.put(ObjectGroup.ROUTINE_GROUP, rule);
465                break;
466            }
467
468            case dbvathena: {
469                IdentifierRules rule = IdentifierRules.forAthena();
470                rules.put(ObjectGroup.NAME_GROUP, rule);
471                rules.put(ObjectGroup.COLUMN_GROUP, rule);
472                rules.put(ObjectGroup.ROUTINE_GROUP, rule);
473                break;
474            }
475
476            default: {
477                IdentifierRules rule = IdentifierRules.forGeneric();
478                rules.put(ObjectGroup.NAME_GROUP, rule);
479                rules.put(ObjectGroup.COLUMN_GROUP, rule);
480                rules.put(ObjectGroup.ROUTINE_GROUP, rule);
481                break;
482            }
483        }
484
485        return rules;
486    }
487
488    // ===== Builder inner class =====
489
490    /**
491     * Builder for creating customized IdentifierProfile instances.
492     *
493     * <p>The builder is seeded from vendor defaults. Overrides can be applied
494     * per object group. The built profile is immutable.
495     *
496     * <p>Usage example:
497     * <pre>
498     * IdentifierProfile profile = IdentifierProfile
499     *     .builder(EDbVendor.dbvmssql)
500     *     .withColumnRules(customColumnRules)
501     *     .build();
502     * </pre>
503     */
504    public static final class Builder {
505
506        private EDbVendor vendor;
507        private VendorFlags flags;
508        private EnumMap<ObjectGroup, IdentifierRules> seededRules;
509        private final EnumMap<ObjectGroup, IdentifierRules> overrides;
510
511        Builder(EDbVendor vendor, VendorFlags flags,
512                EnumMap<ObjectGroup, IdentifierRules> seedRules) {
513            this.vendor = vendor;
514            this.flags = flags;
515            this.seededRules = new EnumMap<>(seedRules);
516            this.overrides = new EnumMap<>(ObjectGroup.class);
517        }
518
519        /**
520         * Override VendorFlags (replaces default-seeded flags).
521         * Re-seeds rules that depend on flags while preserving
522         * explicit overrides set via withRules().
523         *
524         * @param flags new vendor flags
525         * @return this builder
526         */
527        public Builder withFlags(VendorFlags flags) {
528            this.flags = Objects.requireNonNull(flags, "flags");
529            this.seededRules = defaultRulesFor(this.vendor, flags);
530            return this;
531        }
532
533        /**
534         * Override the rules for a specific object group.
535         *
536         * @param group the object group to override
537         * @param rules the custom rules
538         * @return this builder
539         */
540        public Builder withRules(ObjectGroup group, IdentifierRules rules) {
541            Objects.requireNonNull(group, "group");
542            Objects.requireNonNull(rules, "rules");
543            overrides.put(group, rules);
544            return this;
545        }
546
547        /**
548         * Convenience: override NAME_GROUP rules.
549         *
550         * @param rules the custom rules for names
551         * @return this builder
552         */
553        public Builder withNameRules(IdentifierRules rules) {
554            return withRules(ObjectGroup.NAME_GROUP, rules);
555        }
556
557        /**
558         * Convenience: override COLUMN_GROUP rules.
559         *
560         * @param rules the custom rules for columns
561         * @return this builder
562         */
563        public Builder withColumnRules(IdentifierRules rules) {
564            return withRules(ObjectGroup.COLUMN_GROUP, rules);
565        }
566
567        /**
568         * Convenience: override ROUTINE_GROUP rules.
569         *
570         * @param rules the custom rules for routines
571         * @return this builder
572         */
573        public Builder withRoutineRules(IdentifierRules rules) {
574            return withRules(ObjectGroup.ROUTINE_GROUP, rules);
575        }
576
577        /**
578         * Build an immutable IdentifierProfile.
579         * Merges seeded rules with overrides (overrides win).
580         *
581         * @return the built profile
582         */
583        public IdentifierProfile build() {
584            EnumMap<ObjectGroup, IdentifierRules> merged = new EnumMap<>(seededRules);
585            merged.putAll(overrides);
586            return new IdentifierProfile(vendor, merged, flags);
587        }
588    }
589
590    // ===== 查询接口 =====
591
592    /**
593     * 获取指定对象类型的标识符规则
594     *
595     * @param objectType 对象类型
596     * @return 标识符规则
597     */
598    public IdentifierRules getRules(ESQLDataObjectType objectType) {
599        ObjectGroup group = mapToGroup(objectType);
600        return rulesByGroup.get(group);
601    }
602
603    /**
604     * 获取 vendor flags
605     *
606     * @return vendor flags
607     */
608    public VendorFlags getFlags() {
609        return flags;
610    }
611
612    /**
613     * 获取数据库厂商
614     *
615     * @return 厂商
616     */
617    public EDbVendor getVendor() {
618        return vendor;
619    }
620
621    /**
622     * 按对象组获取规则(内部使用)
623     *
624     * @param group 对象组
625     * @return 标识符规则
626     */
627    IdentifierRules getRulesByGroup(ObjectGroup group) {
628        return rulesByGroup.get(group);
629    }
630
631    /**
632     * 将对象类型映射到对象组(公开访问器,供 {@link CanonKey} 的规则域标识使用)
633     *
634     * @param objectType 对象类型
635     * @return 对象组
636     */
637    public ObjectGroup groupOf(ESQLDataObjectType objectType) {
638        return mapToGroup(objectType);
639    }
640
641    /**
642     * 将对象类型映射到对象组
643     */
644    private ObjectGroup mapToGroup(ESQLDataObjectType type) {
645        if (type == null) return ObjectGroup.NAME_GROUP;
646
647        switch (type) {
648            case dotColumn:
649                return ObjectGroup.COLUMN_GROUP;
650
651            case dotFunction:
652                return ObjectGroup.ROUTINE_GROUP;
653
654            // 其他所有类型归入 NAME_GROUP
655            case dotCatalog:
656            case dotSchema:
657            case dotTable:
658            case dotProcedure:
659            case dotTrigger:
660            case dotOraclePackage:
661            default:
662                return ObjectGroup.NAME_GROUP;
663        }
664    }
665
666    // ===== 指纹计算(用于缓存失效) =====
667
668    /**
669     * 计算配置指纹(用于 TObjectName 缓存失效)
670     *
671     * <p>当 vendor 或 flags 变化时,指纹会改变,触发缓存失效
672     *
673     * @return 配置指纹(64位哈希值)
674     */
675    public long getFingerprint() {
676        return fingerprint;
677    }
678
679    // ===== toString 方法(用于调试) =====
680
681    @Override
682    public String toString() {
683        return String.format("IdentifierProfile{vendor=%s, flags=%s}",
684            vendor, flags);
685    }
686}