001package gudusoft.gsqlparser.sqlenv; 002 003import gudusoft.gsqlparser.EDbVendor; 004 005import java.util.Collections; 006import java.util.EnumMap; 007import java.util.EnumSet; 008import java.util.Map; 009import java.util.Objects; 010import java.util.concurrent.ConcurrentHashMap; 011 012/** 013 * 厂商标识符配置档案(Vendor Identifier Profile) 014 * 015 * <p>封装每个数据库厂商的完整标识符配置,包括: 016 * <ul> 017 * <li>按对象组(NAME_GROUP, COLUMN_GROUP, ROUTINE_GROUP)的标识符规则 018 * <li>按对象类别(catalog/project、schema、table、column…)的独立覆盖,见 019 * {@link Builder#withObjectRules(ESQLDataObjectType, IdentifierRules)} 020 * <li>Vendor-specific flags(如 MySQL lower_case_table_names, SQL Server collation) 021 * </li> 022 * 023 * <p>设计目标: 024 * <ul> 025 * <li>集中管理所有 vendor-specific 配置 026 * <li>统一注入路径,避免配置散落在各处 027 * <li>支持缓存失效(通过 fingerprint) 028 * </ul> 029 * 030 * <p>使用示例: 031 * <pre> 032 * // 创建 Oracle profile(使用默认 flags) 033 * IdentifierProfile oracleProfile = IdentifierProfile.forVendor( 034 * EDbVendor.dbvoracle, 035 * VendorFlags.defaults() 036 * ); 037 * 038 * // 创建 MySQL profile(指定 lower_case_table_names) 039 * IdentifierProfile mysqlProfile = IdentifierProfile.forVendor( 040 * EDbVendor.dbvmysql, 041 * new VendorFlags(1, null, false, false) // lower_case_table_names = 1 042 * ); 043 * 044 * // 查询规则 045 * IdentifierRules tableRules = oracleProfile.getRules(ESQLDataObjectType.dotTable); 046 * </pre> 047 * 048 * @since 3.1.0.9 049 */ 050public final class IdentifierProfile { 051 052 private final EDbVendor vendor; 053 054 // ===== 按对象组的规则 ===== 055 private final EnumMap<ObjectGroup, IdentifierRules> rulesByGroup; 056 057 /** 058 * Explicit per-object-category overrides, the finest granularity 059 * ({@link Builder#withObjectRules}). Empty for a plain vendor profile. 060 * 061 * <p>Kept separately from {@link #effectiveRules} so {@link #toBuilder()} can hand the 062 * overrides back as overrides rather than as a new seed — re-seeding with 063 * {@link Builder#withFlags} must not silently freeze an override into a vendor default. 064 */ 065 private final EnumMap<ESQLDataObjectType, IdentifierRules> rulesByType; 066 067 /** 068 * The groups whose rule a caller set ({@link Builder#withRules}) rather than the vendor seed. 069 * A caller's group rule outranks the vendor's own per-category rules 070 * ({@link #defaultTypeRulesFor}), so this has to be remembered to resolve a category. 071 */ 072 private final EnumSet<ObjectGroup> overriddenGroups; 073 074 /** 075 * The resolved rule of EVERY object type — the profile's complete effective strategy. 076 * 077 * <p>Selection order (design §5.1): explicit object-category override > group override 078 * or vendor-seeded group rule. Precomputed for two reasons: {@link #getRules} is on the 079 * per-compare hot path, and equality/fingerprint have to compare the whole strategy, not 080 * the three group slots (two profiles built by different routes — a group override versus 081 * an override on every type of that group — are the same strategy and must compare equal). 082 */ 083 private final EnumMap<ESQLDataObjectType, IdentifierRules> effectiveRules; 084 085 // ===== Vendor-specific flags ===== 086 private final VendorFlags flags; 087 088 // ===== 对象组定义 ===== 089 090 /** 091 * 对象组(用于区分不同对象类型的标识符规则) 092 * 093 * <p>某些数据库对不同对象类型使用不同的大小写规则: 094 * <ul> 095 * <li>BigQuery: 表名敏感,列名不敏感 096 * <li>MySQL: 表名根据 lower_case_table_names,列名始终不敏感 097 * </ul> 098 */ 099 public enum ObjectGroup { 100 /** 101 * 名称组(catalog, schema, table, view, procedure, trigger) 102 */ 103 NAME_GROUP, 104 105 /** 106 * 列组(column) 107 */ 108 COLUMN_GROUP, 109 110 /** 111 * 函数组(function) 112 * <p>某些数据库函数名规则与表名不同 113 */ 114 ROUTINE_GROUP 115 } 116 117 // ===== Vendor flags 封装 ===== 118 119 /** 120 * Vendor-specific flags(厂商特定配置) 121 * 122 * <p>封装各数据库厂商的特殊配置参数 123 */ 124 public static class VendorFlags { 125 /** 126 * MySQL: lower_case_table_names 系统变量(0, 1, 2) 127 * <ul> 128 * <li>0: 大小写敏感(Unix/Linux) 129 * <li>1: 存储为小写,比较不敏感(Windows) 130 * <li>2: 存储保留原样,比较不敏感(macOS) 131 * </ul> 132 */ 133 public final int mysqlLowerCaseTableNames; 134 135 /** 136 * SQL Server / Azure SQL: 默认 collation 名称 137 * <p>例如: "SQL_Latin1_General_CP1_CI_AS" 138 */ 139 public final String defaultCollation; 140 141 /** 142 * Redshift: enable_case_sensitive_identifier 参数 143 * <p>默认 false(与 PostgreSQL 一致) 144 */ 145 public final boolean redshiftEnableCaseSensitive; 146 147 /** 148 * Snowflake: QUOTED_IDENTIFIERS_IGNORE_CASE 会话参数 149 * <p>默认 false(quoted 标识符大小写敏感) 150 */ 151 public final boolean snowflakeQuotedIdentifiersIgnoreCase; 152 153 /** 154 * MySQL 家族: sql_mode 中的 ANSI_QUOTES 选项 155 * 156 * <p>为 true 时,双引号 {@code "} 界定标识符(而非字符串字面量)。 157 * 只影响 {@link IdentifierCodec} 的引号识别/编解码,不影响大小写 158 * 折叠/比较规则(fold/compare 与 ANSI_QUOTES 无关)。 159 * <p>默认 false(MySQL 出厂默认,{@code "} 是字符串定界符)。 160 */ 161 public final boolean mysqlAnsiQuotes; 162 163 /** 164 * 构造 vendor flags(ansiQuotes 取默认值 false) 165 * 166 * @param mysqlLowerCaseTableNames MySQL lower_case_table_names (0, 1, 2) 167 * @param defaultCollation SQL Server collation 名称 168 * @param redshiftEnableCaseSensitive Redshift case sensitive 开关 169 * @param snowflakeQuotedIdentifiersIgnoreCase Snowflake quoted ignore case 开关 170 */ 171 public VendorFlags(int mysqlLowerCaseTableNames, 172 String defaultCollation, 173 boolean redshiftEnableCaseSensitive, 174 boolean snowflakeQuotedIdentifiersIgnoreCase) { 175 this(mysqlLowerCaseTableNames, defaultCollation, redshiftEnableCaseSensitive, 176 snowflakeQuotedIdentifiersIgnoreCase, false); 177 } 178 179 /** 180 * 构造 vendor flags(完整参数) 181 * 182 * @param mysqlLowerCaseTableNames MySQL lower_case_table_names (0, 1, 2) 183 * @param defaultCollation SQL Server collation 名称 184 * @param redshiftEnableCaseSensitive Redshift case sensitive 开关 185 * @param snowflakeQuotedIdentifiersIgnoreCase Snowflake quoted ignore case 开关 186 * @param mysqlAnsiQuotes MySQL 家族 sql_mode ANSI_QUOTES 开关 187 */ 188 public VendorFlags(int mysqlLowerCaseTableNames, 189 String defaultCollation, 190 boolean redshiftEnableCaseSensitive, 191 boolean snowflakeQuotedIdentifiersIgnoreCase, 192 boolean mysqlAnsiQuotes) { 193 this.mysqlLowerCaseTableNames = mysqlLowerCaseTableNames; 194 this.defaultCollation = defaultCollation; 195 this.redshiftEnableCaseSensitive = redshiftEnableCaseSensitive; 196 this.snowflakeQuotedIdentifiersIgnoreCase = snowflakeQuotedIdentifiersIgnoreCase; 197 this.mysqlAnsiQuotes = mysqlAnsiQuotes; 198 } 199 200 /** 201 * 默认 flags(用于大部分数据库) 202 */ 203 public static VendorFlags defaults() { 204 return new VendorFlags( 205 // MySQL lower_case_table_names = 1 (P0d.2): the unconfigured default keeps 206 // the legacy case-INSENSITIVE table equality, and mode 1's LOWER fold keeps 207 // keyForMap bucket keys coherent with that relation (mode 2 would bucket 208 // case-variants apart while areEqual equates them). Mode 0 (Linux 209 // case-sensitive) remains available through explicit configuration. 210 1, 211 "SQL_Latin1_General_CP1_CI_AS", // SQL Server 默认 collation 212 false, // Redshift case sensitive = false 213 false // Snowflake quoted ignore case = false 214 ); 215 } 216 217 @Override 218 public boolean equals(Object o) { 219 if (this == o) return true; 220 if (o == null || getClass() != o.getClass()) return false; 221 VendorFlags that = (VendorFlags) o; 222 return mysqlLowerCaseTableNames == that.mysqlLowerCaseTableNames && 223 redshiftEnableCaseSensitive == that.redshiftEnableCaseSensitive && 224 snowflakeQuotedIdentifiersIgnoreCase == that.snowflakeQuotedIdentifiersIgnoreCase && 225 mysqlAnsiQuotes == that.mysqlAnsiQuotes && 226 Objects.equals(defaultCollation, that.defaultCollation); 227 } 228 229 @Override 230 public int hashCode() { 231 return Objects.hash(mysqlLowerCaseTableNames, defaultCollation, 232 redshiftEnableCaseSensitive, snowflakeQuotedIdentifiersIgnoreCase, 233 mysqlAnsiQuotes); 234 } 235 } 236 237 // ===== 构造函数 ===== 238 239 /** 240 * 构造标识符配置档案 241 * 242 * @param vendor 数据库厂商 243 * @param rulesByGroup 按对象组的规则 244 * @param flags vendor-specific flags 245 */ 246 private IdentifierProfile(EDbVendor vendor, 247 EnumMap<ObjectGroup, IdentifierRules> rulesByGroup, 248 EnumSet<ObjectGroup> overriddenGroups, 249 EnumMap<ESQLDataObjectType, IdentifierRules> rulesByType, 250 VendorFlags flags) { 251 this.vendor = Objects.requireNonNull(vendor, "vendor"); 252 this.rulesByGroup = new EnumMap<>(Objects.requireNonNull(rulesByGroup, "rulesByGroup")); 253 this.overriddenGroups = overriddenGroups == null || overriddenGroups.isEmpty() 254 ? EnumSet.noneOf(ObjectGroup.class) 255 : EnumSet.copyOf(overriddenGroups); 256 this.rulesByType = rulesByType == null || rulesByType.isEmpty() 257 ? new EnumMap<ESQLDataObjectType, IdentifierRules>(ESQLDataObjectType.class) 258 : new EnumMap<>(rulesByType); 259 this.flags = Objects.requireNonNull(flags, "flags"); 260 261 // Resolve every object type ONCE: explicit category override > a caller's group override 262 // > the vendor's own rule for the category (defaultTypeRulesFor; MySQL indexes and 263 // parameters) > the group's vendor-seeded rule. A type no rule singles out inherits its 264 // group's rule, so a default profile keeps exactly the rules it had before per-category 265 // configuration existed. 266 EnumMap<ESQLDataObjectType, IdentifierRules> vendorTypeRules = defaultTypeRulesFor(vendor, flags); 267 this.effectiveRules = new EnumMap<>(ESQLDataObjectType.class); 268 for (ESQLDataObjectType type : ESQLDataObjectType.values()) { 269 IdentifierRules override = this.rulesByType.get(type); 270 if (override == null && !this.overriddenGroups.contains(mapToGroup(type))) { 271 override = vendorTypeRules.get(type); 272 } 273 if (override == null) { 274 override = this.rulesByGroup.get(mapToGroup(type)); 275 } 276 if (override == null) { 277 // A group slot is missing: the seed tables fill all three, so this can only be a 278 // programming error inside this class. Fail loudly instead of comparing under null. 279 throw new IllegalStateException("No identifier rules for " + type 280 + " (group " + mapToGroup(type) + ") on vendor " + vendor); 281 } 282 this.effectiveRules.put(type, override); 283 } 284 285 // Immutable → compute once; getFingerprint sits on the per-compare hot path 286 // since areEqual/canonKey stamp keys with it (P0d). Since per-category configuration 287 // exists the fingerprint must cover the WHOLE effective strategy: hashing only the three 288 // group slots would let a table-only override share a fingerprint with the vendor default 289 // and make two different policies look like one rule domain. 290 this.fingerprint = Objects.hash(vendor, flags, this.effectiveRules); 291 } 292 293 private final long fingerprint; 294 295 // ===== 工厂方法:根据厂商和 flags 创建 ===== 296 297 /** 298 * 为指定厂商创建标识符配置档案 299 * 300 * @param vendor 数据库厂商 301 * @param flags vendor-specific flags 302 * @return 标识符配置档案 303 */ 304 public static IdentifierProfile forVendor(EDbVendor vendor, VendorFlags flags) { 305 Objects.requireNonNull(vendor, "vendor"); 306 Objects.requireNonNull(flags, "flags"); 307 if (DEFAULT_FLAGS.equals(flags)) { 308 // The overwhelmingly common call. Profiles are immutable, so one shared instance per 309 // vendor is safe, and it keeps the per-type resolution below off hot paths that build 310 // a profile per AST node (TObjectName.getIdentifierService). 311 IdentifierProfile cached = DEFAULT_FLAG_PROFILES.get(vendor); 312 if (cached == null) { 313 cached = new IdentifierProfile(vendor, defaultRulesFor(vendor, flags), null, null, flags); 314 IdentifierProfile raced = DEFAULT_FLAG_PROFILES.putIfAbsent(vendor, cached); 315 if (raced != null) { 316 cached = raced; 317 } 318 } 319 return cached; 320 } 321 return new IdentifierProfile(vendor, defaultRulesFor(vendor, flags), null, null, flags); 322 } 323 324 /** 325 * {@code configured} when it is a configuration for {@code vendor}, else that vendor's 326 * default profile. 327 * 328 * <p>For the places that build an object of a dialect an analysis may or may not be configured 329 * for — a metadata file that declares its own {@code dbVendor}, nested SQL in another dialect. 330 * A profile belongs to exactly one vendor, so it is never transplanted onto another: rules 331 * chosen for BigQuery tables say nothing about Oracle's. 332 * 333 * @param configured the analysis's configuration, or null when there is none 334 * @param vendor the vendor the caller actually needs a profile for 335 * @return a profile for {@code vendor}; never null 336 */ 337 public static IdentifierProfile forVendorOr(IdentifierProfile configured, EDbVendor vendor) { 338 if (configured != null && configured.getVendor() == vendor) { 339 return configured; 340 } 341 return forVendor(vendor, VendorFlags.defaults()); 342 } 343 344 private static final VendorFlags DEFAULT_FLAGS = VendorFlags.defaults(); 345 346 private static final ConcurrentHashMap<EDbVendor, IdentifierProfile> DEFAULT_FLAG_PROFILES = 347 new ConcurrentHashMap<EDbVendor, IdentifierProfile>(); 348 349 // ===== Builder entry points ===== 350 351 /** 352 * Create a builder seeded from default rules for the given vendor 353 * with default VendorFlags. 354 * 355 * @param vendor database vendor 356 * @return a new Builder 357 */ 358 public static Builder builder(EDbVendor vendor) { 359 return builder(vendor, VendorFlags.defaults()); 360 } 361 362 /** 363 * Create a builder seeded from default rules for the given vendor 364 * and the specified VendorFlags. 365 * 366 * @param vendor database vendor 367 * @param flags vendor-specific flags 368 * @return a new Builder 369 */ 370 public static Builder builder(EDbVendor vendor, VendorFlags flags) { 371 Objects.requireNonNull(vendor, "vendor"); 372 Objects.requireNonNull(flags, "flags"); 373 return new Builder(vendor, flags, defaultRulesFor(vendor, flags)); 374 } 375 376 /** 377 * Create a builder pre-populated from this profile's current state. 378 * Enables deriving a variant without re-specifying everything. 379 * 380 * @return a new Builder seeded from this profile 381 */ 382 public Builder toBuilder() { 383 Builder builder = new Builder(this.vendor, this.flags, new EnumMap<>(this.rulesByGroup)); 384 builder.typeOverrides.putAll(this.rulesByType); 385 // The seed now holds this profile's group overrides; remember which they are, so they keep 386 // outranking the vendor's per-category rules in the derived profile too. 387 builder.seededGroupOverrides.addAll(this.overriddenGroups); 388 return builder; 389 } 390 391 // ===== Internal: single source of truth for default rules ===== 392 393 /** 394 * Compute the default per-group rules for the given vendor and flags. 395 * This is the sole source of truth for default rules -- both forVendor() 396 * and the Builder use this method. 397 * 398 * @param vendor database vendor 399 * @param flags vendor-specific flags 400 * @return mutable EnumMap with rules for all three groups 401 */ 402 static EnumMap<ObjectGroup, IdentifierRules> defaultRulesFor(EDbVendor vendor, VendorFlags flags) { 403 EnumMap<ObjectGroup, IdentifierRules> rules = new EnumMap<>(ObjectGroup.class); 404 405 switch (vendor) { 406 case dbvoracle: { 407 // Verified on Oracle 23.26 (2026-10-11). The Oracle-shaped forks below keep 408 // forOracle() until their own engines are verified. 409 IdentifierRules rule = IdentifierRules.forOracleNative(); 410 rules.put(ObjectGroup.NAME_GROUP, rule); 411 rules.put(ObjectGroup.COLUMN_GROUP, rule); 412 rules.put(ObjectGroup.ROUTINE_GROUP, rule); 413 break; 414 } 415 416 case dbvdameng: { 417 IdentifierRules rule = IdentifierRules.forDameng(); 418 rules.put(ObjectGroup.NAME_GROUP, rule); 419 rules.put(ObjectGroup.COLUMN_GROUP, rule); 420 rules.put(ObjectGroup.ROUTINE_GROUP, rule); 421 break; 422 } 423 424 case dbvpostgresql: { 425 // Verified on PostgreSQL 16.15, UTF8 (2026-10-11). 426 IdentifierRules rule = IdentifierRules.forPostgreSQLNative(); 427 rules.put(ObjectGroup.NAME_GROUP, rule); 428 rules.put(ObjectGroup.COLUMN_GROUP, rule); 429 rules.put(ObjectGroup.ROUTINE_GROUP, rule); 430 break; 431 } 432 433 case dbvgreenplum: 434 case dbvgaussdb: 435 case dbvedb: { 436 // PostgreSQL forks: not verified on their own engines, so they keep the shape they 437 // shared with PostgreSQL before 2026-10-11. 438 IdentifierRules rule = IdentifierRules.forPostgreSQL(); 439 rules.put(ObjectGroup.NAME_GROUP, rule); 440 rules.put(ObjectGroup.COLUMN_GROUP, rule); 441 rules.put(ObjectGroup.ROUTINE_GROUP, rule); 442 break; 443 } 444 445 case dbvduckdb: { 446 IdentifierRules rule = IdentifierRules.forDuckDB(); 447 rules.put(ObjectGroup.NAME_GROUP, rule); 448 rules.put(ObjectGroup.COLUMN_GROUP, rule); 449 rules.put(ObjectGroup.ROUTINE_GROUP, rule); 450 break; 451 } 452 453 case dbvsqlite: { 454 IdentifierRules rule = IdentifierRules.forSqlite(); 455 rules.put(ObjectGroup.NAME_GROUP, rule); 456 rules.put(ObjectGroup.COLUMN_GROUP, rule); 457 rules.put(ObjectGroup.ROUTINE_GROUP, rule); 458 break; 459 } 460 461 case dbvfirebird: { 462 IdentifierRules rule = IdentifierRules.forFirebird(); 463 rules.put(ObjectGroup.NAME_GROUP, rule); 464 rules.put(ObjectGroup.COLUMN_GROUP, rule); 465 rules.put(ObjectGroup.ROUTINE_GROUP, rule); 466 break; 467 } 468 469 case dbvsybase: 470 case dbvsybasease: { 471 IdentifierRules rule = IdentifierRules.forSybase(); 472 rules.put(ObjectGroup.NAME_GROUP, rule); 473 rules.put(ObjectGroup.COLUMN_GROUP, rule); 474 rules.put(ObjectGroup.ROUTINE_GROUP, rule); 475 break; 476 } 477 478 case dbvsybaseiq: 479 case dbvsqlanywhere: { 480 IdentifierRules rule = IdentifierRules.forSqlAnywhere(); 481 rules.put(ObjectGroup.NAME_GROUP, rule); 482 rules.put(ObjectGroup.COLUMN_GROUP, rule); 483 rules.put(ObjectGroup.ROUTINE_GROUP, rule); 484 break; 485 } 486 487 case dbvaccess: { 488 IdentifierRules rule = IdentifierRules.forAccess(); 489 rules.put(ObjectGroup.NAME_GROUP, rule); 490 rules.put(ObjectGroup.COLUMN_GROUP, rule); 491 rules.put(ObjectGroup.ROUTINE_GROUP, rule); 492 break; 493 } 494 495 case dbvdax: { 496 IdentifierRules rule = IdentifierRules.forDax(); 497 rules.put(ObjectGroup.NAME_GROUP, rule); 498 rules.put(ObjectGroup.COLUMN_GROUP, rule); 499 rules.put(ObjectGroup.ROUTINE_GROUP, rule); 500 break; 501 } 502 503 case dbvmdx: { 504 IdentifierRules rule = IdentifierRules.forMdx(); 505 rules.put(ObjectGroup.NAME_GROUP, rule); 506 rules.put(ObjectGroup.COLUMN_GROUP, rule); 507 rules.put(ObjectGroup.ROUTINE_GROUP, rule); 508 break; 509 } 510 511 case dbvpowerquery: { 512 IdentifierRules rule = IdentifierRules.forPowerQuery(); 513 rules.put(ObjectGroup.NAME_GROUP, rule); 514 rules.put(ObjectGroup.COLUMN_GROUP, rule); 515 rules.put(ObjectGroup.ROUTINE_GROUP, rule); 516 break; 517 } 518 519 case dbvansi: 520 case dbvgeneric: 521 case dbvodbc: { 522 IdentifierRules rule = IdentifierRules.forAnsi(); 523 rules.put(ObjectGroup.NAME_GROUP, rule); 524 rules.put(ObjectGroup.COLUMN_GROUP, rule); 525 rules.put(ObjectGroup.ROUTINE_GROUP, rule); 526 break; 527 } 528 529 case dbvredshift: { 530 IdentifierRules rule = flags.redshiftEnableCaseSensitive 531 ? IdentifierRules.forCouchbase() 532 : IdentifierRules.forRedshift(); 533 rules.put(ObjectGroup.NAME_GROUP, rule); 534 rules.put(ObjectGroup.COLUMN_GROUP, rule); 535 rules.put(ObjectGroup.ROUTINE_GROUP, rule); 536 break; 537 } 538 539 case dbvclickhouse: { 540 IdentifierRules rule = IdentifierRules.forCouchbase(); 541 rules.put(ObjectGroup.NAME_GROUP, rule); 542 rules.put(ObjectGroup.COLUMN_GROUP, rule); 543 rules.put(ObjectGroup.ROUTINE_GROUP, rule); 544 break; 545 } 546 547 case dbvcouchbase: { 548 IdentifierRules rule = IdentifierRules.forCouchbase(); 549 rules.put(ObjectGroup.NAME_GROUP, rule); 550 rules.put(ObjectGroup.COLUMN_GROUP, rule); 551 rules.put(ObjectGroup.ROUTINE_GROUP, rule); 552 break; 553 } 554 555 case dbvmssql: 556 case dbvazuresql: { 557 IdentifierRules rule = IdentifierRules.forSQLServer(flags.defaultCollation); 558 rules.put(ObjectGroup.NAME_GROUP, rule); 559 rules.put(ObjectGroup.COLUMN_GROUP, rule); 560 rules.put(ObjectGroup.ROUTINE_GROUP, rule); 561 break; 562 } 563 564 case dbvmysql: { 565 // Verified on MySQL 8.4.10 (2026-10-11); indexes and parameters get their own 566 // rules in defaultTypeRulesFor. OceanBase, Doris and StarRocks keep the old cells. 567 rules.put(ObjectGroup.NAME_GROUP, 568 IdentifierRules.forMySQLNative(flags.mysqlLowerCaseTableNames)); 569 rules.put(ObjectGroup.COLUMN_GROUP, IdentifierRules.forMySQLNativeColumn()); 570 rules.put(ObjectGroup.ROUTINE_GROUP, IdentifierRules.forMySQLNativeRoutine()); 571 break; 572 } 573 574 case dbvoceanbase: { 575 // Phase 1: identifier rules mirror MySQL because the default 576 // EOBTenantMode is MYSQL and the IdentifierProfile is built 577 // before the user has had a chance to call setOBTenantMode. 578 // TODO(oceanbase Phase 3): split this into MYSQL/ORACLE 579 // branches once the EOBTenantMode is queryable from 580 // IdentifierProfileFlags or wherever the profile is built. 581 rules.put(ObjectGroup.NAME_GROUP, 582 IdentifierRules.forMySQL(flags.mysqlLowerCaseTableNames)); 583 rules.put(ObjectGroup.COLUMN_GROUP, IdentifierRules.forMySQLColumn()); 584 rules.put(ObjectGroup.ROUTINE_GROUP, IdentifierRules.forMySQLRoutine()); 585 break; 586 } 587 588 case dbvdoris: { 589 rules.put(ObjectGroup.NAME_GROUP, IdentifierRules.forDoris()); 590 rules.put(ObjectGroup.COLUMN_GROUP, IdentifierRules.forDorisColumn()); 591 rules.put(ObjectGroup.ROUTINE_GROUP, IdentifierRules.forMySQLRoutine()); 592 break; 593 } 594 595 case dbvstarrocks: { 596 rules.put(ObjectGroup.NAME_GROUP, IdentifierRules.forStarrocks()); 597 rules.put(ObjectGroup.COLUMN_GROUP, IdentifierRules.forStarrocksColumn()); 598 rules.put(ObjectGroup.ROUTINE_GROUP, IdentifierRules.forMySQLRoutine()); 599 break; 600 } 601 602 case dbvbigquery: { 603 rules.put(ObjectGroup.NAME_GROUP, IdentifierRules.forBigQueryTable()); 604 rules.put(ObjectGroup.COLUMN_GROUP, IdentifierRules.forBigQueryColumn()); 605 rules.put(ObjectGroup.ROUTINE_GROUP, IdentifierRules.forBigQueryTable()); 606 break; 607 } 608 609 case dbvdb2: 610 case dbvnetezza: 611 case dbvexasol: { 612 IdentifierRules rule = IdentifierRules.forDB2(); 613 rules.put(ObjectGroup.NAME_GROUP, rule); 614 rules.put(ObjectGroup.COLUMN_GROUP, rule); 615 rules.put(ObjectGroup.ROUTINE_GROUP, rule); 616 break; 617 } 618 619 case dbvsnowflake: { 620 IdentifierRules rule = IdentifierRules.forSnowflake( 621 flags.snowflakeQuotedIdentifiersIgnoreCase); 622 rules.put(ObjectGroup.NAME_GROUP, rule); 623 rules.put(ObjectGroup.COLUMN_GROUP, rule); 624 rules.put(ObjectGroup.ROUTINE_GROUP, rule); 625 break; 626 } 627 628 case dbvhana: { 629 IdentifierRules rule = IdentifierRules.forHANA(); 630 rules.put(ObjectGroup.NAME_GROUP, rule); 631 rules.put(ObjectGroup.COLUMN_GROUP, rule); 632 rules.put(ObjectGroup.ROUTINE_GROUP, rule); 633 break; 634 } 635 636 case dbvpresto: 637 case dbvtrino: { 638 IdentifierRules rule = IdentifierRules.forPresto(); 639 rules.put(ObjectGroup.NAME_GROUP, rule); 640 rules.put(ObjectGroup.COLUMN_GROUP, rule); 641 rules.put(ObjectGroup.ROUTINE_GROUP, rule); 642 break; 643 } 644 645 case dbvvertica: { 646 IdentifierRules rule = IdentifierRules.forVertica(); 647 rules.put(ObjectGroup.NAME_GROUP, rule); 648 rules.put(ObjectGroup.COLUMN_GROUP, rule); 649 rules.put(ObjectGroup.ROUTINE_GROUP, rule); 650 break; 651 } 652 653 case dbvhive: 654 case dbvsparksql: 655 case dbvimpala: 656 case dbvdatabricks: { 657 IdentifierRules rule = IdentifierRules.forHive(); 658 rules.put(ObjectGroup.NAME_GROUP, rule); 659 rules.put(ObjectGroup.COLUMN_GROUP, rule); 660 rules.put(ObjectGroup.ROUTINE_GROUP, rule); 661 break; 662 } 663 664 case dbvflink: { 665 IdentifierRules rule = IdentifierRules.forFlink(); 666 rules.put(ObjectGroup.NAME_GROUP, rule); 667 rules.put(ObjectGroup.COLUMN_GROUP, rule); 668 rules.put(ObjectGroup.ROUTINE_GROUP, rule); 669 break; 670 } 671 672 case dbvteradata: { 673 IdentifierRules rule = IdentifierRules.forTeradata(); 674 rules.put(ObjectGroup.NAME_GROUP, rule); 675 rules.put(ObjectGroup.COLUMN_GROUP, rule); 676 rules.put(ObjectGroup.ROUTINE_GROUP, rule); 677 break; 678 } 679 680 case dbvathena: { 681 IdentifierRules rule = IdentifierRules.forAthena(); 682 rules.put(ObjectGroup.NAME_GROUP, rule); 683 rules.put(ObjectGroup.COLUMN_GROUP, rule); 684 rules.put(ObjectGroup.ROUTINE_GROUP, rule); 685 break; 686 } 687 688 default: { 689 // Vendors without a documented rule of their own here (Informix, OpenEdge, SOQL) 690 // keep the PostgreSQL shape they have always had. 691 IdentifierRules rule = IdentifierRules.forPostgreSQL(); 692 rules.put(ObjectGroup.NAME_GROUP, rule); 693 rules.put(ObjectGroup.COLUMN_GROUP, rule); 694 rules.put(ObjectGroup.ROUTINE_GROUP, rule); 695 break; 696 } 697 } 698 699 return rules; 700 } 701 702 /** 703 * A vendor's own rule for single object categories that do not follow their group, under the 704 * given flags. Empty for every vendor but MySQL. 705 * 706 * <p>MySQL (verified on 8.4.10): index names do not follow {@code lower_case_table_names} — 707 * they compare like column names (under mode 0, {@code INDEX Idx} then {@code ADD INDEX idx} 708 * fails with ERROR 1061) — and routine parameters compare like routine names 709 * ({@code CREATE PROCEDURE pr(P int, p int)} fails with ERROR 1330). Both categories stay in 710 * NAME_GROUP (moving them would regroup them for every vendor); only their rule differs. 711 * 712 * <p>Precedence: an explicit category override and a caller's group override both win over 713 * these; they win over the group's vendor-seeded rule. 714 */ 715 static EnumMap<ESQLDataObjectType, IdentifierRules> defaultTypeRulesFor(EDbVendor vendor, 716 VendorFlags flags) { 717 EnumMap<ESQLDataObjectType, IdentifierRules> rules = new EnumMap<>(ESQLDataObjectType.class); 718 if (vendor == EDbVendor.dbvmysql) { 719 rules.put(ESQLDataObjectType.dotIndex, IdentifierRules.forMySQLNativeColumn()); 720 rules.put(ESQLDataObjectType.dotParameter, IdentifierRules.forMySQLNativeRoutine()); 721 } 722 return rules; 723 } 724 725 // ===== Builder inner class ===== 726 727 /** 728 * Builder for creating customized IdentifierProfile instances. 729 * 730 * <p>The builder is seeded from vendor defaults. Overrides can be applied 731 * per object group. The built profile is immutable. 732 * 733 * <p>Usage example: 734 * <pre> 735 * IdentifierProfile profile = IdentifierProfile 736 * .builder(EDbVendor.dbvmssql) 737 * .withColumnRules(customColumnRules) 738 * .build(); 739 * </pre> 740 */ 741 public static final class Builder { 742 743 private EDbVendor vendor; 744 private VendorFlags flags; 745 private EnumMap<ObjectGroup, IdentifierRules> seededRules; 746 private final EnumMap<ObjectGroup, IdentifierRules> overrides; 747 private final EnumMap<ESQLDataObjectType, IdentifierRules> typeOverrides; 748 /** Groups whose SEED came from a profile's group override ({@link IdentifierProfile#toBuilder}). */ 749 private final EnumSet<ObjectGroup> seededGroupOverrides = EnumSet.noneOf(ObjectGroup.class); 750 751 Builder(EDbVendor vendor, VendorFlags flags, 752 EnumMap<ObjectGroup, IdentifierRules> seedRules) { 753 this.vendor = vendor; 754 this.flags = flags; 755 this.seededRules = new EnumMap<>(seedRules); 756 this.overrides = new EnumMap<>(ObjectGroup.class); 757 this.typeOverrides = new EnumMap<>(ESQLDataObjectType.class); 758 } 759 760 /** 761 * Override VendorFlags (replaces default-seeded flags). 762 * Re-seeds rules that depend on flags while preserving 763 * explicit overrides set via withRules(). 764 * 765 * @param flags new vendor flags 766 * @return this builder 767 */ 768 public Builder withFlags(VendorFlags flags) { 769 this.flags = Objects.requireNonNull(flags, "flags"); 770 this.seededRules = defaultRulesFor(this.vendor, flags); 771 // The seed is the vendor's again: nothing in it is a caller's override any more. 772 this.seededGroupOverrides.clear(); 773 return this; 774 } 775 776 /** 777 * Override the rules for a specific object group. 778 * 779 * @param group the object group to override 780 * @param rules the custom rules 781 * @return this builder 782 */ 783 public Builder withRules(ObjectGroup group, IdentifierRules rules) { 784 Objects.requireNonNull(group, "group"); 785 Objects.requireNonNull(rules, "rules"); 786 overrides.put(group, rules); 787 return this; 788 } 789 790 /** 791 * Convenience: override NAME_GROUP rules. 792 * 793 * @param rules the custom rules for names 794 * @return this builder 795 */ 796 public Builder withNameRules(IdentifierRules rules) { 797 return withRules(ObjectGroup.NAME_GROUP, rules); 798 } 799 800 /** 801 * Convenience: override COLUMN_GROUP rules. 802 * 803 * @param rules the custom rules for columns 804 * @return this builder 805 */ 806 public Builder withColumnRules(IdentifierRules rules) { 807 return withRules(ObjectGroup.COLUMN_GROUP, rules); 808 } 809 810 /** 811 * Convenience: override ROUTINE_GROUP rules. 812 * 813 * @param rules the custom rules for routines 814 * @return this builder 815 */ 816 public Builder withRoutineRules(IdentifierRules rules) { 817 return withRules(ObjectGroup.ROUTINE_GROUP, rules); 818 } 819 820 /** 821 * Override the rules of ONE object category — catalog/project, schema, table, column, 822 * function … — independently of every other category (design §5.1). 823 * 824 * <p>This is the finest granularity and wins over a {@link #withRules(ObjectGroup, 825 * IdentifierRules) group} override and over the vendor default. It is deliberately a new 826 * name rather than an overload of {@code withRules}: {@code withRules(null, rules)} would 827 * become ambiguous, and an object CATEGORY is not an object group. 828 * 829 * <p>The argument is a category, never the name of a particular dataset, table or column — 830 * a profile expresses "how table names compare here", not "this table compares like that". 831 * 832 * <p>Repeating a category keeps the LAST value. To go back to inheriting, call 833 * {@link #withoutObjectRules(ESQLDataObjectType)}; {@code null} rules are rejected rather 834 * than treated as some implicit rule. 835 * 836 * <p>Note what a category override does and does not claim: it is the analysis assumption 837 * the CALLER supplies. It is not read from a live server, and an arbitrary override is not 838 * automatically a vendor's native semantics. 839 * 840 * @param objectType the object category to configure 841 * @param rules the complete rule cell (unquoted fold/compare, quoted fold/compare) 842 * @return this builder 843 */ 844 public Builder withObjectRules(ESQLDataObjectType objectType, IdentifierRules rules) { 845 Objects.requireNonNull(objectType, "objectType"); 846 Objects.requireNonNull(rules, "rules"); 847 typeOverrides.put(objectType, rules); 848 return this; 849 } 850 851 /** 852 * Drop a category override set earlier, so the category inherits its group / vendor rule 853 * again. A category that was never overridden is unaffected. 854 * 855 * @param objectType the object category to stop overriding 856 * @return this builder 857 */ 858 public Builder withoutObjectRules(ESQLDataObjectType objectType) { 859 Objects.requireNonNull(objectType, "objectType"); 860 typeOverrides.remove(objectType); 861 return this; 862 } 863 864 /** 865 * Drop a group override set earlier, so the group falls back to the vendor-seeded rule. 866 * Category overrides are untouched — they sit above the group level. 867 * 868 * @param group the object group to stop overriding 869 * @return this builder 870 */ 871 public Builder withoutRules(ObjectGroup group) { 872 Objects.requireNonNull(group, "group"); 873 overrides.remove(group); 874 return this; 875 } 876 877 /** 878 * Build an immutable IdentifierProfile. 879 * Merges seeded rules with overrides (overrides win), then applies per-category overrides. 880 * 881 * @return the built profile 882 */ 883 public IdentifierProfile build() { 884 EnumMap<ObjectGroup, IdentifierRules> merged = new EnumMap<>(seededRules); 885 merged.putAll(overrides); 886 EnumSet<ObjectGroup> overridden = EnumSet.copyOf(seededGroupOverrides); 887 overridden.addAll(overrides.keySet()); 888 return new IdentifierProfile(vendor, merged, overridden, typeOverrides, flags); 889 } 890 } 891 892 // ===== 查询接口 ===== 893 894 /** 895 * 获取指定对象类型的标识符规则 896 * 897 * @param objectType 对象类型 898 * @return 标识符规则 899 */ 900 public IdentifierRules getRules(ESQLDataObjectType objectType) { 901 if (objectType == null) { 902 // Historic behavior of mapToGroup(null): an unstated category is a plain name. 903 return rulesByGroup.get(ObjectGroup.NAME_GROUP); 904 } 905 return effectiveRules.get(objectType); 906 } 907 908 /** 909 * The effective rule cell of one object category with {@code SAME_AS_UNQUOTED} resolved — 910 * the <em>rule domain</em> a {@link CanonKey} of that category belongs to. 911 * 912 * <p>Since categories inside one {@link ObjectGroup} may now carry different rules, the 913 * group alone no longer identifies the domain a canonical payload was produced in: with 914 * tables insensitive and schemas sensitive, a table keyed as {@code orders} and a schema 915 * literally named {@code orders} would otherwise produce equal keys. Two categories with the 916 * same effective cell share a domain, which is what keeps a default profile's 917 * table/schema/catalog keys interchangeable exactly as before. 918 * 919 * @param objectType the object category 920 * @return the resolved rule cell; never null 921 */ 922 public IdentifierRules ruleDomainOf(ESQLDataObjectType objectType) { 923 return getRules(objectType).resolvedCell(); 924 } 925 926 /** 927 * Whether this profile and {@code other} are the SAME effective strategy: same vendor, same 928 * flags, and the same resolved rule for every object category — regardless of how each was 929 * built (vendor default, group override, category override) or of object identity. 930 * 931 * <p>The {@link #getFingerprint() fingerprint} is a hash: it can accelerate this answer but 932 * must never stand in for it, since distinct strategies can collide in 32 bits. 933 * 934 * @param other the profile to compare with; null is never the same strategy 935 * @return true if the two profiles decide every identifier question identically 936 */ 937 public boolean hasSameEffectivePolicyAs(IdentifierProfile other) { 938 if (this == other) { 939 return true; 940 } 941 if (other == null) { 942 return false; 943 } 944 return vendor == other.vendor 945 && flags.equals(other.flags) 946 && effectiveRules.equals(other.effectiveRules); 947 } 948 949 /** 950 * Value equality over the complete effective strategy — see 951 * {@link #hasSameEffectivePolicyAs}. Two profiles that decide every identifier question the 952 * same way ARE the same profile, so an equivalent instance passed at a second entry point 953 * does not read as a conflicting configuration. 954 */ 955 @Override 956 public boolean equals(Object o) { 957 if (this == o) { 958 return true; 959 } 960 if (!(o instanceof IdentifierProfile)) { 961 return false; 962 } 963 return hasSameEffectivePolicyAs((IdentifierProfile) o); 964 } 965 966 @Override 967 public int hashCode() { 968 return (int) fingerprint; 969 } 970 971 /** 972 * The categories this profile configures explicitly, finest granularity first. Diagnostics 973 * only — the strategy itself is {@link #getRules}. 974 * 975 * @return an unmodifiable view of the per-category overrides 976 */ 977 public Map<ESQLDataObjectType, IdentifierRules> getObjectRuleOverrides() { 978 return Collections.unmodifiableMap(rulesByType); 979 } 980 981 /** 982 * 获取 vendor flags 983 * 984 * @return vendor flags 985 */ 986 public VendorFlags getFlags() { 987 return flags; 988 } 989 990 /** 991 * 获取数据库厂商 992 * 993 * @return 厂商 994 */ 995 public EDbVendor getVendor() { 996 return vendor; 997 } 998 999 /** 1000 * 按对象组获取规则(内部使用) 1001 * 1002 * <p>This is the GROUP-level rule — the value a category inherits when it carries no 1003 * {@link Builder#withObjectRules} override. It is NOT what governs a comparison; only 1004 * {@link #getRules(ESQLDataObjectType)} is. Nothing that decides equality may read this. 1005 * 1006 * @param group 对象组 1007 * @return 标识符规则 1008 */ 1009 IdentifierRules getRulesByGroup(ObjectGroup group) { 1010 return rulesByGroup.get(group); 1011 } 1012 1013 /** 1014 * 将对象类型映射到对象组(公开访问器,供 {@link CanonKey} 的规则域标识使用) 1015 * 1016 * @param objectType 对象类型 1017 * @return 对象组 1018 */ 1019 public ObjectGroup groupOf(ESQLDataObjectType objectType) { 1020 return mapToGroup(objectType); 1021 } 1022 1023 /** 1024 * 将对象类型映射到对象组 1025 */ 1026 private ObjectGroup mapToGroup(ESQLDataObjectType type) { 1027 if (type == null) return ObjectGroup.NAME_GROUP; 1028 1029 // Fail-closed and exhaustive on purpose (stage S0R). A `default: NAME_GROUP` looked 1030 // harmless but meant a NEW object type silently acquired the name rules — and a test 1031 // asserting "the group is non-null" could not tell the difference. Every value is listed; 1032 // a new one fails here until someone assigns it deliberately. 1033 switch (type) { 1034 case dotColumn: 1035 return ObjectGroup.COLUMN_GROUP; 1036 1037 // All four routine-like types share the routine comparison group. Only dotFunction 1038 // used to, which was a defect rather than a namespace statement: 1039 // CatalogModelValidator works around it by comparing every routine as dotFunction, 1040 // and its test proves why — MySQL with lower_case_table_names=0 still treats `Proc` 1041 // and `proc` as one procedure name. The four ESQLDataObjectType values stay distinct 1042 // because quoting can differ per type (DAX), but their CASE rules are the same. 1043 case dotRoutine: 1044 case dotFunction: 1045 case dotProcedure: 1046 case dotOraclePackage: 1047 return ObjectGroup.ROUTINE_GROUP; 1048 1049 case dotUnknown: 1050 case dotCatalog: 1051 case dotSchema: 1052 case dotTable: 1053 case dotTrigger: 1054 case dotSynonyms: 1055 case dotDblink: 1056 case dotDataType: 1057 case dotParameter: 1058 case dotServer: 1059 case dotSequence: 1060 case dotIndex: 1061 case dotConstraint: 1062 return ObjectGroup.NAME_GROUP; 1063 1064 default: 1065 throw new IllegalStateException("No object group for " + type 1066 + " - add it explicitly to IdentifierProfile.mapToGroup"); 1067 } 1068 } 1069 1070 // ===== 指纹计算(用于缓存失效) ===== 1071 1072 /** 1073 * 计算配置指纹(用于 TObjectName 缓存失效) 1074 * 1075 * <p>当 vendor 或 flags 变化时,指纹会改变,触发缓存失效 1076 * 1077 * @return 配置指纹(64位哈希值) 1078 */ 1079 public long getFingerprint() { 1080 return fingerprint; 1081 } 1082 1083 // ===== toString 方法(用于调试) ===== 1084 1085 @Override 1086 public String toString() { 1087 if (rulesByType.isEmpty()) { 1088 return String.format("IdentifierProfile{vendor=%s, flags=%s}", vendor, flags); 1089 } 1090 return String.format("IdentifierProfile{vendor=%s, flags=%s, objectRules=%s}", 1091 vendor, flags, rulesByType); 1092 } 1093}