001 002package gudusoft.gsqlparser.dlineage.dataflow.model; 003 004import gudusoft.gsqlparser.dlineage.routine.RoutineAliasKey; 005import gudusoft.gsqlparser.dlineage.routine.RoutineAliasRegistry; 006import gudusoft.gsqlparser.EDbVendor; 007import gudusoft.gsqlparser.resolver2.metadata.StatementMetadata.ProcedureEnvironment; 008import gudusoft.gsqlparser.EExpressionType; 009import gudusoft.gsqlparser.ESetOperatorType; 010import gudusoft.gsqlparser.ETableEffectType; 011import gudusoft.gsqlparser.TCustomSqlStatement; 012import gudusoft.gsqlparser.dlineage.util.DlineageUtil; 013import gudusoft.gsqlparser.dlineage.util.NormalNameCollisions; 014import gudusoft.gsqlparser.dlineage.util.Pair; 015import gudusoft.gsqlparser.dlineage.util.Pair3; 016import gudusoft.gsqlparser.dlineage.util.SHA256; 017import gudusoft.gsqlparser.dlineage.util.ScopedNameKey; 018import gudusoft.gsqlparser.dlineage.util.TableIdentityKey; 019import gudusoft.gsqlparser.nodes.*; 020import gudusoft.gsqlparser.sqlenv.ESQLDataObjectType; 021import gudusoft.gsqlparser.sqlenv.IdentifierInputForm; 022import gudusoft.gsqlparser.sqlenv.QualifiedCanonKey; 023import gudusoft.gsqlparser.sqlenv.TSQLEnv; 024import gudusoft.gsqlparser.sqlenv.TSQLTable; 025import gudusoft.gsqlparser.stmt.*; 026import gudusoft.gsqlparser.stmt.mssql.TMssqlDeclare; 027import gudusoft.gsqlparser.util.IndexedLinkedHashMap; 028import gudusoft.gsqlparser.util.Logger; 029import gudusoft.gsqlparser.util.LoggerFactory; 030import gudusoft.gsqlparser.util.SQLUtil; 031 032import java.util.*; 033 034@SuppressWarnings({ "unchecked", "rawtypes" }) 035public class ModelBindingManager { 036 037 private static final Logger logger = LoggerFactory.getLogger(ModelBindingManager.class); 038 039 private final Map modelBindingMap = new LinkedHashMap(); 040 private final Map processModelBindingMap = new LinkedHashMap(); 041 private final Map viewModelBindingMap = new LinkedHashMap(); 042 private final Map insertModelBindingMap = new LinkedHashMap(); 043 private final Map createModelBindingMap = new LinkedHashMap(); 044 private final Map createModelQuickBindingMap = new LinkedHashMap(); 045 private final Map mergeModelBindingMap = new LinkedHashMap(); 046 private final Map updateModelBindingMap = new LinkedHashMap(); 047 private final Map cursorModelBindingMap = new LinkedHashMap(); 048 private final Map tableNamesMap = new LinkedHashMap(); 049 // Models bound by table identity: a canonical miss never falls back to their folded name. 050 private final Set<Table> identityBoundTables = Collections.newSetFromMap(new IdentityHashMap<Table, Boolean>()); 051 // Legacy normal names that different names share in this analysis; see normalName. 052 private EDbVendor contestedVendor; 053 private Set<String> contestedNormalNames = Collections.emptySet(); 054 private Set<String> contestedLegacyNames = Collections.emptySet(); 055 private final Map<String, String> collisionFreeNames = new HashMap<String, String>(); 056 private boolean legacyNormalNames; 057 // Metadata identity lets a proven caller/callee share one endpoint after name scopes close. 058 private final Map<TSQLTable, Table> temporaryMetadataModels = new IdentityHashMap<>(); 059 private final Map<TSQLTable, TStoredProcedureSqlStatement> temporaryMetadataOwners = new IdentityHashMap<>(); 060 /** The scopes of the routine bodies being analyzed, innermost first; see enterTemporaryTableScope. */ 061 private final Deque<List<Map>> temporaryScopes = new ArrayDeque<List<Map>>(); 062 private final Set<Table> dropTables = new HashSet<Table>(); 063 // The routine model bound to each routine statement. The same statement is bound to other models later (a SQL 064 // function's result set), which replace it in modelBindingMap; this keeps the routine's own. 065 private final Map<TCustomSqlStatement, Procedure> routineModels = new IdentityHashMap<TCustomSqlStatement, Procedure>(); 066 private gudusoft.gsqlparser.dlineage.routine.RoutineShadow.State routineShadowState; 067 // Routines and Oracle packages by the names as written (RoutineAliasRegistry keeps the legacy index's precedence). 068 private final RoutineAliasRegistry<Procedure> procedureAliases = new RoutineAliasRegistry<Procedure>(); 069 private final RoutineAliasRegistry<OraclePackage> packageAliases = new RoutineAliasRegistry<OraclePackage>(); 070 // A routine's function results (the result sets of its body) by the routine's name as written. 071 private final RoutineAliasRegistry<Set<Object>> functionTableAliases = new RoutineAliasRegistry<Set<Object>>(); 072 // The deprecated text-keyed API's own maps: exactly the entries bound through it, keyed by the text as given and 073 // looked up as the legacy maps were, with no analysis context needed (see bindProcedureByName). 074 private final Map<String, Procedure> legacyProcedureNames = new LinkedHashMap<String, Procedure>(); 075 private final Map<String, OraclePackage> legacyPackageNames = new LinkedHashMap<String, OraclePackage>(); 076 private final Map<Object, Set<Object>> legacyFunctionTables = new LinkedHashMap<Object, Set<Object>>(); 077 // Local symbols (variables, cursors, declared types, trigger correlation names, table variables) by scope and name 078 // as written. 079 private final gudusoft.gsqlparser.dlineage.symbol.LocalSymbolRegistry localSymbols = 080 new gudusoft.gsqlparser.dlineage.symbol.LocalSymbolRegistry(); 081 // The cursor models by cursor name as written, in the declaring scope. 082 private final gudusoft.gsqlparser.dlineage.symbol.LocalSymbolRegistry localCursorModels = 083 new gudusoft.gsqlparser.dlineage.symbol.LocalSymbolRegistry(); 084 // The identifier rules the analysis keyed this manager's routines under, so the deprecated text API can read a 085 // name the same way from a thread with no analysis on it. 086 private gudusoft.gsqlparser.sqlenv.IdentifierService routineService; 087 private final Set<Relationship> relationHolder = Collections.synchronizedSet(new LinkedHashSet<Relationship>()); 088 // A table alias is resolved inside one statement: keyed by the statement's hash and the alias's 089 // identity as written, never by a folded string. 090 private final Map<ScopedNameKey, TTable> tableAliasMap = new LinkedHashMap<ScopedNameKey, TTable>(); 091 private final Map<TCustomSqlStatement, String> tableHashMap = new LinkedHashMap(); 092 private final Map<TCustomSqlStatement, String> topStmtHashMap = new HashMap(); 093 private final Map hashSQLMap = new HashMap(); 094 private final Map dynamicSQLMap = new HashMap(); 095 private final Set tableSet = new LinkedHashSet(); 096 private final Set resultSetSet = new LinkedHashSet(); 097 private final Set<String> dblinkTableSet = new LinkedHashSet(); 098 099 // Pipelined function stitching state 100 // Keyed by a type's name as written under the analysis's rules (PipelinedFunctionAnalyzer); the deprecated 101 // String API keeps its own text keys in the same maps. 102 private final Map<Object, List<String>> objectTypeIndex = new LinkedHashMap<>(); 103 private final Map<Object, String> collectionToElementTypeIndex = new LinkedHashMap<>(); 104 private final Map<Object, List<PipelinedFunctionSignature>> pipelinedSignatures = new LinkedHashMap<>(); 105 private final List<PendingPipelinedCallSite> pendingPipelinedCallSites = new ArrayList<>(); 106 private final Set<String> resolvingPipelinedFunctions = new HashSet<>(); 107 108 public Map<String, Long> DISPLAY_ID = new LinkedHashMap<String, Long>(); 109 public Map<Long, String> DISPLAY_NAME = new LinkedHashMap<Long, String>(); 110 public Map<String, String> virtualTableNames = new LinkedHashMap<String, String>(); 111 112 public long TABLE_COLUMN_ID = 0; 113 public long RELATION_ID = 0; 114 public int virtualTableIndex = -1; 115 116 /** The SQL-LR observation recorder of this analysis, or null (plan 06; internal). */ 117 private SqllrObservations sqllrObservations; 118 119 /** The SQL-LR observation recorder of this analysis, or null. Internal API of the SQL-LR producer. */ 120 public SqllrObservations getSqllrObservations() { 121 return sqllrObservations; 122 } 123 124 /** Binds a SQL-LR observation recorder to this analysis. Internal API of the SQL-LR producer. */ 125 public void setSqllrObservations(SqllrObservations observations) { 126 this.sqllrObservations = observations; 127 } 128 129 /** 130 * The position in the script of the top-level statement being analyzed, as ordered by the analyzer: the unit, 131 * then the statement in the unit; -1 when unknown. A read of a nested field is validated against the CREATE 132 * TABLE in force at this position (plan rule R10, {@link Table#getActiveFieldDeclaration()}). 133 */ 134 private long currentStatementOrdinal = -1; 135 136 public long getCurrentStatementOrdinal() { 137 return currentStatementOrdinal; 138 } 139 140 public void setCurrentStatementOrdinal(long currentStatementOrdinal) { 141 this.currentStatementOrdinal = currentStatementOrdinal; 142 } 143 144 private final static ThreadLocal localManager = new ThreadLocal(); 145 private final static ThreadLocal globalDatabase = new ThreadLocal(); 146 private final static ThreadLocal globalSchema = new ThreadLocal(); 147 /** 148 * The form the global database / schema text arrived in: absent for a SQL spelling (a {@code USE}, 149 * a session default, a search path), {@code CATALOG_STORED} for the stored name a metadata export 150 * gives a query's group. The text stays the bare name, for display; lineage keys read it by its form. 151 */ 152 private final static ThreadLocal<IdentifierInputForm> globalDatabaseForm = new ThreadLocal<IdentifierInputForm>(); 153 private final static ThreadLocal<IdentifierInputForm> globalSchemaForm = new ThreadLocal<IdentifierInputForm>(); 154 private final static ThreadLocal schemaUnsetStatements = new ThreadLocal(); 155 private final static ThreadLocal globalVendor = new ThreadLocal(); 156 private final static ThreadLocal globalSQLEnv = new ThreadLocal(); 157 private final static ThreadLocal globalHash = new ThreadLocal(); 158 private final static ThreadLocal globalStmtStack = new ThreadLocal(); 159 /** 160 * Set while the statement on {@link #getGlobalStmtStack()} is a stand-in rather than the 161 * statement the work belongs to — pipelined stitching runs after every pass and pushes the 162 * script's LAST statement just to have one. Anything that reads the statement for provenance, 163 * such as a comment, must not attribute that statement's text to these relationships. 164 */ 165 private final static ThreadLocal syntheticStmtContext = new ThreadLocal(); 166 167 public static boolean isSyntheticStmtContext() { 168 return Boolean.TRUE.equals(syntheticStmtContext.get()); 169 } 170 171 public static void setSyntheticStmtContext(boolean synthetic) { 172 syntheticStmtContext.set(Boolean.valueOf(synthetic)); 173 } 174 private final static ThreadLocal globalSqlInfo = new ThreadLocal(); 175 private final static ThreadLocal globalOption = new ThreadLocal(); 176 private final static ThreadLocal globalOraclePackage = new ThreadLocal(); 177 private final static ThreadLocal globalProcedure = new ThreadLocal(); 178 179 public static void set(ModelBindingManager modelManager) { 180 if (modelManager != null) { 181 localManager.set(modelManager); 182 } 183 } 184 185 public static ModelBindingManager get() { 186 return (ModelBindingManager) localManager.get(); 187 } 188 189 public static void setGlobalDatabase(String database) { 190 if (database != null) { 191 globalDatabase.set(database); 192 globalDatabaseForm.remove(); 193 } 194 } 195 196 /** {@link #setGlobalDatabase(String)} with the form the text arrived in; null is a SQL spelling. */ 197 public static void setGlobalDatabase(String database, IdentifierInputForm form) { 198 if (database != null) { 199 globalDatabase.set(database); 200 if (form == IdentifierInputForm.CATALOG_STORED) { 201 globalDatabaseForm.set(form); 202 } else { 203 globalDatabaseForm.remove(); 204 } 205 } 206 } 207 208 /** {@code CATALOG_STORED} when the global database text is a stored name, else null. */ 209 public static IdentifierInputForm getGlobalDatabaseForm() { 210 return globalDatabaseForm.get(); 211 } 212 213 /** The global database as a SQL spelling: its text, delimited when it is a stored name that needs it. */ 214 public static String getGlobalDatabaseSpelling() { 215 String database = getGlobalDatabase(); 216 return getGlobalDatabaseForm() == IdentifierInputForm.CATALOG_STORED 217 ? DlineageUtil.storedNameSpelling(ESQLDataObjectType.dotCatalog, database) : database; 218 } 219 220 public static String getGlobalDatabase() { 221 return (String) globalDatabase.get(); 222 } 223 224 public static void removeGlobalDatabase() { 225 globalDatabase.remove(); 226 globalDatabaseForm.remove(); 227 } 228 229 public static void setGlobalSchema(String schema) { 230 if (schema != null) { 231 globalSchema.set(schema); 232 globalSchemaForm.remove(); 233 } 234 } 235 236 /** {@link #setGlobalSchema(String)} with the form the text arrived in; null is a SQL spelling. */ 237 public static void setGlobalSchema(String schema, IdentifierInputForm form) { 238 if (schema != null) { 239 globalSchema.set(schema); 240 if (form == IdentifierInputForm.CATALOG_STORED) { 241 globalSchemaForm.set(form); 242 } else { 243 globalSchemaForm.remove(); 244 } 245 } 246 } 247 248 /** {@code CATALOG_STORED} when the global schema text is a stored name, else null. */ 249 public static IdentifierInputForm getGlobalSchemaForm() { 250 return globalSchemaForm.get(); 251 } 252 253 /** The global schema as a SQL spelling: its text, delimited when it is a stored name that needs it. */ 254 public static String getGlobalSchemaSpelling() { 255 String schema = getGlobalSchema(); 256 return getGlobalSchemaForm() == IdentifierInputForm.CATALOG_STORED 257 ? DlineageUtil.storedNameSpelling(ESQLDataObjectType.dotSchema, schema) : schema; 258 } 259 260 public static void removeGlobalSchema() { 261 globalSchema.remove(); 262 globalSchemaForm.remove(); 263 } 264 265 public static String getGlobalSchema() { 266 return (String) globalSchema.get(); 267 } 268 269 /** 270 * The statements that a Snowflake {@code USE <database>} left with NO current 271 * schema, i.e. everything after such a switch up to the next schema switch. 272 * 273 * <p>Recorded as a set of statements rather than as a running flag on 274 * purpose. The analyzer walks the statement list several times - one pass for 275 * CREATE TABLE, another for views, another for procedures - replaying the 276 * {@code USE} statements in each. A running flag set during one pass is still 277 * set when a LATER pass reaches a statement that precedes the switch, so it 278 * would apply the clearing backwards and strip an already-exact qualification 279 * off a statement that ran while the old schema was still current. Keyed by 280 * statement, the answer is the same no matter which pass asks. 281 * 282 * <p>See GitHub #715 / MantisBT 4677. 283 */ 284 public static void setSchemaUnsetStatements(Set<TCustomSqlStatement> statements) { 285 if (statements == null || statements.isEmpty()) { 286 schemaUnsetStatements.remove(); 287 } else { 288 schemaUnsetStatements.set(statements); 289 } 290 } 291 292 public static void removeSchemaUnsetStatements() { 293 schemaUnsetStatements.remove(); 294 } 295 296 /** 297 * Whether the statement being analyzed right now has no current schema 298 * because a Snowflake {@code USE <database>} unset it. 299 * 300 * @see #setSchemaUnsetStatements(Set) 301 */ 302 @SuppressWarnings("unchecked") 303 public static boolean isGlobalSchemaUnset() { 304 Set<TCustomSqlStatement> unset = (Set<TCustomSqlStatement>) schemaUnsetStatements.get(); 305 if (unset == null || unset.isEmpty()) { 306 return false; 307 } 308 Stack<TCustomSqlStatement> stack = getGlobalStmtStack(); 309 if (stack == null || stack.isEmpty()) { 310 return false; 311 } 312 return unset.contains(DlineageUtil.getTopStmt(stack.peek())); 313 } 314 315 public static void setGlobalHash(String hash) { 316 if (hash != null) { 317 globalHash.set(hash); 318 } 319 } 320 321 public static void removeGlobalHash() { 322 globalHash.remove(); 323 } 324 325 public static String getGlobalHash() { 326 return (String) globalHash.get(); 327 } 328 329 public static void setGlobalOraclePackage(OraclePackage currentOraclePackage) { 330 if (currentOraclePackage != null) { 331 globalOraclePackage.set(currentOraclePackage); 332 } 333 } 334 335 public static void removeGlobalOraclePackage() { 336 globalOraclePackage.remove(); 337 } 338 339 public static OraclePackage getGlobalOraclePackage() { 340 return (OraclePackage) globalOraclePackage.get(); 341 } 342 343 public static void setGlobalProcedure(TStoredProcedureSqlStatement procedureSqlStatement) { 344 if (procedureSqlStatement != null) { 345 globalProcedure.set(procedureSqlStatement); 346 } 347 } 348 349 public static void removeGlobalProcedure() { 350 globalProcedure.remove(); 351 ModelBindingManager.get().cleanTempTable(); 352 } 353 354 /** 355 * Puts back the routine a statement was analyzed under, null for none. Unlike {@link #removeGlobalProcedure()} 356 * this ends no routine: the temporary tables stay where they are. 357 */ 358 public static void restoreGlobalProcedure(TStoredProcedureSqlStatement procedureSqlStatement) { 359 if (procedureSqlStatement != null) { 360 globalProcedure.set(procedureSqlStatement); 361 } else { 362 globalProcedure.remove(); 363 } 364 } 365 366 public static TStoredProcedureSqlStatement getGlobalProcedure() { 367 return (TStoredProcedureSqlStatement) globalProcedure.get(); 368 } 369 370 public static void setGlobalVendor(EDbVendor vendor) { 371 if (vendor != null) { 372 globalVendor.set(vendor); 373 } 374 } 375 376 public static void removeGlobalVendor() { 377 globalVendor.remove(); 378 } 379 380 public static EDbVendor getGlobalVendor() { 381 return (EDbVendor) globalVendor.get(); 382 } 383 384 /** 385 * The frozen {@link gudusoft.gsqlparser.sqlenv.IdentifierService} of the analysis running on 386 * this thread — one engine, configured once at the analyzer entry, used by every lineage 387 * identity key, binding map and output merge in the run. 388 * 389 * <p>Carried the same way this class already carries the vendor, the SQL environment and the 390 * option, so that two analyses with opposite configurations can run concurrently without 391 * either seeing the other's rules. It is NOT consulted by the vendor-only static comparison 392 * API in {@code SQLUtil} / {@code IdentifierService}: those keep their stated default 393 * semantics for independent callers. 394 */ 395 private final static ThreadLocal globalIdentifierService = new ThreadLocal(); 396 397 /** 398 * Install the analysis's identifier service for this thread. 399 * 400 * <p>Callers save the previous value and restore it in a {@code finally} block; nesting on one 401 * thread and thread-pool reuse both depend on that, and a leaked service would let the next 402 * analysis on the same worker key its model under another run's rules. 403 * 404 * @param service the frozen service; null clears the binding 405 */ 406 public static void setGlobalIdentifierService(gudusoft.gsqlparser.sqlenv.IdentifierService service) { 407 if (service == null) { 408 globalIdentifierService.remove(); 409 } else { 410 globalIdentifierService.set(service); 411 } 412 } 413 414 public static void removeGlobalIdentifierService() { 415 globalIdentifierService.remove(); 416 } 417 418 /** 419 * The service bound to this thread with no vendor-default substitution — what 420 * {@link #setGlobalIdentifierService} has to be handed back to restore the previous state. 421 * 422 * @return the bound service, or null when nothing is bound 423 */ 424 /** 425 * Publishes the legacy normal names that different names share in this analysis, as 426 * {@link NormalNameCollisions#scan} found them; {@code null} clears them. 427 */ 428 public void setContestedNormalNames(EDbVendor vendor, Set<String> contested) { 429 boolean none = vendor == null || contested == null || contested.isEmpty(); 430 contestedVendor = none ? null : vendor; 431 contestedNormalNames = none ? Collections.<String>emptySet() : contested; 432 contestedLegacyNames = none ? Collections.<String>emptySet() : NormalNameCollisions.legacyNames(contested); 433 collisionFreeNames.clear(); 434 } 435 436 /** 437 * Makes {@link #normalName} return legacy names, for a pass that only relabels finished output 438 * (normalized display names are bare, as the legacy fold made them). 439 * 440 * @return the previous setting, to restore 441 */ 442 public boolean useLegacyNormalNames(boolean legacy) { 443 boolean previous = legacyNormalNames; 444 legacyNormalNames = legacy; 445 return previous; 446 } 447 448 /** Whether any two different names in this analysis share a legacy normal name. */ 449 public boolean hasContestedNormalNames() { 450 return !contestedLegacyNames.isEmpty(); 451 } 452 453 /** 454 * The normal name of one identifier segment in this analysis: its legacy name, unless a 455 * different name shares that string (see {@link NormalNameCollisions}). 456 */ 457 public String normalName(EDbVendor vendor, ESQLDataObjectType type, String origin, String legacy) { 458 if (contestedLegacyNames.isEmpty() || legacyNormalNames || legacy == null 459 || vendor != contestedVendor || type == null || origin == null 460 || !contestedLegacyNames.contains(legacy.toUpperCase(Locale.ROOT))) { 461 return legacy; 462 } 463 String memoKey = type.name() + '\u0000' + origin; 464 String name = collisionFreeNames.get(memoKey); 465 if (name == null) { 466 name = NormalNameCollisions.normalName(contestedNormalNames, identifierServiceFor(vendor), type, 467 origin, legacy); 468 collisionFreeNames.put(memoKey, name); 469 } 470 return name; 471 } 472 473 /** The analysis's own identifier rules when they are for {@code vendor}, else the vendor's. */ 474 private static gudusoft.gsqlparser.sqlenv.IdentifierService identifierServiceFor(EDbVendor vendor) { 475 gudusoft.gsqlparser.sqlenv.IdentifierService service = getGlobalIdentifierService(); 476 return service != null && service.getProfile().getVendor() == vendor ? service 477 : gudusoft.gsqlparser.sqlenv.IdentifierService.defaultServiceFor(vendor); 478 } 479 480 public static gudusoft.gsqlparser.sqlenv.IdentifierService getGlobalIdentifierServiceRaw() { 481 return (gudusoft.gsqlparser.sqlenv.IdentifierService) globalIdentifierService.get(); 482 } 483 484 /** 485 * The analysis's identifier service, or the vendor default when no analysis configured one. 486 * 487 * @return the service, or null when not even a vendor is known on this thread 488 */ 489 public static gudusoft.gsqlparser.sqlenv.IdentifierService getGlobalIdentifierService() { 490 gudusoft.gsqlparser.sqlenv.IdentifierService service = 491 (gudusoft.gsqlparser.sqlenv.IdentifierService) globalIdentifierService.get(); 492 if (service != null) { 493 return service; 494 } 495 EDbVendor vendor = getGlobalVendor(); 496 return vendor == null ? null 497 : gudusoft.gsqlparser.sqlenv.IdentifierService.defaultServiceFor(vendor); 498 } 499 500 public static void setGlobalSQLEnv(TSQLEnv sqlenv) { 501 if (sqlenv != null) { 502 globalSQLEnv.set(sqlenv); 503 } 504 } 505 506 public static void removeGlobalSQLEnv() { 507 globalSQLEnv.remove(); 508 } 509 510 public static TSQLEnv getGlobalSQLEnv() { 511 return (TSQLEnv) globalSQLEnv.get(); 512 } 513 514 public static String getGlobalServer() { 515 TSQLEnv sqlEnv = getGlobalSQLEnv(); 516 if (sqlEnv != null) 517 return TSQLEnv.DEFAULT_SERVER_NAME.equals(sqlEnv.getDefaultServerName()) ? null : sqlEnv.getDefaultServerName(); 518 return null; 519 } 520 521 /** 522 * Resolve the effective default database (catalog) used to qualify 523 * unqualified / partial object names, with a three-tier precedence: 524 * 525 * <ol> 526 * <li>the active global database — an in-script {@code USE database}, or the 527 * per-group database set by {@code DataFlowAnalyzer};</li> 528 * <li>the per-analysis {@code Option.getDefaultDatabase()};</li> 529 * <li>the attached {@code TSQLEnv} default catalog;</li> 530 * <li>{@code TSQLEnv.DEFAULT_DB_NAME}.</li> 531 * </ol> 532 * 533 * Each lower tier is consulted only when the higher tier is null/empty. This 534 * lets a shared multi-catalog {@code TSQLEnv} be reused across analyses with 535 * different per-analysis default databases (tier 2) without mutating the env, 536 * while still honoring an in-script {@code USE database} (tier 1). 537 */ 538 public static String getEffectiveDefaultDatabase() { 539 // tier 1: active global database (USE database / per-group database) 540 String database = getGlobalDatabase(); 541 if (!SQLUtil.isEmpty(database)) { 542 return database; 543 } 544 // tier 2: per-analysis Option default database 545 Option option = getGlobalOption(); 546 if (option != null) { 547 String optionDatabase = option.getDefaultDatabase(); 548 if (!SQLUtil.isEmpty(optionDatabase) && !TSQLEnv.DEFAULT_DB_NAME.equals(optionDatabase)) { 549 return optionDatabase; 550 } 551 } 552 // tier 3: attached TSQLEnv default catalog 553 TSQLEnv sqlenv = getGlobalSQLEnv(); 554 if (sqlenv != null && !SQLUtil.isEmpty(sqlenv.getDefaultCatalogName())) { 555 return sqlenv.getDefaultCatalogName(); 556 } 557 // tier 4: fallback 558 return TSQLEnv.DEFAULT_DB_NAME; 559 } 560 561 /** 562 * The schema an unqualified name defaults to right now. 563 * 564 * <p>Six model classes used to carry an identical copy of this fallback; it 565 * lives here so the {@link #isGlobalSchemaUnset() unset} rule cannot be 566 * applied to some of them and forgotten on the others. Returns 567 * {@link TSQLEnv#DEFAULT_SCHEMA_NAME} when there is no schema to default to. 568 * 569 * @see #getEffectiveDefaultDatabase() 570 */ 571 public static String getEffectiveDefaultSchema() { 572 // A Snowflake USE <database> unset the current schema for this statement. 573 // The attached env's default schema is batch-global -- TUseSchema writes 574 // it while PARSING, so it holds the LAST USE SCHEMA of the whole script -- 575 // and falling back to it here would re-attach the previous database's 576 // schema to names that follow the switch. See GitHub #715. 577 if (isGlobalSchemaUnset()) { 578 // Unset, unless the attached catalog PROVES what the switch landed 579 // in. Snowflake makes PUBLIC current when the new database has one; 580 // when the catalog shows it does, that is a fact rather than an 581 // assumption, and discarding it would lose an exact resolution. 582 String afterSwitch = null; 583 if (getGlobalSQLEnv() != null) { 584 afterSwitch = getGlobalSQLEnv().findSchemaAfterDatabaseSwitch(getEffectiveDefaultDatabase()); 585 } 586 return SQLUtil.isEmpty(afterSwitch) ? TSQLEnv.DEFAULT_SCHEMA_NAME : afterSwitch; 587 } 588 String defaultSchema = null; 589 if (getGlobalSQLEnv() != null) { 590 defaultSchema = getGlobalSQLEnv().getDefaultSchemaName(); 591 } 592 if (!SQLUtil.isEmpty(defaultSchema)) { 593 return defaultSchema; 594 } 595 return TSQLEnv.DEFAULT_SCHEMA_NAME; 596 } 597 598 public static void setGlobalStmtStack(Stack<TCustomSqlStatement> stack) { 599 if (stack != null) { 600 globalStmtStack.set(stack); 601 } 602 } 603 604 public static void removeGlobalStmtStack() { 605 globalStmtStack.remove(); 606 } 607 608 public static void setGlobalSqlInfo(IndexedLinkedHashMap<String, List<SqlInfo>> sqlInfoMap) { 609 if (sqlInfoMap != null) { 610 globalSqlInfo.set(sqlInfoMap); 611 } 612 } 613 614 public static void removeGlobalSqlInfo() { 615 globalSqlInfo.remove(); 616 } 617 618 public static IndexedLinkedHashMap<String, List<SqlInfo>> getGlobalSqlInfo() { 619 return (IndexedLinkedHashMap<String, List<SqlInfo>>) globalSqlInfo.get(); 620 } 621 622 public static void setGlobalOption(Option option) { 623 if (option != null) { 624 globalOption.set(option); 625 } 626 } 627 628 public static Option getGlobalOption() { 629 return (Option) globalOption.get(); 630 } 631 632 public static void removeGlobalOption() { 633 globalOption.remove(); 634 } 635 636 public static Stack<TCustomSqlStatement> getGlobalStmtStack() { 637 return (Stack<TCustomSqlStatement>) globalStmtStack.get(); 638 } 639 640 public static void remove() { 641 localManager.remove(); 642 globalDatabase.remove(); 643 globalSchema.remove(); 644 globalDatabaseForm.remove(); 645 globalSchemaForm.remove(); 646 schemaUnsetStatements.remove(); 647 globalVendor.remove(); 648 globalSQLEnv.remove(); 649 globalHash.remove(); 650 globalStmtStack.remove(); 651 globalOption.remove(); 652 globalSqlInfo.remove(); 653 globalIdentifierService.remove(); 654 } 655 656 private IdentityHashMap<Object,Object> commentOccurrenceBindings; 657 658 /** Records the model actually selected by the factory, without changing its lookup maps. */ 659 public <T> T observeCommentOccurrence(Object ast, T model) { 660 Option opt = getGlobalOption(); 661 if (opt != null && opt.isCollectCommentBindings() && ast != null && model != null) { 662 if (commentOccurrenceBindings == null) commentOccurrenceBindings = new IdentityHashMap<Object,Object>(); 663 commentOccurrenceBindings.put(ast, model); 664 } 665 return model; 666 } 667 668 /** Read existing AST bindings without name fallback, allocation, or ID changes. */ 669 public void visitExactBindings(java.util.function.BiConsumer<Object, Object> visitor) { 670 if (commentOccurrenceBindings != null) { 671 try { commentOccurrenceBindings.forEach(visitor); } 672 finally { commentOccurrenceBindings = null; } 673 } 674 for (Map map : new Map[] { modelBindingMap, processModelBindingMap, viewModelBindingMap, 675 insertModelBindingMap, createModelBindingMap, mergeModelBindingMap, updateModelBindingMap }) { 676 for (Object item : map.entrySet()) { 677 Map.Entry entry = (Map.Entry) item; 678 visitor.accept(entry.getKey(), entry.getValue()); 679 } 680 } 681 } 682 683 public void bindModel(Object gspModel, Object relationModel) { 684 if(gspModel == null) { 685 return; 686 } 687 modelBindingMap.put(gspModel, relationModel); 688 if (gspModel instanceof TCustomSqlStatement && relationModel instanceof Procedure) { 689 routineModels.put((TCustomSqlStatement) gspModel, (Procedure) relationModel); 690 } 691 692 TTable table = null; 693 if (gspModel instanceof TTable && !(gspModel instanceof TCTE)) { 694 table = ((TTable) gspModel); 695 } else if (gspModel instanceof Table) { 696 table = ((Table) gspModel).getTableObject(); 697 } else if (gspModel instanceof QueryTable) { 698 table = ((QueryTable) gspModel).getTableObject(); 699 } else if (gspModel instanceof TCTE) { 700 TTableList tables = ((TCTE) gspModel).getPreparableStmt().tables; 701 for (int j = 0; j < tables.size(); j++) { 702 TTable item = tables.getTable(j); 703 if (item != null && !SQLUtil.isEmpty(item.getAliasName())) { 704 TCustomSqlStatement stmt = ((TCTE) gspModel).getPreparableStmt(); 705 if (tableHashMap.containsKey(stmt)) { 706 tableAliasMap.put(aliasKey(tableHashMap.get(stmt), item.getAliasName()), item); 707 } else { 708 String sql = stmt.toString(); 709 if(sql == null){ 710 System.err.println("Current statement toString() returns null."); 711 continue; 712 } 713 String hash = SHA256.getMd5(sql); 714 tableHashMap.put(stmt, hash); 715 tableAliasMap.put(aliasKey(hash, item.getAliasName()), item); 716 } 717 } 718 719 if (item != null) { 720 tableSet.add(item); 721 } 722 } 723 } else if (gspModel instanceof Pair && ((Pair) gspModel).first instanceof Table) { 724 table = ((Table) ((Pair) gspModel).first).getTableObject(); 725 } 726 727 if (table == null && relationModel instanceof QueryTable) { 728 table = ((QueryTable) relationModel).getTableObject(); 729 } 730 731 updateTableAliasMap(table); 732 733 if (table != null) { 734 tableSet.add(table); 735 } 736 737 if(relationModel instanceof ResultSet) { 738 resultSetSet.add(relationModel); 739 } 740 } 741 742 /** The alias map's key: the statement hash, and the alias (or the column's qualifier) as written. */ 743 private static ScopedNameKey aliasKey(String statementHash, String aliasAsWritten) { 744 return new ScopedNameKey(statementHash, DlineageUtil.tableNameKey(aliasAsWritten)); 745 } 746 747 protected void updateTableAliasMap(TTable table) { 748 if (table != null && !SQLUtil.isEmpty(table.getAliasName())) { 749 TCustomSqlStatement stmt = ModelBindingManager.getGlobalStmtStack().peek(); 750 if (tableHashMap.containsKey(stmt)) { 751 tableAliasMap.put(aliasKey(tableHashMap.get(stmt), table.getAliasName()), table); 752 } else { 753 String sql = stmt.toString(); 754 if(sql == null){ 755 System.err.println("Current statement toString() returns null."); 756 } 757 else { 758 String hash = SHA256.getMd5(sql); 759 tableHashMap.put(stmt, hash); 760 tableAliasMap.put(aliasKey(hash, table.getAliasName()), table); 761 } 762 } 763 } 764 } 765 766 /** 767 * The model bound to {@code gspModel} itself, never one found by its name: a relation such as a LATERAL VIEW 768 * must not take the model of a table that has its name ({@link #getModel} falls back to the name of a TTable). 769 */ 770 public Object getBoundModel(Object gspModel) { 771 return gspModel == null ? null : modelBindingMap.get(gspModel); 772 } 773 774 public Object getModel(Object gspModel) { 775 if (gspModel == null) { 776 return null; 777 } 778 779 if (gspModel instanceof TTable && !(gspModel instanceof TCTE)) { 780 TTable table = (TTable) gspModel; 781 if (table.getCTE() != null) { 782 Object result = modelBindingMap.get(table.getCTE()); 783 if (result != null) { 784 return result; 785 } 786 } 787 if (table.getAliasClause() != null && table.getAliasClause().getColumns() != null) { 788 Object result = modelBindingMap.get(table.getAliasClause().getColumns()); 789 if (result != null) { 790 return result; 791 } 792 } 793 if (table.getSubquery() != null && !table.getSubquery().isCombinedQuery()) { 794 Object result = modelBindingMap.get(table.getSubquery().getResultColumnList()); 795 if (result != null) { 796 return result; 797 } 798 } 799 } 800 if (gspModel instanceof TSelectSqlStatement) { 801 TSelectSqlStatement select = (TSelectSqlStatement) gspModel; 802 if (!select.isCombinedQuery()) { 803 if (select.getResultColumnList() != null) { 804 Object result = modelBindingMap.get(select.getResultColumnList()); 805 if (result != null) { 806 return result; 807 } 808 } 809 else if (select.getTransformClause() != null) { 810 Object result = modelBindingMap.get(select.getTransformClause()); 811 if (result != null) { 812 return result; 813 } 814 } 815 } 816 } 817 Object result = modelBindingMap.get(gspModel); 818 if (result == null) { 819 result = createModelBindingMap.get(gspModel); 820 } 821 if (result == null) { 822 result = insertModelBindingMap.get(gspModel); 823 } 824 if (result == null) { 825 result = updateModelBindingMap.get(gspModel); 826 } 827 if (result == null) { 828 result = mergeModelBindingMap.get(gspModel); 829 } 830 if (result == null) { 831 result = viewModelBindingMap.get(gspModel); 832 } 833 if (result == null) { 834 result = cursorModelBindingMap.get(gspModel); 835 } 836 837 if (result == null && gspModel instanceof TTable) { 838 result = getCreateTable((TTable) gspModel); 839 } 840 841 if (result == null && gspModel instanceof TTable) { 842 result = (Table) getCreateModel((TTable) gspModel); 843 } 844 845 if (result == null && gspModel instanceof TTable) { 846 TTable table = (TTable) gspModel; 847 if (table.getLinkTable() != null) { 848 result = (Table) getTableByName( 849 DlineageUtil.getTableIdentityKey(table.getLinkTable().getTableName().toString())); 850 if (result == null) { 851 result = (Table) getTableByName( 852 DlineageUtil.getTableIdentityKeyWithDefaultSchema(table.getLinkTable().getTableName().toString())); 853 } 854 } else { 855 result = (Table) getTableByName( 856 DlineageUtil.getTableIdentityKey(((TTable) gspModel).getTableName().toString())); 857 if (result == null) { 858 result = (Table) getTableByName( 859 DlineageUtil.getTableIdentityKeyWithDefaultSchema(((TTable) gspModel).getTableName().toString())); 860 } 861 } 862 TObjectName written = table.getLinkTable() != null ? table.getLinkTable().getTableName() : table.getTableName(); 863 // A table no FROM item or DML target declared (the qualifier of rec.field) names a local first; a FROM or 864 // DML relation (FROM @t) is the table path's, else a local relation. 865 boolean implicit = table.getEffectType() == null 866 || table.getEffectType() == ETableEffectType.tetImplicitLateralDerivedTable; 867 result = resolveMixedRelation(written == null ? null : written.toString(), 868 result instanceof Table ? (Table) result : null, implicit); 869 } 870 871 if (result == null && gspModel instanceof TTable) { 872 TTable table = (TTable) gspModel; 873 if(table.getFuncCall()!=null) { 874 result = getModel(table.getFuncCall()); 875 } 876 } 877 878 if (result == null && gspModel instanceof TTable) { 879 TTable table = (TTable) gspModel; 880 Table tableModel = new Table(table); 881 result = (Table) getTableByName(tableModel.getBindingKey()); 882 } 883 884 if (gspModel instanceof TTable && result instanceof Table) { 885 return visibleTemporaryModel((TTable) gspModel, (Table) result); 886 } 887 return result; 888 } 889 890 /** The named routine whose body holds a routine statement; an anonymous block is part of the one around it. */ 891 private static TStoredProcedureSqlStatement namedRoutine(TStoredProcedureSqlStatement routine) { 892 for (TCustomSqlStatement current = routine; current != null; current = current.getParentStmt()) { 893 if (current instanceof TStoredProcedureSqlStatement 894 && ((TStoredProcedureSqlStatement) current).getStoredProcedureName() != null) { 895 return (TStoredProcedureSqlStatement) current; 896 } 897 } 898 return routine; 899 } 900 901 /** Name-based caches must not override the metadata captured at a temporary reference. */ 902 Table visibleTemporaryModel(TTable reference, Table candidate) { 903 if (candidate == null || !reference.isResolved() 904 || !(reference.getSqlEnv() instanceof ProcedureEnvironment)) return candidate; 905 TTable origin = candidate.getTableObject(); 906 if (origin == reference) return candidate; 907 if (origin != null && reference.getResolvedTable() == null && origin.getResolvedTable() != null 908 && getGlobalProcedure() != null 909 && namedRoutine(temporaryMetadataOwners.get(origin.getResolvedTable())) == namedRoutine(getGlobalProcedure()) 910 && gudusoft.gsqlparser.sqlenv.TemporaryTableSemantics.ofName(reference.dbvendor, reference.getTableName()) 911 == gudusoft.gsqlparser.sqlenv.TemporaryTableSemantics.Scope.SESSION) { 912 // This routine created the session table in SQL resolver2 never saw (EXECUTE of a 913 // string). It outlives that statement, so the routine's later reference is to it. 914 return candidate; 915 } 916 if (origin == null || origin.getResolvedTable() != reference.getResolvedTable()) return null; 917 if (reference.getResolvedTable() == null && origin.getSqlEnv() != reference.getSqlEnv()) return null; 918 return candidate; 919 } 920 921 public List<Procedure> getProcedureModels(){ 922 List<Procedure> procedures = new ArrayList<Procedure>(); 923 for (Object obj : modelBindingMap.values()) { 924 if(obj instanceof Procedure && !procedures.contains(obj)){ 925 procedures.add((Procedure)obj); 926 } 927 } 928 return procedures; 929 } 930 931 public List<OraclePackage> getOraclePackageModels(){ 932 List<OraclePackage> oraclePackages = new ArrayList<OraclePackage>(); 933 for (Object obj : modelBindingMap.values()) { 934 if(obj instanceof OraclePackage && !oraclePackages.contains(obj)){ 935 oraclePackages.add((OraclePackage)obj); 936 } 937 } 938 return oraclePackages; 939 } 940 941 public List<Process> getProcessModels(){ 942 List<Process> processes = new ArrayList<Process>(); 943 for (Object obj : processModelBindingMap.values()) { 944 if(obj instanceof Process && !processes.contains(obj)){ 945 processes.add((Process)obj); 946 } 947 } 948 return processes; 949 } 950 951 public void bindProcessModel(TParseTreeNode gspModel, Object relationModel) { 952 processModelBindingMap.put(gspModel, relationModel); 953 } 954 955 public void unbindProcessModel(TParseTreeNode gspModel) { 956 processModelBindingMap.remove(gspModel); 957 } 958 959 public Process getProcessModel(TParseTreeNode gspModel) { 960 return (Process)processModelBindingMap.get(gspModel); 961 } 962 963 public void bindViewModel(Object gspModel, Object relationModel) { 964 viewModelBindingMap.put(gspModel, relationModel); 965 } 966 967 public Object getViewModel(Object gspModel) { 968 return viewModelBindingMap.get(gspModel); 969 } 970 971 public void bindUpdateModel(Object gspModel, Object relationModel) { 972 updateModelBindingMap.put(gspModel, relationModel); 973 } 974 975 public Object getUpdateModel(Object gspModel) { 976 return updateModelBindingMap.get(gspModel); 977 } 978 979 public void bindMergeModel(Object gspModel, Object relationModel) { 980 mergeModelBindingMap.put(gspModel, relationModel); 981 } 982 983 public Object getMergeModel(Object gspModel) { 984 return mergeModelBindingMap.get(gspModel); 985 } 986 987 public void bindInsertModel(Object gspModel, Object relationModel) { 988 insertModelBindingMap.put(gspModel, relationModel); 989 } 990 991 public Object getInsertModel(Object gspModel) { 992 return insertModelBindingMap.get(gspModel); 993 } 994 995 /** Adds one of a routine's function results under the routine's name as written. */ 996 public void bindFunctionResult(RoutineAliasKey key, Object functionTable) { 997 if (key == null) return; 998 noteRoutineService(key); 999 Set<Object> tableSet = functionTableAliases.get(key); 1000 if (tableSet == null) { 1001 tableSet = new LinkedHashSet<Object>(); 1002 functionTableAliases.put(key, tableSet); 1003 } 1004 tableSet.add(functionTable); 1005 } 1006 1007 /** A routine's function results, by its name as written (the exact name, else the other shape's routine level). */ 1008 public Set<Object> getFunctionResults(RoutineAliasKey key) { 1009 return functionTableAliases.lookupName(key); 1010 } 1011 1012 /** 1013 * Adds a function result under a key of the deprecated text-keyed API: the key as given (any object), and, for text, 1014 * also the routine name the text reads as, so the analysis's own lookups see it. 1015 * 1016 * @deprecated function results are keyed by the routine's name as written: use 1017 * {@link #bindFunctionResult(RoutineAliasKey, Object)} 1018 */ 1019 @Deprecated 1020 public void bindTableFunction(Object gspModel, Object functionTable) { 1021 Set<Object> tableSet = legacyFunctionTables.get(gspModel); 1022 if (tableSet == null) { 1023 tableSet = new LinkedHashSet<Object>(); 1024 legacyFunctionTables.put(gspModel, tableSet); 1025 } 1026 tableSet.add(functionTable); 1027 if (gspModel instanceof String) { 1028 bindFunctionResult(legacyTextKey((String) gspModel), functionTable); 1029 } 1030 } 1031 1032 /** 1033 * The function results under a key of the deprecated text-keyed API, looked up as the legacy map was (the key as 1034 * given; for text, a qualified key's last segment against an unqualified one, as stored names); then, for text, 1035 * under the routine name the text reads as. 1036 * 1037 * @deprecated use {@link #getFunctionResults(RoutineAliasKey)} 1038 */ 1039 @Deprecated 1040 public Set<Object> getFunctionTable(Object gspModel) { 1041 Set<Object> result = legacyFunctionTables.get(gspModel); 1042 if (result == null && gspModel instanceof String) { 1043 String modelString = (String) gspModel; 1044 for (Map.Entry<Object, Set<Object>> entry : legacyFunctionTables.entrySet()) { 1045 if (!(entry.getKey() instanceof String)) { 1046 continue; 1047 } 1048 String keyString = (String) entry.getKey(); 1049 if (keyString.indexOf(".") != -1 && modelString.indexOf(".") == -1 1050 && sameStoredBindingName(ESQLDataObjectType.dotTable, 1051 keyString.substring(keyString.lastIndexOf(".") + 1), modelString)) { 1052 return entry.getValue(); 1053 } 1054 if (keyString.indexOf(".") == -1 && modelString.indexOf(".") != -1 1055 && sameStoredBindingName(ESQLDataObjectType.dotTable, keyString, 1056 modelString.substring(modelString.lastIndexOf(".") + 1))) { 1057 return entry.getValue(); 1058 } 1059 } 1060 result = getFunctionResults(legacyTextKey(modelString)); 1061 } 1062 return result; 1063 } 1064 1065 /** Stored-name equality under the analysis's rules, for the deprecated text-keyed API's legacy lookups. */ 1066 private boolean sameStoredBindingName(ESQLDataObjectType type, String a, String b) { 1067 gudusoft.gsqlparser.sqlenv.IdentifierService service = getGlobalIdentifierService(); 1068 if (service == null) return a.equalsIgnoreCase(b); // non-identifier-compare: no vendor or configuration is known on this thread, so no identifier rules apply; the legacy maps' last-resort fallback 1069 return service.canonKeyOf(a, type, gudusoft.gsqlparser.sqlenv.IdentifierInputForm.CATALOG_STORED) 1070 .equals(service.canonKeyOf(b, type, gudusoft.gsqlparser.sqlenv.IdentifierInputForm.CATALOG_STORED)); 1071 } 1072 1073 public Table getCreateTable(TTable table) { 1074 Table scoped = getTemporaryMetadataModel(table); 1075 if (scoped != null && temporaryMetadataOwners.get(table.getResolvedTable()) != getGlobalProcedure()) return scoped; 1076 if (table != null && table.getFullName() != null) { 1077 return visibleTemporaryModel(table, (Table) createModelQuickBindingMap.get( 1078 DlineageUtil.getTableIdentityKey(table.getTableName().toString()))); 1079 } 1080 if(modelBindingMap.get(table) instanceof Table) { 1081 return (Table)modelBindingMap.get(table); 1082 } 1083 return null; 1084 } 1085 1086 public Table getCreateModel(TTable table) { 1087 return (Table) createModelBindingMap.get(table); 1088 } 1089 1090 Table getTemporaryMetadataModel(TTable table) { 1091 return isScopedTemporary(table) ? temporaryMetadataModels.get(table.getResolvedTable()) : null; 1092 } 1093 1094 /** 1095 * Whether a table reference names one routine body's temporary table rather than a shared 1096 * one: its name says so ({@code #t}), or resolver2 bound it to a table that a routine 1097 * declared temporary ({@code CREATE TEMP TABLE t}). See 1098 * {@link gudusoft.gsqlparser.sqlenv.TemporaryTableSemantics}. 1099 */ 1100 public static boolean isScopedTemporary(TTable table) { 1101 return table != null && table.getTableName() != null 1102 && (table.getSqlEnv() instanceof ProcedureEnvironment 1103 || SQLUtil.isLocalTemporaryTable(table.dbvendor, table.getTableName())); 1104 } 1105 1106 public void bindCreateModel(TTable table, Table tableModel) { 1107 createModelBindingMap.put(table, tableModel); 1108 Object key = DlineageUtil.getTableIdentityKey(table.getTableName().toString()); 1109 if ((getGlobalProcedure() != null && isScopedTemporary(table)) || !createModelQuickBindingMap.containsKey(key)) { 1110 displaceBinding(createModelQuickBindingMap, 1, key, tableModel); 1111 createModelQuickBindingMap.put(key, tableModel); 1112 } 1113 } 1114 1115 public void bindCreateModel(TObjectName tableName, Table tableModel) { 1116 if (!createModelQuickBindingMap.containsKey(DlineageUtil.getTableIdentityKey(tableName.toString()))) { 1117 createModelQuickBindingMap.put(DlineageUtil.getTableIdentityKey(tableName.toString()), tableModel); 1118 } 1119 } 1120 1121 public void bindCreateModel(String tableName, Table tableModel) { 1122 if (!createModelQuickBindingMap.containsKey(DlineageUtil.getTableIdentityKey(tableName))) { 1123 createModelQuickBindingMap.put(DlineageUtil.getTableIdentityKey(tableName), tableModel); 1124 } 1125 } 1126 1127 public TObjectName[] getTableColumns(TTable table) { 1128 Table createTable = getCreateTable(table); 1129 if (createTable != null) { 1130 List<TableColumn> columnList = createTable.getColumns(); 1131 TObjectName[] columns = new TObjectName[columnList.size()]; 1132 for (int i = 0; i < columns.length; i++) { 1133 columns[i] = columnList.get(i).getColumnObject(); 1134 } 1135 Arrays.sort(columns, new Comparator<TObjectName>() { 1136 1137 @Override 1138 public int compare(TObjectName o1, TObjectName o2) { 1139 if (o1 == null) { 1140 return -1; 1141 } 1142 if (o2 == null) { 1143 return 1; 1144 } 1145 return o1.getStartToken().posinlist - o2.getStartToken().posinlist; 1146 } 1147 }); 1148 return columns; 1149 } 1150 else if(modelBindingMap.get(table) instanceof QueryTable) { 1151 QueryTable queryTable = (QueryTable)modelBindingMap.get(table); 1152 List<TObjectName> columns = new ArrayList<TObjectName>(); 1153 for (ResultColumn resultColumn : queryTable.getColumns()) { 1154 if(resultColumn.hasStarLinkColumn()) { 1155 for(TObjectName column: resultColumn.getStarLinkColumnList()) { 1156 columns.add(column); 1157 } 1158 } 1159 else if (resultColumn.getColumnObject() instanceof TResultColumn) { 1160 TResultColumn columnObject = ((TResultColumn) resultColumn.getColumnObject()); 1161 TAliasClause alias = columnObject.getAliasClause(); 1162 if (alias != null && alias.getAliasName() != null) { 1163 if(resultColumn.isStruct() && resultColumn.isStarColumn()) { 1164 TObjectName structColumn = DlineageUtil.syntheticStarName(); 1165 columns.add(structColumn); 1166 } 1167 else { 1168 columns.add(alias.getAliasName()); 1169 } 1170 } else { 1171 if (columnObject.getFieldAttr() != null) { 1172 columns.add(columnObject.getFieldAttr()); 1173 } else { 1174 TObjectName column = new TObjectName(); 1175 if (columnObject.getExpr().getExpressionType() == EExpressionType.typecast_t) { 1176 column.setString(columnObject.getExpr().getLeftOperand().toString()); 1177 } else { 1178 column.setString(columnObject.toString()); 1179 } 1180 columns.add(column); 1181 } 1182 } 1183 } else if (resultColumn.getColumnObject() instanceof TObjectName) { 1184 columns.add((TObjectName) resultColumn.getColumnObject()); 1185 } 1186 } 1187 return columns.toArray(new TObjectName[0]); 1188 } 1189 1190 TObjectNameList list = table.getLinkedColumns(); 1191 List<TObjectName> columns = new ArrayList<TObjectName>(); 1192 1193 if (table.getCTE() != null) { 1194 ResultSet resultSet = (ResultSet) getModel(table.getCTE()); 1195 if (resultSet != null) { 1196 List<ResultColumn> columnList = resultSet.getColumns(); 1197 for (int i = 0; i < columnList.size(); i++) { 1198 ResultColumn resultColumn = columnList.get(i); 1199 if (resultColumn.getColumnObject() instanceof TResultColumn) { 1200 TResultColumn columnObject = ((TResultColumn) resultColumn.getColumnObject()); 1201 TAliasClause alias = columnObject.getAliasClause(); 1202 if (alias != null && alias.getAliasName() != null) { 1203 columns.add(alias.getAliasName()); 1204 } else { 1205 if (columnObject.getFieldAttr() != null) { 1206 columns.add(columnObject.getFieldAttr()); 1207 } else { 1208 continue; 1209 } 1210 } 1211 } else if (resultColumn.getColumnObject() instanceof TObjectName) { 1212 columns.add((TObjectName) resultColumn.getColumnObject()); 1213 } 1214 } 1215 } 1216 } else if (list.size() == 1 && DlineageUtil.isStarName(list.getObjectName(0)) && table.getSubquery() != null) { 1217 addSubqueryColumns(table, columns); 1218 } else { 1219 for (int i = 0; i < list.size(); i++) { 1220 TObjectName object = list.getObjectName(i); 1221 if (DlineageUtil.isStarName(object) && table.getSubquery() != null) { 1222 addSubqueryColumns(table, columns); 1223 } 1224 else 1225 columns.add(object); 1226 } 1227 } 1228 Collections.sort(columns, new Comparator<TObjectName>() { 1229 1230 @Override 1231 public int compare(TObjectName o1, TObjectName o2) { 1232 return o1.getStartToken().posinlist - o2.getStartToken().posinlist; 1233 } 1234 }); 1235 return columns.toArray(new TObjectName[0]); 1236 } 1237 1238 private void addSubqueryColumns(TTable table, List<TObjectName> columns) { 1239 ResultSet resultSet = (ResultSet) getModel(table.getSubquery()); 1240 if (resultSet != null) { 1241 List<ResultColumn> columnList = resultSet.getColumns(); 1242 for (int i = 0; i < columnList.size(); i++) { 1243 ResultColumn resultColumn = columnList.get(i); 1244 if (resultColumn.getColumnObject() instanceof TResultColumn) { 1245 TResultColumn columnObject = ((TResultColumn) resultColumn.getColumnObject()); 1246 TAliasClause alias = columnObject.getAliasClause(); 1247 if (alias != null && alias.getAliasName() != null) { 1248 columns.add(alias.getAliasName()); 1249 } else { 1250 if (columnObject.getFieldAttr() != null) { 1251 columns.add(columnObject.getFieldAttr()); 1252 } else { 1253 TObjectName column = new TObjectName(); 1254 if (columnObject.getExpr().getExpressionType() == EExpressionType.typecast_t) { 1255 column.setString(columnObject.getExpr().getLeftOperand().toString()); 1256 } else { 1257 column.setString(columnObject.toString()); 1258 } 1259 columns.add(column); 1260 } 1261 } 1262 } else if (resultColumn.getColumnObject() instanceof TObjectName) { 1263 columns.add((TObjectName) resultColumn.getColumnObject()); 1264 } 1265 } 1266 } 1267 } 1268 1269 public TTable getTableFromColumn(TObjectName column) { 1270 if (column.getSourceTable() != null) { 1271 return column.getSourceTable(); 1272 } 1273 1274 if (column.getTableString() != null && column.getTableString().trim().length() > 0) { 1275 TCustomSqlStatement peekStmt = ModelBindingManager.getGlobalStmtStack().peek(); 1276 while (peekStmt != null) { 1277 TTable table = null; 1278 if (tableHashMap.containsKey(peekStmt)) { 1279 table = tableAliasMap.get(aliasKey(tableHashMap.get(peekStmt), column.getTableString())); 1280 } 1281 if (table != null) { 1282 return table; 1283 } else { 1284 peekStmt = peekStmt.getParentStmt(); 1285 } 1286 } 1287 } 1288 return null; 1289 } 1290 1291 public TTable getTable(TCustomSqlStatement stmt, TObjectName column) { 1292 if (column.getSourceTable() != null) { 1293 return column.getSourceTable(); 1294 } 1295 1296 if (column.getTableString() != null && column.getTableString().trim().length() > 0) { 1297 TCustomSqlStatement peekStmt = ModelBindingManager.getGlobalStmtStack().peek(); 1298 while (peekStmt != null) { 1299 TTable table = null; 1300 if (tableHashMap.containsKey(peekStmt)) { 1301 table = tableAliasMap.get(aliasKey(tableHashMap.get(peekStmt), column.getTableString())); 1302 } 1303 if (table != null && table.getSubquery() != stmt) 1304 return table; 1305 1306 if (peekStmt.getTables() != null) { 1307 for (TTable tableItem : peekStmt.getTables()) { 1308 if (DlineageUtil.sameQualifiedName(ESQLDataObjectType.dotTable, tableItem.getTableName().toString(), 1309 column.getTableString())) { 1310 return tableItem; 1311 } 1312 } 1313 } 1314 1315 peekStmt = peekStmt.getParentStmt(); 1316 } 1317 } 1318 1319 if (stmt.getTables() != null) { 1320 for (int j = 0; j < stmt.getTables().size(); j++) { 1321 TTable table = (TTable) stmt.getTables().getTable(j); 1322 if (table.getSubquery() == stmt) 1323 continue; 1324 1325 TObjectName[] columns = getTableColumns(table); 1326 for (int i = 0; i < columns.length; i++) { 1327 TObjectName columnName = columns[i]; 1328 if (columnName == null || DlineageUtil.isWildcard(columnName)) 1329 continue; 1330 if (DlineageUtil.sameQualifiedName(ESQLDataObjectType.dotColumn, columnName.toString(), column.toString())) { 1331 if (columnName.getSourceTable() == null || columnName.getSourceTable() == table) { 1332 return decided(SqllrObservations.DECISION_FIRST_TABLE_WITH_COLUMN, column, table); 1333 } 1334 } 1335 if (!SQLUtil.isEmpty(columnName.getColumnNameOnly()) 1336 && DlineageUtil.sameName(ESQLDataObjectType.dotColumn, columnName.getColumnNameOnly(), 1337 column.getColumnNameOnly())) { 1338 if (columnName.getSourceTable() == null || columnName.getSourceTable() == table) { 1339 return decided(SqllrObservations.DECISION_FIRST_TABLE_WITH_COLUMN, column, table); 1340 } 1341 } 1342 } 1343 } 1344 } 1345 1346 return null; 1347 } 1348 1349 public TTable guessTable(TCustomSqlStatement stmt, TObjectName column) { 1350 if (column.getTableString() != null && column.getTableString().trim().length() > 0) { 1351 TCustomSqlStatement peekStmt = ModelBindingManager.getGlobalStmtStack().peek(); 1352 TTable table = null; 1353 if (tableHashMap.containsKey(peekStmt)) { 1354 table = tableAliasMap.get(aliasKey(tableHashMap.get(peekStmt), column.getTableString())); 1355 } 1356 if (table != null && table.getSubquery() != stmt) 1357 return table; 1358 } 1359 1360 Iterator iter = tableSet.iterator(); 1361 while (iter.hasNext()) { 1362 TTable table = (TTable) iter.next(); 1363 1364 if (table.getSubquery() == stmt) 1365 continue; 1366 1367 TObjectName[] columns = getTableColumns(table); 1368 for (int i = 0; i < columns.length; i++) { 1369 TObjectName columnName = columns[i]; 1370 if (DlineageUtil.isWildcard(columnName)) 1371 continue; 1372 if (DlineageUtil.sameQualifiedName(ESQLDataObjectType.dotColumn, columnName.toString(), column.toString())) { 1373 if (columnName.getSourceTable() == null || columnName.getSourceTable() == table) { 1374 return decided(SqllrObservations.DECISION_GUESS_TABLE, column, table); 1375 } 1376 } 1377 } 1378 } 1379 return null; 1380 } 1381 1382 /** Records a fallback binding for the SQL-LR producer's snapshot (observe-only) and returns the table. */ 1383 private static TTable decided(String id, TObjectName column, TTable table) { 1384 SqllrObservations observations = SqllrObservations.current(); 1385 if (observations != null) { 1386 observations.decision(id, column, table, "a column-name scan over the FROM items"); 1387 } 1388 return table; 1389 } 1390 1391 public List<Table> getTablesByName() { 1392 // A table bound by identity is also bound under its legacy name; list it once, at that 1393 // name, as before. Only a table whose legacy name now belongs to another table (two 1394 // tables the analysis keeps apart but the vendor-default fold merges) is listed by its 1395 // canonical key. 1396 Set<Object> alsoBoundByName = Collections.newSetFromMap(new IdentityHashMap<Object, Boolean>()); 1397 for (Object entry : tableNamesMap.entrySet()) { 1398 Map.Entry binding = (Map.Entry) entry; 1399 if (!(binding.getKey() instanceof QualifiedCanonKey)) { 1400 alsoBoundByName.add(binding.getValue()); 1401 } 1402 } 1403 List<Table> tables = new ArrayList<Table>(); 1404 for (Object entry : tableNamesMap.entrySet()) { 1405 Map.Entry binding = (Map.Entry) entry; 1406 if (binding.getKey() instanceof QualifiedCanonKey && getGlobalVendor() != EDbVendor.dbvbigquery 1407 && alsoBoundByName.contains(binding.getValue())) { 1408 continue; 1409 } 1410 tables.add((Table) binding.getValue()); 1411 } 1412 return tables; 1413 } 1414 1415 public List<TTable> getBaseTables() { 1416 List<TTable> tables = new ArrayList<TTable>(); 1417 1418 Iterator iter = modelBindingMap.keySet().iterator(); 1419 while (iter.hasNext()) { 1420 Object key = iter.next(); 1421 if (!(key instanceof TTable)) { 1422 continue; 1423 } 1424 TTable table = (TTable) key; 1425 if (table.getSubquery() == null) { 1426 tables.add(table); 1427 } 1428 } 1429 1430 iter = createModelBindingMap.keySet().iterator(); 1431 while (iter.hasNext()) { 1432 Object key = iter.next(); 1433 if (!(key instanceof TTable)) { 1434 continue; 1435 } 1436 TTable table = (TTable) key; 1437 tables.add(table); 1438 } 1439 1440 iter = insertModelBindingMap.keySet().iterator(); 1441 while (iter.hasNext()) { 1442 Object key = iter.next(); 1443 if (!(key instanceof TTable)) { 1444 continue; 1445 } 1446 TTable table = (TTable) key; 1447 tables.add(table); 1448 } 1449 1450 iter = mergeModelBindingMap.keySet().iterator(); 1451 while (iter.hasNext()) { 1452 Object key = iter.next(); 1453 if (!(key instanceof TTable)) { 1454 continue; 1455 } 1456 TTable table = (TTable) key; 1457 tables.add(table); 1458 } 1459 1460 iter = updateModelBindingMap.keySet().iterator(); 1461 while (iter.hasNext()) { 1462 Object key = iter.next(); 1463 if (!(key instanceof TTable)) { 1464 continue; 1465 } 1466 TTable table = (TTable) key; 1467 if (!tables.contains(table)) { 1468 tables.add(table); 1469 } 1470 } 1471 1472 return tables; 1473 } 1474 1475 public List<TCustomSqlStatement> getViews() { 1476 List<TCustomSqlStatement> views = new ArrayList<TCustomSqlStatement>(); 1477 1478 Iterator iter = viewModelBindingMap.keySet().iterator(); 1479 while (iter.hasNext()) { 1480 Object key = iter.next(); 1481 if (!(key instanceof TCreateViewSqlStatement) && !(key instanceof TCreateMaterializedSqlStatement)) { 1482 continue; 1483 } 1484 TCustomSqlStatement view = (TCustomSqlStatement) key; 1485 if (!views.contains(view)) { 1486 views.add(view); 1487 } 1488 } 1489 return views; 1490 } 1491 1492 public List<TResultColumnList> getSelectResultSets() { 1493 List<TResultColumnList> resultSets = new ArrayList<TResultColumnList>(); 1494 1495 Iterator iter = modelBindingMap.keySet().iterator(); 1496 while (iter.hasNext()) { 1497 Object key = iter.next(); 1498 if (!(key instanceof TResultColumnList)) { 1499 continue; 1500 } 1501 TResultColumnList resultset = (TResultColumnList) key; 1502 resultSets.add(resultset); 1503 } 1504 return resultSets; 1505 } 1506 1507 public List<TObjectNameList> getQueryAliasTables() { 1508 List<TObjectNameList> resultSets = new ArrayList<TObjectNameList>(); 1509 1510 Iterator iter = modelBindingMap.keySet().iterator(); 1511 while (iter.hasNext()) { 1512 Object key = iter.next(); 1513 if (!(key instanceof TObjectNameList)) { 1514 continue; 1515 } 1516 TObjectNameList resultset = (TObjectNameList) key; 1517 resultSets.add(resultset); 1518 } 1519 return resultSets; 1520 } 1521 1522 public List<TSelectSqlStatement> getSelectSetResultSets() { 1523 List<TSelectSqlStatement> resultSets = new ArrayList<TSelectSqlStatement>(); 1524 1525 Iterator iter = modelBindingMap.keySet().iterator(); 1526 while (iter.hasNext()) { 1527 Object key = iter.next(); 1528 if (!(key instanceof TSelectSqlStatement)) { 1529 continue; 1530 } 1531 1532 TSelectSqlStatement stmt = (TSelectSqlStatement) key; 1533 if (stmt.getSetOperatorType() == ESetOperatorType.none) 1534 continue; 1535 1536 resultSets.add(stmt); 1537 } 1538 return resultSets; 1539 } 1540 1541 public List<TCTE> getCTEs() { 1542 List<TCTE> resultSets = new ArrayList<TCTE>(); 1543 1544 Iterator iter = modelBindingMap.keySet().iterator(); 1545 while (iter.hasNext()) { 1546 Object key = iter.next(); 1547 if (!(key instanceof TCTE)) { 1548 continue; 1549 } 1550 1551 TCTE cte = (TCTE) key; 1552 resultSets.add(cte); 1553 } 1554 return resultSets; 1555 } 1556 1557 public List<TTable> getTableWithSelectSetResultSets() { 1558 List<TTable> resultSets = new ArrayList<TTable>(); 1559 1560 Iterator iter = modelBindingMap.keySet().iterator(); 1561 while (iter.hasNext()) { 1562 Object key = iter.next(); 1563 if (!(key instanceof TTable)) { 1564 continue; 1565 } 1566 1567 if (((TTable) key).getSubquery() == null) 1568 continue; 1569 TSelectSqlStatement stmt = ((TTable) key).getSubquery(); 1570 if (stmt.getSetOperatorType() == ESetOperatorType.none) 1571 continue; 1572 1573 resultSets.add((TTable) key); 1574 } 1575 return resultSets; 1576 } 1577 1578 public List<TParseTreeNode> getMergeResultSets() { 1579 List<TParseTreeNode> resultSets = new ArrayList<TParseTreeNode>(); 1580 1581 Iterator iter = modelBindingMap.keySet().iterator(); 1582 while (iter.hasNext()) { 1583 Object key = iter.next(); 1584 if (!(key instanceof TMergeUpdateClause) && !(key instanceof TMergeInsertClause)) { 1585 continue; 1586 } 1587 TParseTreeNode resultset = (TParseTreeNode) key; 1588 resultSets.add(resultset); 1589 } 1590 return resultSets; 1591 } 1592 1593 public List<TParseTreeNode> getOutputResultSets() { 1594 List<TParseTreeNode> resultSets = new ArrayList<TParseTreeNode>(); 1595 1596 Iterator iter = modelBindingMap.keySet().iterator(); 1597 while (iter.hasNext()) { 1598 Object key = iter.next(); 1599 if (!(key instanceof TOutputClause)) { 1600 continue; 1601 } 1602 TParseTreeNode resultset = (TParseTreeNode) key; 1603 resultSets.add(resultset); 1604 } 1605 return resultSets; 1606 } 1607 1608 public List<TParseTreeNode> getUpdateResultSets() { 1609 List<TParseTreeNode> resultSets = new ArrayList<TParseTreeNode>(); 1610 1611 Iterator iter = modelBindingMap.keySet().iterator(); 1612 while (iter.hasNext()) { 1613 Object key = iter.next(); 1614 if (!(key instanceof TUpdateSqlStatement)) { 1615 continue; 1616 } 1617 TParseTreeNode resultset = (TParseTreeNode) key; 1618 resultSets.add(resultset); 1619 } 1620 return resultSets; 1621 } 1622 1623 public List<TParseTreeNode> getFunctoinCalls() { 1624 List<TParseTreeNode> resultSets = new ArrayList<TParseTreeNode>(); 1625 1626 Iterator iter = modelBindingMap.keySet().iterator(); 1627 while (iter.hasNext()) { 1628 Object key = iter.next(); 1629 if (!(key instanceof TFunctionCall) && !(key instanceof TCaseExpression)) { 1630 continue; 1631 } 1632 TParseTreeNode resultset = (TParseTreeNode) key; 1633 resultSets.add(resultset); 1634 } 1635 return resultSets; 1636 } 1637 1638 public List<TParseTreeNode> getAliases() { 1639 List<TParseTreeNode> resultSets = new ArrayList<TParseTreeNode>(); 1640 1641 Iterator iter = modelBindingMap.keySet().iterator(); 1642 while (iter.hasNext()) { 1643 Object key = iter.next(); 1644 if (!(key instanceof TAliasClause)) { 1645 continue; 1646 } 1647 TParseTreeNode resultset = (TParseTreeNode) key; 1648 resultSets.add(resultset); 1649 } 1650 return resultSets; 1651 } 1652 1653 public List<TParseTreeNode> getCursors() { 1654 List<TParseTreeNode> resultSets = new ArrayList<TParseTreeNode>(); 1655 1656 Iterator iter = modelBindingMap.keySet().iterator(); 1657 while (iter.hasNext()) { 1658 Object key = iter.next(); 1659 if (!(key instanceof TCursorDeclStmt) && !(key instanceof TOpenforStmt)) { 1660 continue; 1661 } 1662 TParseTreeNode resultset = (TParseTreeNode) key; 1663 resultSets.add(resultset); 1664 } 1665 return resultSets; 1666 } 1667 1668 public List<TParseTreeNode> getPivotdTables() { 1669 List<TParseTreeNode> resultSets = new ArrayList<TParseTreeNode>(); 1670 1671 Iterator iter = modelBindingMap.keySet().iterator(); 1672 while (iter.hasNext()) { 1673 Object key = iter.next(); 1674 if (!(key instanceof TPivotClause)) { 1675 continue; 1676 } 1677 TParseTreeNode resultset = (TParseTreeNode) key; 1678 resultSets.add(resultset); 1679 } 1680 return resultSets; 1681 } 1682 1683 public void addRelation(Relationship relation) { 1684 if (relation != null && !relationHolder.contains(relation)) { 1685 relationHolder.add(relation); 1686 } 1687 } 1688 1689 public void removeRelation(ImpactRelationship relation) { 1690 if (relation != null) { 1691 relationHolder.remove(relation); 1692 } 1693 } 1694 1695 public Relationship[] getRelations() { 1696 return relationHolder.toArray(new Relationship[0]); 1697 } 1698 1699 public void reset() { 1700 currentStatementOrdinal = -1; 1701 commentOccurrenceBindings = null; 1702 modelBindingMap.clear(); 1703 processModelBindingMap.clear(); 1704 viewModelBindingMap.clear(); 1705 insertModelBindingMap.clear(); 1706 createModelBindingMap.clear(); 1707 createModelQuickBindingMap.clear(); 1708 mergeModelBindingMap.clear(); 1709 updateModelBindingMap.clear(); 1710 cursorModelBindingMap.clear(); 1711 tableNamesMap.clear(); 1712 temporaryScopes.clear(); 1713 identityBoundTables.clear(); 1714 setContestedNormalNames(null, null); 1715 legacyNormalNames = false; 1716 dropTables.clear(); 1717 routineModels.clear(); 1718 routineShadowState = null; 1719 procedureAliases.clear(); 1720 packageAliases.clear(); 1721 functionTableAliases.clear(); 1722 legacyProcedureNames.clear(); 1723 legacyPackageNames.clear(); 1724 legacyFunctionTables.clear(); 1725 routineService = null; 1726 localSymbols.clear(); 1727 localCursorModels.clear(); 1728 poolStack.clear(); 1729 relationHolder.clear(); 1730 tableAliasMap.clear(); 1731 tableHashMap.clear(); 1732 topStmtHashMap.clear(); 1733 hashSQLMap.clear(); 1734 dynamicSQLMap.clear(); 1735 tableSet.clear(); 1736 resultSetSet.clear(); 1737 dblinkTableSet.clear(); 1738 objectTypeIndex.clear(); 1739 collectionToElementTypeIndex.clear(); 1740 pipelinedSignatures.clear(); 1741 pendingPipelinedCallSites.clear(); 1742 resolvingPipelinedFunctions.clear(); 1743 DISPLAY_ID.clear(); 1744 DISPLAY_NAME.clear(); 1745 virtualTableNames.clear(); 1746 } 1747 1748 public void bindCursorModel(TCursorDeclStmt stmt, Variable resultSet) { 1749 bindCursorModel(stmt, stmt.getCursorName(), resultSet); 1750 } 1751 1752 public void bindCursorModel(TMssqlDeclare stmt, Variable resultSet) { 1753 bindCursorModel(stmt, stmt.getCursorName(), resultSet); 1754 } 1755 1756 public void bindCursorModel(TOpenforStmt stmt, Variable resultSet) { 1757 bindCursorModel(stmt, stmt.getCursorVariableName(), resultSet); 1758 } 1759 1760 public void bindCursorModel(TForStmt stmt, Variable resultSet) { 1761 bindCursorModel(stmt, stmt.getCursorName(), resultSet); 1762 } 1763 1764 public void bindCursorModel(TLoopStmt stmt, Variable resultSet) { 1765 bindCursorModel(stmt, stmt.getRecordName(), resultSet); 1766 } 1767 1768 /** A cursor's model, by the cursor's name as written in the scope of the statement that declares or opens it. */ 1769 private void bindCursorModel(TCustomSqlStatement stmt, TObjectName cursorName, Variable resultSet) { 1770 gudusoft.gsqlparser.dlineage.symbol.LocalSymbolKey key = cursorName == null ? null 1771 : localCursorKey(stmt, cursorName.toString()); 1772 if (key != null) { 1773 localCursorModels.put(key, resultSet); 1774 } 1775 } 1776 1777 /** 1778 * Binds a cursor FOR loop's index to the cursor the loop names: one declared in the loop's routine, an enclosing 1779 * routine or the package. 1780 */ 1781 public void bindCursorIndex(TObjectName indexName, TObjectName cursorName) { 1782 TCustomSqlStatement stmt = getGlobalStmtStack().peek(); 1783 gudusoft.gsqlparser.dlineage.symbol.LocalSymbolKey cursorKey = localCursorKey(stmt, cursorName.toString()); 1784 Table cursor = cursorKey == null ? null : firstVisible(localCursorModels, cursorKey, 1785 gudusoft.gsqlparser.dlineage.symbol.LocalScopes.chainOf(this, stmt), false); 1786 if (cursor instanceof Variable) { 1787 publishLocalSymbol(gudusoft.gsqlparser.dlineage.symbol.LocalSymbolKey.of(cursorKey.getScope(), 1788 stripColon(indexName.toString()), gudusoft.gsqlparser.dlineage.symbol.LocalSymbolKey.Kind.VARIABLE), 1789 cursor); 1790 } 1791 } 1792 1793 /** A cursor's key: its name as written, in the scope of the statement that names it. */ 1794 private gudusoft.gsqlparser.dlineage.symbol.LocalSymbolKey localCursorKey(TCustomSqlStatement stmt, String cursorName) { 1795 return gudusoft.gsqlparser.dlineage.symbol.LocalSymbolKey.of( 1796 gudusoft.gsqlparser.dlineage.symbol.LocalScopes.scopeOf(this, stmt), stripColon(cursorName), 1797 gudusoft.gsqlparser.dlineage.symbol.LocalSymbolKey.Kind.CURSOR); 1798 } 1799 1800 private static String stripColon(String name) { 1801 return name != null && name.startsWith(":") ? name.substring(1) : name; 1802 } 1803 1804 public List<TStoredProcedureSqlStatement> getProcedures() { 1805 List<TStoredProcedureSqlStatement> procedures = new ArrayList(); 1806 Iterator iter = this.modelBindingMap.keySet().iterator(); 1807 1808 while (iter.hasNext()) { 1809 Object key = iter.next(); 1810 if (key instanceof TStoredProcedureSqlStatement) { 1811 TStoredProcedureSqlStatement procedure = (TStoredProcedureSqlStatement) key; 1812 procedures.add(procedure); 1813 } 1814 } 1815 1816 return procedures; 1817 } 1818 1819 // public void bindTableByName(TObjectName tableName, Table tableModel) { 1820 // tableNamesMap.put(tableName, tableModel); 1821 // 1822 // } 1823 // 1824 public void bindTableByName(String tableName, Table tableModel) { 1825 bindTableByName((Object) tableName, tableModel); 1826 } 1827 1828 /** Canonical physical-table keys share lifecycle/ordering with legacy bindings. */ 1829 public void bindTableByName(Object tableName, Table tableModel) { 1830 if (tableName instanceof TableIdentityKey) { 1831 TableIdentityKey key = (TableIdentityKey) tableName; 1832 displaceBinding(tableNamesMap, 0, key.getCanonKey(), tableModel); 1833 tableNamesMap.put(key.getCanonKey(), tableModel); 1834 identityBoundTables.add(tableModel); 1835 // Name-based lookups outside the table identity path (variables, cursors, qualifier 1836 // strings) still ask by the legacy folded name, so it stays bound as before. 1837 if (key.getLegacyKey() != null) { 1838 displaceBinding(tableNamesMap, 0, key.getLegacyKey(), tableModel); 1839 tableNamesMap.put(key.getLegacyKey(), tableModel); 1840 } 1841 } else { 1842 displaceBinding(tableNamesMap, 0, tableName, tableModel); 1843 tableNamesMap.put(tableName, tableModel); 1844 } 1845 TTable origin = tableModel.getTableObject(); 1846 if (origin != null && origin.getResolvedTable() != null && isScopedTemporary(origin)) { 1847 temporaryMetadataModels.put(origin.getResolvedTable(), tableModel); 1848 temporaryMetadataOwners.put(origin.getResolvedTable(), getGlobalProcedure()); 1849 } 1850 1851 } 1852 1853 // public Table getTableByName(TObjectName tableName) { 1854 // return (Table)tableNamesMap.get(tableName); 1855 // } 1856 1857 /** 1858 * B3 (routine-summary-scc-design §3): the overload pools of the routines whose bodies are being analyzed, active 1859 * ONLY under {@code Option.identityFirstVariablePools}. A pool is keyed by the routine's frozen scope and its 1860 * declaration identity ({@code IdentityScope}), so two same-named overloads keep separate formals and locals; a 1861 * name lookup in a body prefers its pool's variable. Empty in LEGACY by construction. The legacy identity text 1862 * rides along only for the deprecated {@link #peekIdentityScope()}. 1863 */ 1864 private final java.util.ArrayDeque<Object[]> poolStack = new java.util.ArrayDeque<Object[]>(); 1865 1866 /** @deprecated an overload pool is keyed by its typed scope: use {@link #pushIdentityScope(String, Object)}. */ 1867 @Deprecated 1868 public void pushIdentityScope(String identityScopeKey) { 1869 pushIdentityScope(identityScopeKey, null); 1870 } 1871 1872 /** 1873 * Enters an overload's pool: its typed scope ({@code IdentityScope}) and its legacy identity text (metadata). Every 1874 * push is matched by a {@link #popIdentityScope()}; a pool without a typed scope is entered but never active. 1875 */ 1876 public void pushIdentityScope(String identityScopeKey, Object poolScope) { 1877 if (identityScopeKey != null || poolScope != null) { 1878 poolStack.push(new Object[] { poolScope, identityScopeKey }); 1879 } 1880 } 1881 1882 public void popIdentityScope() { 1883 if (!poolStack.isEmpty()) { 1884 poolStack.pop(); 1885 } 1886 } 1887 1888 /** The active overload pool's typed scope, or null. */ 1889 public Object peekPoolScope() { 1890 Object[] top = poolStack.peek(); 1891 return top == null ? null : top[0]; 1892 } 1893 1894 public boolean hasIdentityScope() { 1895 return peekPoolScope() != null; 1896 } 1897 1898 /** @deprecated the pool is keyed by {@link #peekPoolScope()}; this is its legacy text, never used to resolve. */ 1899 @Deprecated 1900 public String peekIdentityScope() { 1901 Object[] top = poolStack.peek(); 1902 return top == null ? null : (String) top[1]; 1903 } 1904 1905 /** Indexes a local symbol by its key without listing it again (an alias of a model already listed). */ 1906 public void bindLocalSymbol(gudusoft.gsqlparser.dlineage.symbol.LocalSymbolKey key, Table symbol) { 1907 if (key != null) { 1908 localSymbols.put(key, symbol); 1909 } 1910 } 1911 1912 /** 1913 * Binds a local symbol by its key and lists it among the analysis's tables at this point (getTablesByName, and so 1914 * the output order), as every binding of a variable, cursor or correlation name has been listed. 1915 */ 1916 public void publishLocalSymbol(gudusoft.gsqlparser.dlineage.symbol.LocalSymbolKey key, Table symbol) { 1917 if (key != null) { 1918 localSymbols.put(key, symbol); 1919 bindTableByName((Object) key, symbol); 1920 } 1921 } 1922 1923 /** 1924 * The local symbol a reference in {@code stmt} sees under {@code key}'s name: the first scope of the statement's 1925 * lexical chain (routine, enclosing routines, package) that declares it; at top level, the top-level one. 1926 */ 1927 public Table lookupVisibleLocalSymbol(gudusoft.gsqlparser.dlineage.symbol.LocalSymbolKey key, TCustomSqlStatement stmt) { 1928 return lookupVisibleLocalSymbol(key, stmt, false); 1929 } 1930 1931 /** 1932 * As {@link #lookupVisibleLocalSymbol(gudusoft.gsqlparser.dlineage.symbol.LocalSymbolKey, TCustomSqlStatement)}; 1933 * with {@code topLevel}, a declaration at top level is visible from inside a routine too (a declared type). 1934 */ 1935 public Table lookupVisibleLocalSymbol(gudusoft.gsqlparser.dlineage.symbol.LocalSymbolKey key, TCustomSqlStatement stmt, 1936 boolean topLevel) { 1937 if (key == null) { 1938 return null; 1939 } 1940 Object poolScope = peekPoolScope(); 1941 java.util.List<Object> chain = gudusoft.gsqlparser.dlineage.symbol.LocalScopes.chainOf(this, stmt); 1942 if (poolScope != null) { 1943 Table pooled = localSymbols.get(key.inScope(poolScope)); 1944 if (pooled instanceof Variable) { 1945 return pooled; 1946 } 1947 // Every declaration of the overload being analyzed is in its pool: a declared name the pool lacks is not 1948 // this overload's, and the overload-blind scope of its routine may hold another overload's; so the 1949 // enclosing routines and the package answer. 1950 if (poolScope instanceof gudusoft.gsqlparser.dlineage.symbol.IdentityScope 1951 && key.getKind().getNamespace() == gudusoft.gsqlparser.dlineage.symbol.LocalSymbolKey.Namespace.DECLARED) { 1952 chain = new java.util.ArrayList<Object>(chain); 1953 chain.remove(((gudusoft.gsqlparser.dlineage.symbol.IdentityScope) poolScope).getScope()); 1954 } 1955 } 1956 Table found = firstVisible(localSymbols, key, chain, topLevel); 1957 if (found == null && key.getKind() != gudusoft.gsqlparser.dlineage.symbol.LocalSymbolKey.Kind.CORRELATION) { 1958 // A bare correlation name (NEW.col) when no declared local of that name is visible. 1959 found = firstVisible(localSymbols, key.asCorrelation(), chain, topLevel); 1960 } 1961 return found; 1962 } 1963 1964 private static Table firstVisible(gudusoft.gsqlparser.dlineage.symbol.LocalSymbolRegistry symbols, 1965 gudusoft.gsqlparser.dlineage.symbol.LocalSymbolKey key, java.util.List<Object> chain, boolean topLevel) { 1966 for (Object scope : chain) { 1967 Table found = symbols.get(key.inScope(scope)); 1968 if (found != null) { 1969 return found; 1970 } 1971 } 1972 return chain.isEmpty() || topLevel ? symbols.get(key.inScope(null)) : null; 1973 } 1974 1975 /** The local symbol visible from the current statement under a name as written (a leading ':' is a sigil). */ 1976 public Table visibleLocal(String written, gudusoft.gsqlparser.dlineage.symbol.LocalSymbolKey.Kind kind) { 1977 if (SQLUtil.isEmpty(written)) { 1978 return null; 1979 } 1980 boolean correlation = written.startsWith(":"); 1981 Stack<TCustomSqlStatement> stack = getGlobalStmtStack(); 1982 return lookupVisibleLocalSymbol(gudusoft.gsqlparser.dlineage.symbol.LocalSymbolKey.of(null, 1983 correlation ? written.substring(1) : written, 1984 correlation ? gudusoft.gsqlparser.dlineage.symbol.LocalSymbolKey.Kind.CORRELATION : kind), 1985 stack == null || stack.isEmpty() ? null : stack.peek()); 1986 } 1987 1988 /** 1989 * A name that may denote a table or a local relation (table variable, cursor, record), by the site's precedence: 1990 * with {@code localFirst} (a %ROWTYPE or %TYPE anchor, a qualifier no FROM item declares) the local relation visible 1991 * from the current statement answers before the table path, as PL/SQL scoping does; otherwise (a FROM or DML 1992 * relation) the table path's answer stands and the local relation answers only when the table path has none. 1993 */ 1994 public Table resolveMixedRelation(String written, Table tablePath, boolean localFirst) { 1995 if (tablePath != null && !localFirst) { 1996 return tablePath; 1997 } 1998 // A local relation: a table variable, cursor or record, or a trigger correlation name (:NEW, the source table). 1999 Table local = visibleLocal(written, gudusoft.gsqlparser.dlineage.symbol.LocalSymbolKey.Kind.TABLE_VARIABLE); 2000 return local != null ? local : tablePath; 2001 } 2002 2003 /** The local symbol under a typed key, or null. */ 2004 public Table getLocalSymbol(gudusoft.gsqlparser.dlineage.symbol.LocalSymbolKey key) { 2005 return key == null ? null : localSymbols.get(key); 2006 } 2007 2008 /** A canonical miss must not fall back to a case-folded display name. */ 2009 public Table getTableByName(Object tableKey) { 2010 if (tableKey instanceof TableIdentityKey) { 2011 TableIdentityKey key = (TableIdentityKey) tableKey; 2012 Object bound = tableNamesMap.get(key.getCanonKey()); 2013 if (bound != null) { 2014 return (Table) bound; 2015 } 2016 // The folded name of a table is exactly what merges two tables the analysis keeps 2017 // apart, so it never answers for one. A binding that was never keyed by table 2018 // identity (a stage, a stream) is still found by it, as it always was; a local 2019 // symbol never is: the sites that may name one ask for it explicitly. 2020 Table byName = key.getLegacyKey() == null ? null : getTableOnly(key.getLegacyKey()); 2021 return byName == null || identityBoundTables.contains(byName) ? null : byName; 2022 } 2023 return tableKey instanceof String ? getTableByName((String) tableKey) 2024 : (Table) tableNamesMap.get(tableKey); 2025 } 2026 2027 /** A table or other named object bound under a name; never a local symbol. */ 2028 private Table getTableOnly(String tableName) { 2029 Table table = (Table) tableNamesMap.get(tableName); 2030 return table != null ? table : (Table) tableNamesMap.get(DlineageUtil.getTableFullName(tableName)); 2031 } 2032 2033 /** 2034 * A table or other named object bound under a name; inside a statement, a local symbol visible from it under the 2035 * name as written answers before the name is read again with the session's defaults, as it always has. A name 2036 * composed with a routine's scope ("PROC.@X") no longer names a local symbol: local symbols are keyed by their 2037 * declaring scope and their names as written. 2038 */ 2039 public Table getTableByName(String tableName) { 2040 if (tableName == null) { 2041 return null; 2042 } 2043 Table table = (Table) tableNamesMap.get(tableName); 2044 if (table != null) { 2045 return table; 2046 } 2047 Stack<TCustomSqlStatement> stmtStack = getGlobalStmtStack(); 2048 if (stmtStack != null && !stmtStack.isEmpty()) { 2049 Table local = visibleLocal(tableName, gudusoft.gsqlparser.dlineage.symbol.LocalSymbolKey.Kind.VARIABLE); 2050 if (local != null) { 2051 return local; 2052 } 2053 return (Table) tableNamesMap.get(DlineageUtil.getTableFullName(tableName)); 2054 } 2055 Table named = (Table) tableNamesMap.get(DlineageUtil.getTableFullName(tableName)); 2056 return named != null ? named : visibleLocal(tableName, gudusoft.gsqlparser.dlineage.symbol.LocalSymbolKey.Kind.VARIABLE); 2057 } 2058 2059 2060 /** The routine-registry shadow of this analysis (measurement only; see RoutineShadow). */ 2061 public gudusoft.gsqlparser.dlineage.routine.RoutineShadow.State routineShadowState() { 2062 if (routineShadowState == null) { 2063 routineShadowState = new gudusoft.gsqlparser.dlineage.routine.RoutineShadow.State(); 2064 } 2065 return routineShadowState; 2066 } 2067 2068 /** 2069 * The lineage model of a routine statement: the routine model last bound to it, whatever else the statement was 2070 * bound to since. Null when no routine model was bound to it. 2071 */ 2072 public Procedure getRoutineModel(TCustomSqlStatement routine) { 2073 return routine == null ? null : routineModels.get(routine); 2074 } 2075 2076 /** Binds a routine model under one of its names as written with one alias suffix. */ 2077 public void bindProcedure(RoutineAliasKey key, Procedure procedureModel) { 2078 procedureAliases.put(key, procedureModel); 2079 noteRoutineService(key); 2080 } 2081 2082 private void noteRoutineService(RoutineAliasKey key) { 2083 if (key != null && routineService == null) { 2084 routineService = getGlobalIdentifierService(); 2085 } 2086 } 2087 2088 /** 2089 * Deprecated text read under the rules this manager's routines were keyed by, else under the analysis's on this 2090 * thread: another analysis on the calling thread must not change how this manager reads a name. 2091 */ 2092 private RoutineAliasKey legacyTextKey(String text) { 2093 return RoutineAliasKey.ofLegacyText(text, routineService != null ? routineService : getGlobalIdentifierService()); 2094 } 2095 2096 /** 2097 * Binds a routine model under a name of the deprecated text-keyed API: the text as given, and the routine name the 2098 * text reads as, so the analysis's own lookups see it. 2099 * 2100 * @deprecated routines are keyed by their names as written: use {@link #bindProcedure(RoutineAliasKey, Procedure)} 2101 */ 2102 @Deprecated 2103 public void bindProcedureByName(String procedureName, Procedure procedureModel) { 2104 legacyProcedureNames.put(procedureName, procedureModel); 2105 bindProcedure(legacyTextKey(procedureName), procedureModel); 2106 } 2107 2108 /** 2109 * The routine a name of the deprecated text-keyed API names: looked up as the legacy map was (the enclosing package's 2110 * name prefixed unless the text starts with it; the text as given; then the first entry whose text starts with the 2111 * name and a suffix, or whose last segment matches when one side is qualified), then the routine name the text 2112 * reads as. 2113 * 2114 * @deprecated routines are keyed by their names as written: use {@link #getProcedure(RoutineAliasKey)} 2115 */ 2116 @Deprecated 2117 public Procedure getProcedureByName(String procedureName) { 2118 if (procedureName == null) { 2119 return null; 2120 } 2121 String name = procedureName; 2122 if (getGlobalOraclePackage() != null && !name.startsWith(getGlobalOraclePackage().getName())) { 2123 name = getGlobalOraclePackage().getName() + "." + name; 2124 } 2125 Procedure procedure = legacyProcedureNames.get(name); 2126 if (procedure == null) { 2127 boolean suffixed = name.indexOf("(") != -1; 2128 for (Map.Entry<String, Procedure> entry : legacyProcedureNames.entrySet()) { 2129 String key = entry.getKey(); 2130 if (key == null) { 2131 continue; 2132 } 2133 if (!suffixed) { 2134 if (key.startsWith(name + "(")) { 2135 return entry.getValue(); 2136 } 2137 if (key.indexOf(".") != -1 && name.indexOf(".") == -1 2138 && key.substring(key.lastIndexOf(".") + 1).startsWith(name + "(")) { 2139 return entry.getValue(); 2140 } 2141 if (key.indexOf(".") == -1 && name.indexOf(".") != -1 2142 && key.startsWith(name.substring(name.lastIndexOf(".") + 1) + "(")) { 2143 return entry.getValue(); 2144 } 2145 } else { 2146 if (key.startsWith(name)) { 2147 return entry.getValue(); 2148 } 2149 if (key.indexOf(".") != -1 && name.indexOf(".") == -1 && sameStoredBindingName( 2150 ESQLDataObjectType.dotProcedure, key.substring(key.lastIndexOf(".") + 1), name)) { 2151 return entry.getValue(); 2152 } 2153 if (key.indexOf(".") == -1 && name.indexOf(".") != -1 && sameStoredBindingName( 2154 ESQLDataObjectType.dotProcedure, key, name.substring(name.lastIndexOf(".") + 1))) { 2155 return entry.getValue(); 2156 } 2157 } 2158 } 2159 procedure = getProcedure(legacyTextKey(procedureName)); 2160 } 2161 return procedure; 2162 } 2163 2164 /** The routine a name as written refers to, by the legacy index's precedence (RoutineAliasRegistry.lookup). */ 2165 public Procedure getProcedure(RoutineAliasKey key) { 2166 OraclePackage enclosing = getGlobalOraclePackage(); 2167 return procedureAliases.lookup(key, enclosing == null ? null : enclosing.getAliasKey()); 2168 } 2169 2170 public void bindOraclePackage(RoutineAliasKey key, OraclePackage oraclePackageModel) { 2171 packageAliases.put(key, oraclePackageModel); 2172 noteRoutineService(key); 2173 } 2174 2175 public OraclePackage getOraclePackage(RoutineAliasKey key) { 2176 return packageAliases.get(key); 2177 } 2178 2179 /** 2180 * Binds an Oracle package under a name of the deprecated text-keyed API: the text as given, and the package name 2181 * the text reads as. 2182 * 2183 * @deprecated packages are keyed by their names as written: use {@link #bindOraclePackage(RoutineAliasKey, OraclePackage)} 2184 */ 2185 @Deprecated 2186 public void bindOraclePackageByName(String oraclePackageName, OraclePackage oraclePackageModel) { 2187 legacyPackageNames.put(oraclePackageName, oraclePackageModel); 2188 bindOraclePackage(legacyTextKey(oraclePackageName), oraclePackageModel); 2189 } 2190 2191 /** 2192 * The Oracle package under a name of the deprecated text-keyed API: the text as given, then the package name the 2193 * text reads as. 2194 * 2195 * @deprecated packages are keyed by their names as written: use {@link #getOraclePackage(RoutineAliasKey)} 2196 */ 2197 @Deprecated 2198 public OraclePackage getOraclePackageByName(String oraclePackageName) { 2199 OraclePackage oraclePackage = legacyPackageNames.get(oraclePackageName); 2200 return oraclePackage != null ? oraclePackage : getOraclePackage(legacyTextKey(oraclePackageName)); 2201 } 2202 2203 2204 public String getSqlHash(TCustomSqlStatement stmt) { 2205 return topStmtHashMap.get(stmt); 2206 } 2207 2208 public Map getHashSQLMap() { 2209 return hashSQLMap; 2210 } 2211 2212 public Map getDynamicSQLMap() { 2213 return dynamicSQLMap; 2214 } 2215 2216 public void collectSqlHash(TCustomSqlStatement stmt) { 2217 if (getGlobalOption().getAnalyzeMode() == AnalyzeMode.crud) { 2218 String sql = stmt.toString(); 2219 if (sql != null) { 2220 sql = stmt.getStartToken().lineNo + ":" + sql; 2221 String hash = SQLUtil.stringToMD5(sql); 2222 topStmtHashMap.put(stmt, hash); 2223 hashSQLMap.put(hash, new Pair3<Long, Long, String>(stmt.getStartToken().lineNo, stmt.getEndToken().lineNo, ModelBindingManager.getGlobalHash())); 2224 } 2225 } else { 2226 try { 2227 String sql = stmt.asCanonical(); 2228 if (sql != null) { 2229 String hash = SQLUtil.stringToMD5(sql); 2230 topStmtHashMap.put(stmt, hash); 2231 hashSQLMap.put(hash, sql); 2232 } 2233 } catch (Exception e) { 2234 logger.error("collectSqlHash error: " + e.getMessage(), e); 2235 } 2236 } 2237 } 2238 2239 public void collectDynamicSqlHash(TCustomSqlStatement stmt) { 2240 if (getGlobalOption().getAnalyzeMode() == AnalyzeMode.dynamic) { 2241 String sql = stmt.toString(); 2242 if (sql != null) { 2243 sql = stmt.getStartToken().lineNo + ":" + sql; 2244 String hash = SQLUtil.stringToMD5(sql); 2245 dynamicSQLMap.put(hash, new Pair3<Long, Long, String>(stmt.getStartToken().lineNo, stmt.getEndToken().lineNo, ModelBindingManager.getGlobalHash())); 2246 } 2247 } 2248 } 2249 2250 public void appendDblinkTable(String name) { 2251 if (!SQLUtil.isEmpty(name)) { 2252 dblinkTableSet.add(name); 2253 } 2254 } 2255 2256 public boolean isDblinkTable(String name) { 2257 return dblinkTableSet.contains(name); 2258 } 2259 2260 public Set<ResultSet> getResultSets() { 2261 return resultSetSet; 2262 } 2263 2264 public void dropTable(Table table) { 2265 List tableNames = new ArrayList(tableNamesMap.keySet()); 2266 for (Object tableName : tableNames) { 2267 if(tableNamesMap.get(tableName) == table) { 2268 tableNamesMap.remove(tableName); 2269 dropTables.add(table); 2270 } 2271 } 2272 } 2273 2274 public void cleanTempTable() { 2275 Iterator<Object> iter = tableNamesMap.keySet().iterator(); 2276 while(iter.hasNext()) { 2277 Object tableName = iter.next(); 2278 if(DlineageUtil.isTempTable((Table)tableNamesMap.get(tableName), getGlobalVendor())) { 2279 dropTables.add((Table)tableNamesMap.get(tableName)); 2280 iter.remove(); 2281 } 2282 } 2283 } 2284 2285 /** 2286 * Save session/caller bindings while analyzing a separate routine body. The first two maps 2287 * hold the temporary tables visible on entry; the last two, filled while the body is analyzed, 2288 * hold the shared tables its own temporary tables displaced by name. 2289 */ 2290 public List<Map> enterTemporaryTableScope() { 2291 List<Map> saved = new ArrayList<Map>(); 2292 for (Map bindings : Arrays.asList(tableNamesMap, createModelQuickBindingMap)) { 2293 Map snapshot = new LinkedHashMap(); 2294 for (Object key : bindings.keySet()) { 2295 if (isScopedTemporaryModel(bindings.get(key))) snapshot.put(key, bindings.get(key)); 2296 } 2297 saved.add(snapshot); 2298 } 2299 saved.add(new LinkedHashMap()); 2300 saved.add(new LinkedHashMap()); 2301 temporaryScopes.push(saved); 2302 return saved; 2303 } 2304 2305 /** Local declarations remain in the output, but cannot become another routine's metadata. */ 2306 public void leaveTemporaryTableScope(List<Map> saved) { 2307 for (Iterator<List<Map>> scopes = temporaryScopes.iterator(); scopes.hasNext();) { 2308 if (scopes.next() == saved) { 2309 scopes.remove(); 2310 break; 2311 } 2312 } 2313 int index = 0; 2314 for (Map bindings : Arrays.asList(tableNamesMap, createModelQuickBindingMap)) { 2315 Iterator iterator = bindings.values().iterator(); 2316 while (iterator.hasNext()) { 2317 if (isScopedTemporaryModel(iterator.next())) iterator.remove(); 2318 } 2319 bindings.putAll(saved.get(index)); 2320 // A temporary table named like a shared one (CREATE TEMP TABLE t, with a permanent t) 2321 // hid it only while this body was analyzed. 2322 for (Object entry : saved.get(index + 2).entrySet()) { 2323 Map.Entry displaced = (Map.Entry) entry; 2324 if (!bindings.containsKey(displaced.getKey())) bindings.put(displaced.getKey(), displaced.getValue()); 2325 } 2326 index++; 2327 } 2328 } 2329 2330 /** Remember a shared binding that a routine body's temporary table is about to hide. */ 2331 private void displaceBinding(Map bindings, int index, Object key, Object model) { 2332 if (temporaryScopes.isEmpty() || !isScopedTemporaryModel(model)) return; 2333 Object previous = bindings.get(key); 2334 if (previous == null || previous == model || isScopedTemporaryModel(previous)) return; 2335 Map displaced = temporaryScopes.peek().get(index + 2); 2336 if (!displaced.containsKey(key)) displaced.put(key, previous); 2337 } 2338 2339 private boolean isScopedTemporaryModel(Object model) { 2340 return model instanceof Table && isScopedTemporary(((Table) model).getTableObject()); 2341 } 2342 2343 public Set<Table> getDropTables() { 2344 return dropTables; 2345 } 2346 2347 // --- Pipelined function stitching methods --- 2348 2349 /** 2350 * Indexes an object type's fields under a key of the deprecated text API. 2351 * 2352 * @deprecated types are keyed by their names as written: use {@link #indexObjectTypeFields(Object, List)} 2353 */ 2354 @Deprecated 2355 public void indexObjectType(String normalizedName, List<String> fieldNames) { 2356 objectTypeIndex.put(normalizedName, fieldNames); 2357 } 2358 2359 /** @deprecated use {@link #getObjectTypeFieldsByKey(Object)} */ 2360 @Deprecated 2361 public List<String> getObjectTypeFields(String normalizedName) { 2362 return objectTypeIndex.get(normalizedName); 2363 } 2364 2365 /** @deprecated use {@link #indexCollectionElementType(Object, String)} */ 2366 @Deprecated 2367 public void indexCollectionType(String collectionName, String elementTypeName) { 2368 collectionToElementTypeIndex.put(collectionName, elementTypeName); 2369 } 2370 2371 /** @deprecated use {@link #getCollectionElementType(Object)} */ 2372 @Deprecated 2373 public String getElementTypeName(String collectionName) { 2374 return collectionToElementTypeIndex.get(collectionName); 2375 } 2376 2377 /** Indexes an object type's fields under the identity of its name. */ 2378 public void indexObjectTypeFields(Object typeKey, List<String> fieldNames) { 2379 if (typeKey != null) { 2380 objectTypeIndex.put(typeKey, fieldNames); 2381 } 2382 } 2383 2384 /** The fields of the object type a key names, or null. */ 2385 public List<String> getObjectTypeFieldsByKey(Object typeKey) { 2386 return typeKey == null ? null : objectTypeIndex.get(typeKey); 2387 } 2388 2389 /** Indexes a collection type's element type, the element type's name as written, under the collection's key. */ 2390 public void indexCollectionElementType(Object collectionKey, String elementTypeName) { 2391 if (collectionKey != null) { 2392 collectionToElementTypeIndex.put(collectionKey, elementTypeName); 2393 } 2394 } 2395 2396 /** The element type name, as written, of the collection type a key names, or null. */ 2397 public String getCollectionElementType(Object collectionKey) { 2398 return collectionKey == null ? null : collectionToElementTypeIndex.get(collectionKey); 2399 } 2400 2401 /** 2402 * Registers a pipelined signature under its function's identity and the identity of its bare name; a signature 2403 * built through the deprecated text keys is registered under those. 2404 */ 2405 public void addPipelinedSignature(PipelinedFunctionSignature sig) { 2406 Object key = sig.getFunctionIdentity() != null ? sig.getFunctionIdentity() : sig.getFunctionKey(); 2407 Object naked = sig.getNakedIdentity() != null ? sig.getNakedIdentity() : sig.getNakedKey(); 2408 pipelinedSignatures.computeIfAbsent(key, k -> new ArrayList<>()).add(sig); 2409 if (naked != null && !naked.equals(key)) { 2410 pipelinedSignatures.computeIfAbsent(naked, k -> new ArrayList<>()).add(sig); 2411 } 2412 } 2413 2414 /** 2415 * The signature a call names under keys of the deprecated text API. 2416 * 2417 * @deprecated signatures are registered under the identities of their names: use 2418 * {@link #findPipelinedSignatureByKeys(List, int)} 2419 */ 2420 @Deprecated 2421 public PipelinedFunctionSignature findPipelinedSignature(List<String> candidates, int argCount) { 2422 return findPipelinedSignatureByKeys(candidates, argCount); 2423 } 2424 2425 /** 2426 * The one signature registered under the first of {@code keys} that has any accepting {@code argCount} (any count 2427 * when negative); null when none, or when that key is ambiguous. 2428 */ 2429 public PipelinedFunctionSignature findPipelinedSignatureByKeys(List<?> keys, int argCount) { 2430 for (Object key : keys) { 2431 List<PipelinedFunctionSignature> sigs = pipelinedSignatures.get(key); 2432 if (sigs != null) { 2433 PipelinedFunctionSignature match = null; 2434 for (PipelinedFunctionSignature sig : sigs) { 2435 if (sig.getArgCount() == argCount || argCount < 0) { 2436 if (match != null) { 2437 return null; // ambiguous 2438 } 2439 match = sig; 2440 } 2441 } 2442 if (match != null) { 2443 return match; 2444 } 2445 } 2446 } 2447 return null; 2448 } 2449 2450 /** 2451 * @deprecated signatures are registered under the identities of their names: use 2452 * {@link #findPipelinedSignatureAnyByKeys(List)} 2453 */ 2454 @Deprecated 2455 public PipelinedFunctionSignature findPipelinedSignatureAny(List<String> candidates) { 2456 return findPipelinedSignatureAnyByKeys(candidates); 2457 } 2458 2459 /** The only signature registered under the first of {@code keys} that has exactly one; null when none. */ 2460 public PipelinedFunctionSignature findPipelinedSignatureAnyByKeys(List<?> keys) { 2461 for (Object key : keys) { 2462 List<PipelinedFunctionSignature> sigs = pipelinedSignatures.get(key); 2463 if (sigs != null && !sigs.isEmpty()) { 2464 if (sigs.size() == 1) { 2465 return sigs.get(0); 2466 } 2467 } 2468 } 2469 return null; 2470 } 2471 2472 public void addPendingPipelinedCallSite(PendingPipelinedCallSite callSite) { 2473 pendingPipelinedCallSites.add(callSite); 2474 } 2475 2476 public List<PendingPipelinedCallSite> getPendingPipelinedCallSites() { 2477 return pendingPipelinedCallSites; 2478 } 2479 2480 public Set<String> getResolvingPipelinedFunctions() { 2481 return resolvingPipelinedFunctions; 2482 } 2483 2484 /** 2485 * The registered signatures under their names as written, the function's name and then its bare name: a view for 2486 * the deprecated text API (the registry is keyed by the names' identities). 2487 * 2488 * @deprecated use {@link #getPipelinedSignaturesByKey()} 2489 */ 2490 @Deprecated 2491 public Map<String, List<PipelinedFunctionSignature>> getPipelinedSignatures() { 2492 Map<String, List<PipelinedFunctionSignature>> byName = new LinkedHashMap<>(); 2493 Set<PipelinedFunctionSignature> listed = Collections.newSetFromMap( 2494 new IdentityHashMap<PipelinedFunctionSignature, Boolean>()); 2495 for (List<PipelinedFunctionSignature> signatures : pipelinedSignatures.values()) { 2496 for (PipelinedFunctionSignature sig : signatures) { 2497 if (!listed.add(sig)) { 2498 continue; 2499 } 2500 byName.computeIfAbsent(sig.getFunctionKey(), k -> new ArrayList<>()).add(sig); 2501 if (sig.getNakedKey() != null && !sig.getNakedKey().equals(sig.getFunctionKey())) { 2502 byName.computeIfAbsent(sig.getNakedKey(), k -> new ArrayList<>()).add(sig); 2503 } 2504 } 2505 } 2506 return byName; 2507 } 2508 2509 /** The pipelined signature registry: signatures under the identities of their names (and deprecated text keys). */ 2510 public Map<Object, List<PipelinedFunctionSignature>> getPipelinedSignaturesByKey() { 2511 return pipelinedSignatures; 2512 } 2513 2514}