001package gudusoft.gsqlparser.sqlenv; 002 003 004import gudusoft.gsqlparser.TBaseType; 005import gudusoft.gsqlparser.util.SQLUtil; 006 007import java.util.*; 008 009/** 010 * SQL schema, contains a list of schema objects. 011 */ 012public class TSQLSchema extends TSQLObject { 013 014 015 public String getQualifiedName(){ 016 return catalog.name+"."+name; 017 } 018 019 private TSQLCatalog catalog; 020 021 public TSQLCatalog getCatalog() { 022 return catalog; 023 } 024 025 // ===== Shadow Introduction: New type-specific indexes (Phase 2) ===== 026 // These maps provide O(1) lookup per object type, replacing the mixed schemaObjectMap. 027 // ConcurrentHashMap: name resolution reads them from other threads while DDL 028 // registration writes (shared-TSQLEnv concurrent analyzers); only keyed get/put, 029 // no iteration, so insertion order is not relied on. 030 private final java.util.concurrent.ConcurrentHashMap<String, TSQLTable> tables = new java.util.concurrent.ConcurrentHashMap<>(); 031 private final java.util.concurrent.ConcurrentHashMap<String, TSQLFunction> functions = new java.util.concurrent.ConcurrentHashMap<>(); 032 private final java.util.concurrent.ConcurrentHashMap<String, TSQLProcedure> procedures = new java.util.concurrent.ConcurrentHashMap<>(); 033 private final java.util.concurrent.ConcurrentHashMap<String, TSQLTrigger> triggers = new java.util.concurrent.ConcurrentHashMap<>(); 034 private final java.util.concurrent.ConcurrentHashMap<String, TSQLOraclePackage> oraclePackages = new java.util.concurrent.ConcurrentHashMap<>(); 035 private final java.util.concurrent.ConcurrentHashMap<String, TSQLSynonyms> synonyms = new java.util.concurrent.ConcurrentHashMap<>(); 036 037 // ===== Phase 3: Bucketed indexes for SQL Server COLLATION_BASED ===== 038 // These provide ~25x performance improvement for SQL Server (10,000 objects: 20μs → 800ns). 039 // ConcurrentHashMap keyed by object type: lazy creation must be atomic — two 040 // threads racing the old per-field lazy init could each build an index and one 041 // set of writes would be lost. 042 private final java.util.concurrent.ConcurrentHashMap<ESQLDataObjectType, BucketedIndex> bucketedIndexes = new java.util.concurrent.ConcurrentHashMap<>(); 043 044 // IdentifierService for consistent key generation (lazy initialization). 045 // volatile: duplicate (equivalent) instances under a racy lazy init are harmless, 046 // but a partially constructed instance must never be published to another thread. 047 private volatile IdentifierService identifierService; 048 049 // CollatorProvider for SQL Server (lazy initialization, volatile for safe publication) 050 private volatile CollatorProvider collatorProvider; 051 052 // ===== Phase 6: Schema seal mechanism for thread-safety hardening ===== 053 /** 054 * Seal state flag (volatile for thread visibility). 055 */ 056 private volatile boolean sealed = false; 057 058 /** 059 * Seal this schema to prevent further modifications (Phase 6). 060 * 061 * <p>See for detailed documentation. 062 */ 063 public void seal() { 064 this.sealed = true; 065 } 066 067 /** 068 * Check if this schema is sealed (Phase 6). 069 * 070 * @return true if sealed, false otherwise 071 */ 072 public boolean isSealed() { 073 return sealed; 074 } 075 076 /** 077 * Check if schema is not sealed, throw exception if sealed and enforcement is enabled. 078 * 079 * @throws IllegalStateException if sealed and TBaseType.ENFORCE_CATALOG_SEAL is true 080 */ 081 private void checkNotSealed() { 082 if (sealed && TBaseType.ENFORCE_CATALOG_SEAL) { 083 throw new IllegalStateException("Cannot modify sealed schema. Call seal() marks schema as read-only."); 084 } 085 } 086 087 /** 088 * Create a new instance of the schema, added to the catalog. 089 * 090 * @param sqlCatalog catalog contains this schema 091 * @param schemaName name of the schema 092 */ 093 public TSQLSchema(TSQLCatalog sqlCatalog,String schemaName){ 094 super(sqlCatalog.getSqlEnv(), schemaName,ESQLDataObjectType.dotSchema); 095 this.catalog = sqlCatalog; 096 this.catalog.addSchema(this); 097 } 098 099 /** 100 * Get or create IdentifierService (lazy initialization) 101 */ 102 private IdentifierService getIdentifierService() { 103 if (identifierService == null) { 104 IdentifierProfile profile = IdentifierProfile.forVendor( 105 this.sqlEnv.getDBVendor(), 106 IdentifierProfile.VendorFlags.defaults() 107 ); 108 identifierService = new IdentifierService(profile, null); 109 } 110 return identifierService; 111 } 112 113 /** 114 * Get or create CollatorProvider (lazy initialization, SQL Server only) 115 */ 116 private CollatorProvider getCollatorProvider() { 117 if (collatorProvider == null) { 118 collatorProvider = new CollatorProvider(); 119 } 120 return collatorProvider; 121 } 122 123 /** 124 * Check if SQL Server and bucketed index is enabled 125 */ 126 private boolean shouldUseBucketedIndex() { 127 return TBaseType.USE_BUCKETED_INDEX && 128 (this.sqlEnv.getDBVendor() == gudusoft.gsqlparser.EDbVendor.dbvmssql || 129 this.sqlEnv.getDBVendor() == gudusoft.gsqlparser.EDbVendor.dbvazuresql); 130 } 131 132 /** 133 * Slice S3: cache of whether the hierarchical type-specific map is globally 134 * legacy-compatible for a given {@code (vendor, objectType)} pair. 135 * 136 * <p>Key format: {@code "<EDbVendor.name()>|<ESQLDataObjectType.name()>"}. 137 * Both the IdentifierProfile (built from {@link IdentifierProfile#forVendor}) 138 * and the legacy {@link TSQLEnv} per-vendor case-sensitivity maps are static 139 * relative to the (vendor, type) tuple, so the answer is stable for the 140 * JVM lifetime and safe to cache process-wide. 141 */ 142 private static final java.util.concurrent.ConcurrentHashMap<String, Boolean> 143 HIERARCHICAL_COMPAT_CACHE = new java.util.concurrent.ConcurrentHashMap<>(); 144 145 /** 146 * Slice S3: detect whether the hierarchical type-specific map can be safely 147 * consulted for the entire {@code (vendor, objectType)} pair. 148 * 149 * <p>The hierarchical map keys come from {@link IdentifierService#keyForMap}. 150 * The legacy {@code schemaObjectMap} keys come from 151 * {@link gudusoft.gsqlparser.util.SQLUtil#getIdentifierNormalName}. 152 * 153 * <p>For several vendor/type combinations the two key functions diverge: 154 * <ul> 155 * <li>SQL Server / Azure SQL with {@code COLLATION_BASED} rules 156 * ({@code IdentifierRules.forSQLServer}: {@code CaseFold.NONE}) when 157 * {@link TSQLEnv#tableCollationCaseSensitive} reports {@code false} 158 * (legacy adds a {@code toUpperCase} pass).</li> 159 * <li>BigQuery tables ({@code IdentifierRules.forBigQueryTable}: 160 * {@code CaseFold.NONE / SENSITIVE}) where 161 * {@link TSQLEnv#tableCollationCaseSensitive} is {@code false}.</li> 162 * <li>Hive/Teradata routines and any vendor whose {@link IdentifierRules} 163 * differ from the legacy {@link TSQLEnv} per-type case-sensitivity 164 * map.</li> 165 * </ul> 166 * 167 * <p>When the two diverge, distinct case-different writes land under 168 * different keys in hierarchical (both retained) but collapse to the same 169 * key in legacy (last write wins). A subsequent lookup that happens to 170 * use a probe input where the two functions <em>locally</em> agree (e.g. 171 * an already-uppercase {@code "FOO"} on BigQuery) would still hit the 172 * stale earlier hierarchical entry while legacy holds the authoritative 173 * later write. So input-local parity is insufficient — the gate must be 174 * vendor/type-global. 175 * 176 * <p>We probe once with a case-mixed ASCII input and cache the result. 177 * If the two key functions agree on the probe they agree on every input 178 * (they only differ in fold direction; a probe that survives both folds 179 * is sufficient). When they diverge, hierarchical is skipped permanently 180 * for that {@code (vendor, type)} and the lookup falls through to legacy. 181 * 182 * <p>This is intentionally conservative; once the strict-mode switch from 183 * plan §3.3 lands (slice S7) the gate can flip to "always trust 184 * hierarchical" and accept the (more correct) behavior change. 185 */ 186 private boolean isHierarchicalCompatibleForType(ESQLDataObjectType objectType) { 187 try { 188 gudusoft.gsqlparser.EDbVendor vendor = this.sqlEnv.getDBVendor(); 189 String cacheKey = vendor.name() + "|" + objectType.name(); 190 Boolean cached = HIERARCHICAL_COMPAT_CACHE.get(cacheKey); 191 if (cached != null) { 192 return cached; 193 } 194 // Probe both unquoted and quoted representative inputs because 195 // IdentifierRules has independent {@code unquotedFold/Compare} and 196 // {@code quotedFold/Compare} axes; a vendor/type can match for one 197 // representation and diverge for the other (e.g. Snowflake where 198 // unquoted folds to UPPER on both sides but quoted preserves case 199 // in keyForMap while legacy still appends toUpperCase via 200 // tableCollationCaseSensitive=false). 201 String unquotedProbe = "ProbeXyz"; 202 String quotedProbe = quoteIdentifierForProbe(vendor, unquotedProbe); 203 IdentifierService svc = getIdentifierService(); 204 boolean compat = probeMatches(svc, vendor, unquotedProbe, objectType) 205 && probeMatches(svc, vendor, quotedProbe, objectType); 206 HIERARCHICAL_COMPAT_CACHE.put(cacheKey, compat); 207 return compat; 208 } catch (Throwable ignore) { 209 return false; 210 } 211 } 212 213 private boolean probeMatches(IdentifierService svc, 214 gudusoft.gsqlparser.EDbVendor vendor, 215 String probe, 216 ESQLDataObjectType objectType) { 217 String hierarchicalKey = svc.keyForMap(probe, objectType); 218 String legacyKey = SQLUtil.getIdentifierNormalName(vendor, probe, objectType); 219 return hierarchicalKey != null && hierarchicalKey.equals(legacyKey); 220 } 221 222 /** 223 * Wrap {@code name} in the vendor's quoted-identifier delimiter so the 224 * probe exercises {@code IdentifierRules.quotedFold/quotedCompare} and the 225 * legacy {@link gudusoft.gsqlparser.util.SQLUtil#normalizeIdentifier} 226 * branch that strips quotes before folding. Mirrors the dispatch in 227 * {@link TSQLEnv#isDelimitedIdentifier(gudusoft.gsqlparser.EDbVendor, String)}. 228 */ 229 private static String quoteIdentifierForProbe(gudusoft.gsqlparser.EDbVendor vendor, String name) { 230 switch (vendor) { 231 case dbvmssql: 232 case dbvazuresql: 233 return "[" + name + "]"; 234 case dbvmysql: 235 case dbvbigquery: 236 case dbvcouchbase: 237 case dbvhive: 238 case dbvimpala: 239 return "`" + name + "`"; 240 case dbvdax: 241 return "'" + name + "'"; 242 default: 243 return "\"" + name + "\""; 244 } 245 } 246 247 /** 248 * Get or create bucketed index for a specific object type 249 */ 250 private BucketedIndex getBucketedIndex(ESQLDataObjectType objectType) { 251 if (!shouldUseBucketedIndex()) { 252 return null; 253 } 254 255 switch (objectType) { 256 case dotTable: 257 case dotFunction: 258 case dotProcedure: 259 case dotTrigger: 260 case dotOraclePackage: 261 case dotSynonyms: 262 // atomic lazy creation; a racy per-field init could build two 263 // indexes for the same type and lose one side's writes 264 return bucketedIndexes.computeIfAbsent(objectType, type -> 265 new BucketedIndex(getCollatorProvider(), 266 getIdentifierService().getProfile().getFlags().defaultCollation, type)); 267 default: 268 return null; 269 } 270 } 271 272 public List<TSQLSchemaObject> getSchemaObjectList() { 273 List<TSQLSchemaObject> schemaObjectList = new LinkedList<>(); 274 synchronized (schemaObjectMap) { 275 for(String schemaObjectKey: schemaObjectMap.keySet()){ 276 TSQLSchemaObject schemaObject = schemaObjectMap.get(schemaObjectKey); 277 if(schemaObject instanceof TSQLProcedure){ 278 if(((TSQLProcedure)schemaObject).isOraclePackageProcedure()){ 279 continue; 280 } 281 } 282 schemaObjectList.add(schemaObject); 283 } 284 } 285 return schemaObjectList; 286 } 287 288 public List<TSQLSchemaObject> getPackageObjectList(TSQLOraclePackage oraclePackage) { 289 List<TSQLSchemaObject> schemaObjectList = new LinkedList<>(); 290 synchronized (schemaObjectMap) { 291 for (String schemaObjectKey : schemaObjectMap.keySet()) { 292 TSQLSchemaObject schemaObject = schemaObjectMap.get(schemaObjectKey); 293 if (schemaObject instanceof TSQLProcedure) { 294 if (!((TSQLProcedure) schemaObject).isOraclePackageProcedure()) { 295 continue; 296 } 297 TSQLProcedure procedure = (TSQLProcedure) schemaObject; 298 if (procedure.getOraclePackage() == oraclePackage) { 299 schemaObjectList.add(schemaObject); 300 } 301 } 302 } 303 } 304 return schemaObjectList; 305 } 306 307 private Map<String, TSQLSchemaObject> schemaObjectMap = Collections.synchronizedMap(new LinkedHashMap<String, TSQLSchemaObject>( )); 308 309// public boolean addTable(TSQLTable sqlTable){ 310// return addDataObject(sqlTable); 311// } 312// 313// public boolean addProcedure(TSQLProcedure sqlProcedure){ 314// return addDataObject(sqlProcedure); 315// } 316// 317// public boolean addFunction(TSQLFunction sqlFunction){ 318// return addDataObject(sqlFunction); 319// } 320// 321// public boolean addTrigger(TSQLTrigger sqlTrigger){ 322// return addDataObject(sqlTrigger); 323// } 324 325 /** 326 * add database object 327 * 328 * <p><strong>Dual-write strategy:</strong> Updates both legacy schemaObjectMap 329 * and new type-specific maps for backward compatibility and performance optimization. 330 * 331 * @param schemaObject schema object to add 332 */ 333 protected void addSchemaObject(TSQLSchemaObject schemaObject){ 334 // same monitor as createSchemaObject: registration of one object must be 335 // atomic across all indexes (reentrant when called from createSchemaObject 336 // via the schema-object constructors). 337 // Note: env.putSchemaObject (and thus the catalog provider) runs while this 338 // monitor is held — in-tree providers never call back into schema/env 339 // creation; a custom ICatalogProvider that does could deadlock. 340 synchronized (schemaObjectMap) { 341 checkNotSealed(); // Phase 6: checked under the mutation lock (seal() itself takes no monitor, so this narrows, not closes, the seal race) 342 // ===== Legacy path: Update schemaObjectMap (backward compatibility) ===== 343 String normalName = SQLUtil.getIdentifierNormalName(this.sqlEnv.getDBVendor(), schemaObject.name, schemaObject.getDataObjectType()); 344 if (schemaObject.getDataObjectType() == ESQLDataObjectType.dotFunction) 345 { 346 normalName = normalName+"$function"; 347 } 348 else if (schemaObject.getDataObjectType() == ESQLDataObjectType.dotProcedure) 349 { 350 normalName = normalName+"$procedure"; 351 } 352 else if (schemaObject.getDataObjectType() == ESQLDataObjectType.dotTrigger) 353 { 354 normalName = normalName+"$trigger"; 355 } 356 else if (schemaObject.getDataObjectType() == ESQLDataObjectType.dotOraclePackage) 357 { 358 normalName = normalName+"$package"; 359 } 360 else if (schemaObject.getDataObjectType() == ESQLDataObjectType.dotSynonyms) 361 { 362 normalName = normalName+"$synonyms"; 363 } 364 schemaObjectMap.put(normalName, schemaObject); 365 this.catalog.getSqlEnv().putSchemaObject(schemaObject.getQualifiedName(),schemaObject); 366 367 // ===== New path: Update type-specific maps (Phase 2 optimization) ===== 368 if (TBaseType.USE_HIERARCHICAL_INDEX) { 369 try { 370 String key = getIdentifierService().keyForMap(schemaObject.name, schemaObject.getDataObjectType()); 371 372 switch (schemaObject.getDataObjectType()) { 373 case dotTable: 374 tables.put(key, (TSQLTable) schemaObject); 375 break; 376 case dotFunction: 377 functions.put(key, (TSQLFunction) schemaObject); 378 break; 379 case dotProcedure: 380 procedures.put(key, (TSQLProcedure) schemaObject); 381 break; 382 case dotTrigger: 383 triggers.put(key, (TSQLTrigger) schemaObject); 384 break; 385 case dotOraclePackage: 386 oraclePackages.put(key, (TSQLOraclePackage) schemaObject); 387 break; 388 case dotSynonyms: 389 synonyms.put(key, (TSQLSynonyms) schemaObject); 390 break; 391 } 392 } catch (Throwable ignore) { 393 // Silently fail to maintain backward compatibility 394 } 395 } 396 397 // ===== Phase 3: Update bucketed index for SQL Server ===== 398 if (shouldUseBucketedIndex()) { 399 try { 400 BucketedIndex bucketedIndex = getBucketedIndex(schemaObject.getDataObjectType()); 401 if (bucketedIndex != null) { 402 bucketedIndex.put(schemaObject.name, schemaObject); 403 } 404 } catch (Throwable ignore) { 405 // Silently fail to maintain backward compatibility 406 } 407 } 408 } 409 } 410 411 TSQLSchemaObject findSchemaObject(ESQLDataObjectType dataObjectType, String schemaObjectName) { 412 // Phase 3.5: Use bucketed index for SQL Server (25x faster for 10,000+ objects) 413 if (shouldUseBucketedIndex()) { 414 try { 415 BucketedIndex bucketedIndex = getBucketedIndex(dataObjectType); 416 if (bucketedIndex != null) { 417 TSQLSchemaObject result = bucketedIndex.get(schemaObjectName); 418 if (result != null) { 419 if (TBaseType.LOG_INDEX_HIT_RATE) { 420 System.out.println("[BucketedIndex] Hit: " + dataObjectType + " - " + schemaObjectName); 421 } 422 return result; 423 } 424 } 425 } catch (Throwable ignore) { 426 // Fall through to legacy path 427 } 428 } 429 430 // Phase 2: Use hierarchical index (type-specific maps) when flag is enabled. 431 // 432 // Slice S3: Only consult hierarchical when its IdentifierService.keyForMap 433 // key function is globally compatible with legacy 434 // SQLUtil.getIdentifierNormalName for this {@code (vendor, type)} pair. 435 // See {@link #isHierarchicalCompatibleForType} for the rationale; in 436 // particular, an input-local parity check is insufficient because a 437 // case-different earlier write may already have populated hierarchical 438 // under a key that legacy collapsed (e.g. BigQuery: writes "FOO" then 439 // "foo"; legacy retains only the last; hierarchical retains both; 440 // querying "FOO" would still return the stale earlier hierarchical 441 // entry). Falling through to legacy preserves write/read symmetry. 442 if (TBaseType.USE_HIERARCHICAL_INDEX 443 && isHierarchicalCompatibleForType(dataObjectType)) { 444 try { 445 String key = getIdentifierService().keyForMap(schemaObjectName, dataObjectType); 446 TSQLSchemaObject result = null; 447 448 switch (dataObjectType) { 449 case dotTable: 450 result = tables.get(key); 451 break; 452 case dotFunction: 453 result = functions.get(key); 454 break; 455 case dotProcedure: 456 result = procedures.get(key); 457 break; 458 case dotTrigger: 459 result = triggers.get(key); 460 break; 461 case dotOraclePackage: 462 result = oraclePackages.get(key); 463 break; 464 case dotSynonyms: 465 result = synonyms.get(key); 466 break; 467 } 468 469 if (result != null) { 470 // Log hit rate if enabled 471 if (TBaseType.LOG_INDEX_HIT_RATE) { 472 System.out.println("[HierarchicalIndex] Hit: " + dataObjectType + " - " + schemaObjectName); 473 } 474 return result; 475 } 476 } catch (Throwable ignore) { 477 // Fall through to legacy path 478 } 479 } 480 481 // Legacy path: use suffix hacks and local map (backward compatibility) 482 String normalName = SQLUtil.getIdentifierNormalName(this.sqlEnv.getDBVendor(), schemaObjectName, dataObjectType); 483 if (dataObjectType == ESQLDataObjectType.dotFunction) 484 { 485 normalName = normalName+"$function"; 486 } 487 else if (dataObjectType == ESQLDataObjectType.dotProcedure) 488 { 489 normalName = normalName+"$procedure"; 490 } 491 else if (dataObjectType == ESQLDataObjectType.dotTrigger) 492 { 493 normalName = normalName+"$trigger"; 494 } 495 else if (dataObjectType == ESQLDataObjectType.dotOraclePackage) 496 { 497 normalName = normalName+"$package"; 498 } 499 else if (dataObjectType == ESQLDataObjectType.dotSynonyms) 500 { 501 normalName = normalName+"$synonyms"; 502 } 503 return schemaObjectMap.get(normalName); 504 } 505 506 /** 507 * create a oracle package belong to this schema 508 * 509 * @param oraclePackageName oracle package name 510 * @return an instance of the oracle package 511 */ 512 public TSQLOraclePackage createOraclePackage(String oraclePackageName){ 513 return (TSQLOraclePackage)createSchemaObject(oraclePackageName,ESQLDataObjectType.dotOraclePackage); 514 } 515 516 /** 517 * create a procedure belong to this schema 518 * 519 * @param procedureName procedure name 520 * @return an instance of the SQL procedure 521 */ 522 public TSQLProcedure createProcedure(String procedureName){ 523 return (TSQLProcedure)createSchemaObject(procedureName,ESQLDataObjectType.dotProcedure); 524 } 525 526 public TSQLProcedure createProcedure(String procedureName, ESQLDataObjectType type){ 527 return (TSQLProcedure)createSchemaObject(procedureName,type); 528 } 529 530 /** 531 * create a function belong to this schema 532 * 533 * @param functionName function name 534 * @return an instance of the SQL function. 535 */ 536 public TSQLFunction createFunction(String functionName){ 537 return (TSQLFunction)createSchemaObject(functionName,ESQLDataObjectType.dotFunction); 538 } 539 540 /** 541 * create a trigger belong to this schema 542 * 543 * @param triggerName trigger name 544 * @return an instance of the SQL trigger. 545 */ 546 public TSQLTrigger createTrigger(String triggerName){ 547 return (TSQLTrigger)createSchemaObject(triggerName,ESQLDataObjectType.dotTrigger); 548 } 549 550 /** 551 * create a synonyms belong to this schema 552 * 553 * @param synonymsName synonyms name 554 * @return an instance of the SQL synonyms 555 */ 556 public TSQLSynonyms createSynonyms(String synonymsName){ 557 return (TSQLSynonyms)createSchemaObject(synonymsName,ESQLDataObjectType.dotSynonyms); 558 } 559 560 /** 561 * create a synonyms belong to this schema and record its base target. 562 * 563 * <p>If a synonym with the same name already exists it is returned with its 564 * base target updated. Any of {@code sourceDatabase}/{@code sourceSchema} may 565 * be {@code null} to mean "the synonym's own database/schema"; cross-database 566 * base targets are allowed. 567 * 568 * @param synonymsName synonyms name 569 * @param sourceDatabase base database/catalog, or {@code null} 570 * @param sourceSchema base schema, or {@code null} 571 * @param sourceName base object name 572 * @return an instance of the SQL synonyms 573 */ 574 public TSQLSynonyms createSynonyms(String synonymsName, String sourceDatabase, String sourceSchema, String sourceName){ 575 TSQLSynonyms synonyms = createSynonyms(synonymsName); 576 if (synonyms != null) { 577 synonyms.setBaseTarget(sourceDatabase, sourceSchema, sourceName); 578 } 579 return synonyms; 580 } 581 582 /** 583 * create a table belong to this schema. If table with the same name already exists in the schema 584 * return the existing table. 585 * 586 * @param tableName table name 587 * @return an instance of the SQL table 588 */ 589 public TSQLTable createTable(String tableName){ 590 return (TSQLTable)createSchemaObject(tableName,ESQLDataObjectType.dotTable); 591 } 592 593 public TSQLTable createTable(String tableName, int priority){ 594 return (TSQLTable)createSchemaObject(tableName,ESQLDataObjectType.dotTable, priority); 595 } 596 597 // Slice S3: route public lookups through findSchemaObject so they go through 598 // the bucketed → hierarchical (IdentifierService.keyForMap) → legacy fallback 599 // chain, instead of bypassing both indexes via raw schemaObjectMap. 600 public boolean containsTable(String tableName) { 601 return findSchemaObject(ESQLDataObjectType.dotTable, tableName) != null; 602 } 603 604 public TSQLTable findTable(String tableName) { 605 return (TSQLTable) findSchemaObject(ESQLDataObjectType.dotTable, tableName); 606 } 607 608 public TSQLSynonyms findSynonyms(String synonymsName) { 609 return (TSQLSynonyms) findSchemaObject(ESQLDataObjectType.dotSynonyms, synonymsName); 610 } 611 612 public TSQLSchemaObject findSchemaObject(String schemaObjectName) { 613 TSQLSchemaObject object = findSchemaObject(ESQLDataObjectType.dotFunction, schemaObjectName); 614 if (object == null) { 615 object = findSchemaObject(ESQLDataObjectType.dotProcedure, schemaObjectName); 616 } 617 if (object == null) { 618 object = findSchemaObject(ESQLDataObjectType.dotTrigger, schemaObjectName); 619 } 620 if (object == null) { 621 object = findSchemaObject(ESQLDataObjectType.dotOraclePackage, schemaObjectName); 622 } 623 if (object == null) { 624 object = findSchemaObject(ESQLDataObjectType.dotSynonyms, schemaObjectName); 625 } 626 return object; 627 } 628 629 /** 630 * 631 * @param schemaObjectName 该名称不带 catalog, schema 前缀 632 * @param dataObjectType 633 * @return 634 */ 635 public TSQLSchemaObject createSchemaObject(String schemaObjectName, ESQLDataObjectType dataObjectType){ 636 return createSchemaObject(schemaObjectName, dataObjectType, 0); 637 } 638 639 /** 640 * 641 * @param schemaObjectName 该名称不带 catalog, schema 前缀 642 * @param dataObjectType 643 * @param priority 644 * @return 645 */ 646 protected TSQLSchemaObject createSchemaObject(String schemaObjectName, ESQLDataObjectType dataObjectType, int priority){ 647 TSQLSchemaObject result = null; 648 // synchronized so concurrent get-or-create of the same object can't produce 649 // duplicate twins (columns added to the losing twin would be invisible). 650 // The schema-object constructors re-enter this monitor via addSchemaObject — 651 // reentrant, keep both on the same lock or the race comes back. 652 synchronized (schemaObjectMap) { 653 TSQLSchemaObject schemaObject = findSchemaObject(dataObjectType, schemaObjectName); 654 if (schemaObject == null){ 655 switch (dataObjectType){ 656 case dotTable: 657 result = new TSQLTable(this,schemaObjectName); 658 break; 659 case dotOraclePackage: 660 result = new TSQLOraclePackage(this,schemaObjectName); 661 break; 662 case dotProcedure: 663 result = new TSQLProcedure(this,schemaObjectName); 664 break; 665 case dotFunction: 666 result = new TSQLFunction(this,schemaObjectName); 667 break; 668 case dotTrigger: 669 result = new TSQLTrigger(this,schemaObjectName); 670 break; 671 case dotSynonyms: 672 result = new TSQLSynonyms(this,schemaObjectName); 673 break; 674 } 675 }else if (dataObjectType == schemaObject.getDataObjectType()){ 676 result = schemaObject; 677 }else{ 678 System.out.println("object name conflict:"+getQualifiedName()+"."+schemaObjectName+",type:"+dataObjectType+" VS "+schemaObject.getQualifiedName()+", type:"+schemaObject.getDataObjectType()); 679 }; 680 681 if (result!=null && priority > result.getPriority()) { 682 result.setPriority(priority); 683 } 684 } 685 return result; 686 } 687 688}