001 002package gudusoft.gsqlparser.dlineage.dataflow.model; 003 004import gudusoft.gsqlparser.EDbVendor; 005import gudusoft.gsqlparser.ETableKind; 006import gudusoft.gsqlparser.ETableSource; 007import gudusoft.gsqlparser.TCustomSqlStatement; 008import gudusoft.gsqlparser.TSourceToken; 009import gudusoft.gsqlparser.dlineage.util.DlineageUtil; 010import gudusoft.gsqlparser.dlineage.util.Pair; 011import gudusoft.gsqlparser.dlineage.util.Pair3; 012import gudusoft.gsqlparser.nodes.TFunctionCall; 013import gudusoft.gsqlparser.nodes.TObjectName; 014import gudusoft.gsqlparser.nodes.TTable; 015import gudusoft.gsqlparser.sqlenv.*; 016import gudusoft.gsqlparser.stmt.TCreateTableSqlStatement; 017import gudusoft.gsqlparser.stmt.TCreateViewSqlStatement; 018import gudusoft.gsqlparser.util.Logger; 019import gudusoft.gsqlparser.util.LoggerFactory; 020import gudusoft.gsqlparser.util.SQLUtil; 021 022import java.io.ByteArrayInputStream; 023import java.io.IOException; 024import java.util.*; 025 026public class Table { 027 028 private static final Logger logger = LoggerFactory.getLogger(Table.class); 029 030 private long id; 031 protected String server; 032 protected String database; 033 protected String schema; 034 protected String name; 035 protected String displayName; 036 protected String fullName; 037 protected Set<String> alias = new LinkedHashSet<String>(); 038 protected Set<Pair<Pair3<Long, Long, String>, Pair3<Long, Long, String>>> positions = new LinkedHashSet<>(); 039 protected String parent; 040 protected Pair3<Long, Long, String> startPosition; 041 protected Pair3<Long, Long, String> endPosition; 042 private List<TableColumn> columns = new ArrayList<TableColumn>(); 043 private boolean subquery = false; 044 045 /** 046 * Set when an unqualified one-part name matches the same base table in two or 047 * more schemas of the (otherwise unambiguous) default catalog and the default 048 * schema holds no such object. In that case GSP must NOT fabricate a 049 * non-existent default-schema object; it leaves the reference unqualified and 050 * exposes the candidate set instead. See 051 * docs/tmp/unqualified-name-default-schema-masks-cross-schema-ambiguity.md. 052 */ 053 private boolean ambiguousUnqualifiedTable = false; 054 private List<String> candidateTables; 055 056 private TTable tableObject; 057 private TObjectName viewName; 058 private boolean isCreateTable; 059 private boolean isView; 060 private boolean isPseudo; 061 private boolean isExternal; 062 private boolean isStage; 063 private boolean isSequence; 064 private boolean isPath; 065 private boolean isStream; 066 private boolean isVariable; 067 private boolean isConstant; 068 private boolean isTarget; 069 private String procedureId; 070 private boolean isDatabase; 071 private boolean isSchema; 072 private boolean isDataSource; 073 private String location; 074 private String fileType; 075 private String fileFormat; 076 private List<Process> processes; 077 private SubType subType; 078 private String starStmt; 079 private boolean hasSQLEnv = false; 080 private String currentAlias; 081 private boolean isDetermined; 082 083 private boolean fromDDL; 084 085 /** 086 * How this endpoint was introduced in the analyzed SQL. The create-effect values 087 * (CREATE_TABLE_DDL / SELECT_INTO / CTAS / CLONE_TABLE) are set as parse facts at 088 * build time, independent of {@link #isDetermined()} — unlike {@link #fromDDL}. 089 * Strictly additive; never back-fills {@link #subType}. See 090 * docs/tmp/dlineage-authoritative-endpoint-classification.md. 091 */ 092 private EndpointIntroduction endpointIntroduction = EndpointIntroduction.UNKNOWN; 093 094 /** 095 * True when this endpoint is created by the analyzed SQL (CREATE TABLE / SELECT 096 * INTO / CTAS / clone target), set regardless of schema resolution. 097 */ 098 private boolean createdInSql; 099 100 /** 101 * Pipe-joined, outermost-first list of the identifier segments of {@link #name} that 102 * overlap an unresolved dynamic-SQL hole ({@code "db"}, {@code "db|name"}, ...), or 103 * {@code null} when this endpoint is not runtime-templated. Written ONLY from inside a 104 * dynamic-fold context by the analyzer, so a static reference to a legally delimited 105 * {@code [weird@name]} is never marked. See 106 * docs/tmp/dynamic-template-endpoint-provenance.md. 107 */ 108 private String templatedParts; 109 110 /** 111 * True when the hole covers the LEADING character of the object-name segment — i.e. 112 * the {@code #}/{@code ##}/{@code @} prefix that would otherwise classify this endpoint 113 * as a temp table / table variable is itself runtime text, so that classification is a 114 * fold artifact rather than a fact. Drives the {@link EndpointKind#DYNAMIC_TEMPLATE} 115 * precedence in {@link #getEndpointKind()}. 116 */ 117 private boolean templatedIdentity; 118 119 private TableRelationRows relationRows = new TableRelationRows(this); 120 121 private TCustomSqlStatement stmt; 122 123 public Table(TCustomSqlStatement stmt, TObjectName viewName) { 124 this(viewName); 125 this.stmt = stmt; 126 if (stmt != null) { 127 setPosition(stmt); 128 } 129 } 130 131 public TCustomSqlStatement getSqlStatement() { 132 return stmt; 133 } 134 135 public Table(TTable table, String tableName) { 136 if (table == null) { 137 throw new IllegalArgumentException("Table arguments can't be null."); 138 } 139 140 id = ++ModelBindingManager.get().TABLE_COLUMN_ID; 141 142 this.tableObject = table; 143 144 setPosition(table); 145 146 this.name = tableName; 147 this.fullName = tableName; 148 setAlias(table.getAliasName()); 149 setCurrentAlias(table.getAliasName()); 150 151 if (name.startsWith("`") && name.endsWith("`") && name.indexOf(".") != -1 152 && ModelBindingManager.getGlobalOption().getVendor() == EDbVendor.dbvbigquery) { 153 this.name = SQLUtil.trimColumnStringQuote(name); 154 this.fullName = SQLUtil.trimColumnStringQuote(fullName); 155 } 156 157 if (table.getSubquery() != null) { 158 subquery = true; 159 } 160 161 EDbVendor vendor = ModelBindingManager.getGlobalOption().getVendor(); 162 boolean supportCatalog = TSQLEnv.supportCatalog(vendor); 163 boolean supportSchema = TSQLEnv.supportSchema(vendor); 164 165 if (table.getTableName().getDblink() == null) { 166 this.server = DlineageUtil.getTableServer(table.getTableName().toString()); 167 this.database = DlineageUtil.getTableDatabase(table.getTableName().toString()); 168 this.schema = DlineageUtil.getTableSchema(table.getTableName().toString()); 169 } 170 else { 171 if (this.server == null && !TSQLEnv.DEFAULT_SERVER_NAME.equals(getDefaultServer())) { 172 this.server = getDefaultServer(); 173 } 174 this.database = SQLUtil.normalizeIdentifier(vendor, ESQLDataObjectType.dotDblink, table.getTableName().getDblink().toString()); 175 if (!SQLUtil.isEmpty(table.getTableName().getSchemaString())) { 176 this.schema = table.getTableName().getSchemaString(); 177 } 178 this.subType = SubType.dblink; 179 this.name = this.name.replace(table.getTableName().getDblink().toString(), this.database); 180 ModelBindingManager.get().appendDblinkTable(this.name); 181 return; 182 } 183 184 fillSchemaInfo(); 185 186 if (SQLUtil.isEmpty(this.database)) { 187 this.database = DlineageUtil.getTableDatabase(table.getTableName().toString()); 188 if (SQLUtil.isEmpty(this.database) && ModelBindingManager.getGlobalOption().isShowImplicitSchema() 189 && !SQLUtil.isEmpty(table.getTableName().getImplictDatabaseString())) { 190 this.database = table.getTableName().getImplictDatabaseString(); 191 } 192 } 193 194 if (SQLUtil.isEmpty(this.schema)) { 195 this.schema = DlineageUtil.getTableSchema(table.getTableName().toString()); 196 if (SQLUtil.isEmpty(this.schema) && ModelBindingManager.getGlobalOption().isShowImplicitSchema() 197 && !SQLUtil.isEmpty(implicitSchemaOf(table.getTableName()))) { 198 this.schema = implicitSchemaOf(table.getTableName()); 199 } 200 } 201 202 if (!SQLUtil.isEmpty(this.database) && SQLUtil.isEmpty(this.schema)) { 203 if (!SQLUtil.isEmpty(implicitSchemaOf(table.getTableName()))) { 204 this.schema = implicitSchemaOf(table.getTableName()); 205 } 206 } 207 208 if (table.getTableType() == ETableSource.function) { 209 subType = SubType.function; 210 } 211 212 if (!supportCatalog) { 213 this.database = null; 214 } else if (this.database == null && !TSQLEnv.DEFAULT_DB_NAME.equals(getDefaultDatabase())) { 215 this.database = getDefaultDatabase(); 216 } 217 218 if (!supportSchema) { 219 this.schema = null; 220 } else if (this.schema == null && !TSQLEnv.DEFAULT_SCHEMA_NAME.equals(getDefaultSchema())) { 221 this.schema = getDefaultSchema(); 222 } 223 224 updateTableName(supportCatalog, supportSchema); 225 226 if (this.server == null && !TSQLEnv.DEFAULT_SERVER_NAME.equals(getDefaultServer())) { 227 this.server = getDefaultServer(); 228 } 229 } 230 231 public Table(TTable table) { 232 if (table == null) { 233 throw new IllegalArgumentException("Table arguments can't be null."); 234 } 235 236 id = ++ModelBindingManager.get().TABLE_COLUMN_ID; 237 238 this.tableObject = table; 239 240 setPosition(table); 241 242 if (table.getLinkTable() != null) { 243 this.fullName = table.getLinkTable().getFullName(); 244 this.name = table.getLinkTable().getName(); 245 setAlias(table.getName()); 246 setCurrentAlias(table.getName()); 247 } else if (table.getTableType() == ETableSource.function && table.getFuncCall() != null) { 248 TFunctionCall function = table.getFuncCall(); 249 this.fullName = function.getFunctionName().toString(); 250 this.name = function.getFunctionName().toString(); 251 setAlias(table.getAliasName()); 252 setCurrentAlias(table.getAliasName()); 253 } else { 254 if (table.getSubquery() != null && table.getAliasClause() != null) { 255 this.name = table.getAliasClause().getAliasName().toString(); 256 this.subType = SubType.alias_table; 257 } else if (table.getTableName() != null) { 258 this.name = table.getTableName().toString(); 259 } else { 260 this.name = table.getName(); 261 } 262 263 this.fullName = table.getFullName(); 264 if (this.fullName == null) { 265 this.fullName = this.name; 266 } 267 268 setAlias(table.getAliasName()); 269 setCurrentAlias(table.getAliasName()); 270 } 271 272 if (name.startsWith("`") && name.endsWith("`") && name.indexOf(".") != -1 273 && ModelBindingManager.getGlobalOption().getVendor() == EDbVendor.dbvbigquery) { 274 this.name = SQLUtil.trimColumnStringQuote(name); 275 this.fullName = SQLUtil.trimColumnStringQuote(fullName); 276 } 277 278 if (table.getSubquery() != null) { 279 subquery = true; 280 } 281 282 EDbVendor vendor = ModelBindingManager.getGlobalOption().getVendor(); 283 boolean supportCatalog = TSQLEnv.supportCatalog(vendor); 284 boolean supportSchema = TSQLEnv.supportSchema(vendor); 285 286 if (table.getTableName().getDblink() == null) { 287 this.server = DlineageUtil.getTableServer(table.getTableName().toString()); 288 this.database = DlineageUtil.getTableDatabase(table.getTableName().toString()); 289 this.schema = DlineageUtil.getTableSchema(table.getTableName().toString()); 290 } 291 else { 292 if (this.server == null && !TSQLEnv.DEFAULT_SERVER_NAME.equals(getDefaultServer())) { 293 this.server = getDefaultServer(); 294 } 295 this.database = SQLUtil.normalizeIdentifier(vendor, ESQLDataObjectType.dotDblink, table.getTableName().getDblink().toString()); 296 if (!SQLUtil.isEmpty(table.getTableName().getSchemaString())) { 297 this.schema = table.getTableName().getSchemaString(); 298 } 299 this.subType = SubType.dblink; 300 this.name = this.name.replace(table.getTableName().getDblink().toString(), this.database); 301 ModelBindingManager.get().appendDblinkTable(this.name); 302 return; 303 } 304 305 fillSchemaInfo(); 306 307 if (SQLUtil.isEmpty(this.database)) { 308 this.database = DlineageUtil.getTableDatabase(table.getTableName().toString()); 309 if (SQLUtil.isEmpty(this.database) && ModelBindingManager.getGlobalOption().isShowImplicitSchema() 310 && !SQLUtil.isEmpty(table.getTableName().getImplictDatabaseString())) { 311 this.database = table.getTableName().getImplictDatabaseString(); 312 } 313 } 314 315 if (SQLUtil.isEmpty(this.schema)) { 316 this.schema = DlineageUtil.getTableSchema(table.getTableName().toString()); 317 if (SQLUtil.isEmpty(this.schema) && ModelBindingManager.getGlobalOption().isShowImplicitSchema() 318 && !SQLUtil.isEmpty(implicitSchemaOf(table.getTableName()))) { 319 this.schema = implicitSchemaOf(table.getTableName()); 320 } 321 } 322 323 if (!SQLUtil.isEmpty(this.database) && SQLUtil.isEmpty(this.schema)) { 324 if (!SQLUtil.isEmpty(implicitSchemaOf(table.getTableName()))) { 325 this.schema = implicitSchemaOf(table.getTableName()); 326 } 327 } 328 329 if (table.getTableType() == ETableSource.function) { 330 subType = SubType.function; 331 } 332 333 if (!supportCatalog) { 334 this.database = null; 335 } else if (this.database == null && !TSQLEnv.DEFAULT_DB_NAME.equals(getDefaultDatabase())) { 336 this.database = getDefaultDatabase(); 337 } 338 339 if (!supportSchema) { 340 this.schema = null; 341 } else if (this.schema == null && !TSQLEnv.DEFAULT_SCHEMA_NAME.equals(getDefaultSchema())) { 342 this.schema = getDefaultSchema(); 343 } 344 345 updateTableName(supportCatalog, supportSchema); 346 347 if (this.server == null && !TSQLEnv.DEFAULT_SERVER_NAME.equals(getDefaultServer())) { 348 this.server = getDefaultServer(); 349 } 350 } 351 352 public void addColumnsFromSQLEnv(){ 353 TSQLEnv sqlEnv = ModelBindingManager.getGlobalSQLEnv(); 354 if (sqlEnv == null) { 355 return; 356 } 357 TSQLTable tsqlTable = sqlEnv.searchTable(ModelFactory.getQualifiedTableName(this)); 358 if (tsqlTable != null && tsqlTable.getColumnList()!=null && !tsqlTable.getColumnList().isEmpty()) { 359 for(TSQLColumn tsqlColumn: tsqlTable.getColumnList()){ 360 TObjectName columnName = new TObjectName(); 361 columnName.setString(tsqlColumn.getName()); 362 new TableColumn(this, columnName); 363 } 364 this.setCreateTable(true); 365 this.setHasSQLEnv(true); 366 } 367 } 368 369 private void fillSchemaInfo() { 370 Stack<TCustomSqlStatement> stmtStack = ModelBindingManager.getGlobalStmtStack(); 371 if (!stmtStack.isEmpty()) { 372 TCustomSqlStatement currentStmt = stmtStack.peek(); 373 TCustomSqlStatement stmt = DlineageUtil.getTopStmt(stmtStack.peek()); 374 375 String sqlComment = null; 376 try { 377 sqlComment = stmt.getCommentBeforeNode(); 378 } catch (Exception e) { 379 } 380 if (!SQLUtil.isEmpty(sqlComment) && (sqlComment.indexOf("db") != -1 || sqlComment.indexOf("schema") != -1)) { 381 Properties properties = new Properties(); 382 try { 383 properties.load( 384 new ByteArrayInputStream(sqlComment.replace("--", "").trim().replace(",", "\n").getBytes())); 385 if (SQLUtil.isEmpty(this.server) && properties.containsKey("db-instance")) { 386 this.server = properties.getProperty("db-instance"); 387 } 388 if (SQLUtil.isEmpty(this.database) && properties.containsKey("db")) { 389 this.database = properties.getProperty("db"); 390 if(this.database.indexOf(".")!=-1) { 391 this.database = SQLUtil.quoteDottedName(ModelBindingManager.getGlobalOption().getVendor(), ESQLDataObjectType.dotCatalog, this.database); 392 } 393 } 394 if (SQLUtil.isEmpty(this.schema) && properties.containsKey("schema")) { 395 this.schema = properties.getProperty("schema"); 396 if(this.schema.indexOf(".")!=-1) { 397 this.schema = SQLUtil.quoteDottedName(ModelBindingManager.getGlobalOption().getVendor(), ESQLDataObjectType.dotSchema, this.schema); 398 } 399 } 400 } catch (IOException e) { 401 logger.error("load sql comment properties failed.", e); 402 } 403 } 404 405 if(stmt instanceof TCreateTableSqlStatement) { 406 TCreateTableSqlStatement createTableStmt = (TCreateTableSqlStatement)stmt; 407 if (createTableStmt.getTableKinds() != null 408 && (createTableStmt.getTableKinds().contains(ETableKind.etkTemporary) 409 || createTableStmt.getTableKinds().contains(ETableKind.etkTemp) 410 || createTableStmt.getTableKinds().contains(ETableKind.etkLocalTemporary) 411 || createTableStmt.getTableKinds().contains(ETableKind.etkLocalTemp) 412 || createTableStmt.getTableKinds().contains(ETableKind.etkGlobalTemporary) 413 || createTableStmt.getTableKinds().contains(ETableKind.etkGlobalTemp) 414 || createTableStmt.getTableKinds().contains(ETableKind.etkScopedTemporary))) { 415 if (this.tableObject == createTableStmt.getTargetTable()) { 416 setSubType(SubType.temp_table); 417 return; 418 } 419 } 420 } 421 422 if(currentStmt instanceof TCreateTableSqlStatement) { 423 TCreateTableSqlStatement createTableStmt = (TCreateTableSqlStatement)currentStmt; 424 if (createTableStmt.getTableKinds() != null 425 && (createTableStmt.getTableKinds().contains(ETableKind.etkTemporary) 426 || createTableStmt.getTableKinds().contains(ETableKind.etkTemp) 427 || createTableStmt.getTableKinds().contains(ETableKind.etkLocalTemporary) 428 || createTableStmt.getTableKinds().contains(ETableKind.etkLocalTemp) 429 || createTableStmt.getTableKinds().contains(ETableKind.etkGlobalTemporary) 430 || createTableStmt.getTableKinds().contains(ETableKind.etkGlobalTemp) 431 || createTableStmt.getTableKinds().contains(ETableKind.etkScopedTemporary))) { 432 if (this.tableObject == createTableStmt.getTargetTable()) { 433 setSubType(SubType.temp_table); 434 return; 435 } 436 } 437 } 438 439 if(stmt instanceof TCreateViewSqlStatement) { 440 TCreateViewSqlStatement createViewStmt = (TCreateViewSqlStatement)stmt; 441 if (createViewStmt.getTableKind() != null 442 && (createViewStmt.getTableKind() == (ETableKind.etkTemporary) 443 || createViewStmt.getTableKind() == (ETableKind.etkTemp) 444 || createViewStmt.getTableKind() == (ETableKind.etkLocalTemporary) 445 || createViewStmt.getTableKind() == (ETableKind.etkLocalTemp) 446 || createViewStmt.getTableKind() == (ETableKind.etkGlobalTemporary) 447 || createViewStmt.getTableKind() == (ETableKind.etkGlobalTemp))) { 448 if (this.viewName == createViewStmt.getViewName()) { 449 setSubType(SubType.temp_table); 450 return; 451 } 452 } 453 } 454 455 if(currentStmt instanceof TCreateViewSqlStatement) { 456 TCreateViewSqlStatement createViewStmt = (TCreateViewSqlStatement)currentStmt; 457 if (createViewStmt.getTableKind() != null 458 && (createViewStmt.getTableKind() == (ETableKind.etkTemporary) 459 || createViewStmt.getTableKind() == (ETableKind.etkTemp) 460 || createViewStmt.getTableKind() == (ETableKind.etkLocalTemporary) 461 || createViewStmt.getTableKind() == (ETableKind.etkLocalTemp) 462 || createViewStmt.getTableKind() == (ETableKind.etkGlobalTemporary) 463 || createViewStmt.getTableKind() == (ETableKind.etkGlobalTemp))) { 464 if (this.viewName == createViewStmt.getViewName()) { 465 setSubType(SubType.temp_table); 466 return; 467 } 468 } 469 } 470 } 471 472 473 if (SQLUtil.isEmpty(this.database) || this.database.equals(ModelBindingManager.getGlobalDatabase())) { 474 TSQLEnv sqlEnv = ModelBindingManager.getGlobalSQLEnv(); 475 if (sqlEnv == null) { 476 return; 477 } 478 int occurrence = 0; 479 int maxPriority = -1; 480 String tableDatabase = null; 481 String tableSchema = null; 482 Map<Integer, List<String>> databaseSchemasMap = new HashMap<Integer, List<String>>(); 483 // Every cross-schema hit for the bare name, grouped by priority, so a name 484 // that is ambiguous across schemas (and which the rules below cannot resolve 485 // to a single winner) can be surfaced as a candidate set instead of being 486 // stamped with a non-existent default schema. 487 Map<Integer, List<String[]>> candidatesByPriority = new TreeMap<Integer, List<String[]>>(); 488 for (TSQLCatalog catalog : sqlEnv.getCatalogList()) { 489 if (SQLUtil.isEmpty(schema) || this.schema.equals(ModelBindingManager.getGlobalSchema())) { 490 for (TSQLSchema schema : catalog.getSchemaList()) { 491 TSQLSchemaObject tsqlTable = schema.findTable(DlineageUtil.getSimpleTableName(this.name)); 492 if(tsqlTable == null){ 493 tsqlTable = schema.findSynonyms(DlineageUtil.getSimpleTableName(this.name)); 494 } 495 if (tsqlTable != null) { 496 occurrence += 1; 497 String candDb = quoteDottedSegment(catalog.getName()); 498 String candSchema = quoteDottedSegment(schema.getName()); 499 String candName = candDb + "." + candSchema + "." 500 + DlineageUtil.getSimpleTableName(this.name); 501 int candPriority = tsqlTable.getPriority(); 502 if (!candidatesByPriority.containsKey(candPriority)) { 503 candidatesByPriority.put(candPriority, new ArrayList<String[]>()); 504 } 505 candidatesByPriority.get(candPriority).add(new String[] { candDb, candSchema, candName }); 506 if (tsqlTable.getPriority() > maxPriority 507 || (maxPriority > 0 && tsqlTable.getPriority() == maxPriority)) { 508 tableDatabase = catalog.getName(); 509 if(tableDatabase.indexOf(".")!=-1) { 510 tableDatabase = SQLUtil.quoteDottedName(ModelBindingManager.getGlobalOption().getVendor(), ESQLDataObjectType.dotCatalog, tableDatabase); 511 } 512 tableSchema = schema.getName(); 513 if(tableSchema.indexOf(".")!=-1) { 514 tableSchema = SQLUtil.quoteDottedName(ModelBindingManager.getGlobalOption().getVendor(), ESQLDataObjectType.dotSchema, tableSchema); 515 } 516 maxPriority = tsqlTable.getPriority(); 517 if(!databaseSchemasMap.containsKey(maxPriority)) { 518 databaseSchemasMap.put(maxPriority, new ArrayList<String>()); 519 } 520 databaseSchemasMap.get(maxPriority).add(tableDatabase + "___" + tableSchema); 521 } 522 } 523 } 524// if (occurrence > 1) { 525// break; 526// } 527 } else { 528 TSQLSchema schema = catalog.getSchema(this.schema, false); 529 if (schema != null) { 530 TSQLSchemaObject tsqlTable = schema.findTable(DlineageUtil.getSimpleTableName(this.name)); 531 if(tsqlTable == null){ 532 tsqlTable = schema.findSynonyms(DlineageUtil.getSimpleTableName(this.name)); 533 } 534 if (tsqlTable != null) { 535 occurrence += 1; 536 if (tsqlTable.getPriority() > maxPriority 537 || (maxPriority > 0 && tsqlTable.getPriority() == maxPriority)) { 538 tableDatabase = catalog.getName(); 539 if(tableDatabase.indexOf(".")!=-1) { 540 tableDatabase = SQLUtil.quoteDottedName(ModelBindingManager.getGlobalOption().getVendor(), ESQLDataObjectType.dotCatalog, tableDatabase); 541 } 542 tableSchema = schema.getName(); 543 if(tableSchema.indexOf(".")!=-1) { 544 tableSchema = SQLUtil.quoteDottedName(ModelBindingManager.getGlobalOption().getVendor(), ESQLDataObjectType.dotSchema, tableSchema); 545 } 546 maxPriority = tsqlTable.getPriority(); 547 if(!databaseSchemasMap.containsKey(maxPriority)) { 548 databaseSchemasMap.put(maxPriority, new ArrayList<String>()); 549 } 550 databaseSchemasMap.get(maxPriority).add(tableDatabase + "___" + tableSchema); 551 } 552 } 553 } 554 } 555 } 556 if (occurrence == 1 || (maxPriority > 0 && databaseSchemasMap.get(maxPriority).size() == 1)) { 557 if (this.database == null && tableDatabase != null && !TSQLEnv.DEFAULT_DB_NAME.equalsIgnoreCase(tableDatabase)) { 558 this.database = tableDatabase; 559 } 560 if (this.schema == null && tableSchema != null && !TSQLEnv.DEFAULT_SCHEMA_NAME.equalsIgnoreCase(tableSchema)) { 561 this.schema = tableSchema; 562 } 563 } else if ((this.schema == null || this.schema.equals(ModelBindingManager.getGlobalSchema())) 564 && !candidatesByPriority.isEmpty()) { 565 // The existing rules could not pick a single winner and the schema is 566 // still only a default (null or the global default). Look at the 567 // top-priority group: when it holds two or more schemas AND none of 568 // them is the default schema, the unqualified name is genuinely 569 // ambiguous. Flag it and record the candidate set so the output layer 570 // can surface the reference unqualified instead of fabricating a 571 // non-existent default-schema object. A match in the default schema 572 // (e.g. an existing dbo.orders) is left to win through the normal 573 // stamping path. 574 int maxCandPriority = Collections.max(candidatesByPriority.keySet()); 575 List<String[]> topCandidates = candidatesByPriority.get(maxCandPriority); 576 String defaultSchema = getDefaultSchema(); 577 boolean defaultSchemaHasObject = false; 578 for (String[] cand : topCandidates) { 579 if (cand[1] != null && DlineageUtil.sameName(ESQLDataObjectType.dotSchema, cand[1], defaultSchema)) { 580 defaultSchemaHasObject = true; 581 break; 582 } 583 } 584 if (!defaultSchemaHasObject && topCandidates.size() >= 2) { 585 List<String> candidates = new ArrayList<String>(); 586 for (String[] cand : topCandidates) { 587 candidates.add(cand[2]); 588 } 589 this.candidateTables = candidates; 590 this.ambiguousUnqualifiedTable = true; 591 } 592 } 593 } else if (SQLUtil.isEmpty(this.schema) || this.schema.equals(ModelBindingManager.getGlobalSchema())) { 594 TSQLEnv sqlEnv = ModelBindingManager.getGlobalSQLEnv(); 595 if (sqlEnv == null) { 596 return; 597 } 598 int occurrence = 0; 599 int maxPriority = -1; 600 String tableDatabase = null; 601 String tableSchema = null; 602 Map<Integer, List<String>> databaseSchemasMap = new HashMap<Integer, List<String>>(); 603 TSQLCatalog catalog = sqlEnv.searchCatalog(this.database); 604 if (catalog != null) { 605 for (TSQLSchema schema : catalog.getSchemaList()) { 606 TSQLSchemaObject tsqlTable = schema.findTable(DlineageUtil.getSimpleTableName(this.name)); 607 if(tsqlTable == null){ 608 tsqlTable = schema.findSynonyms(DlineageUtil.getSimpleTableName(this.name)); 609 } 610 if (tsqlTable != null) { 611 occurrence += 1; 612 if (tsqlTable.getPriority() > maxPriority 613 || (maxPriority > 0 && tsqlTable.getPriority() == maxPriority)) { 614 tableDatabase = catalog.getName(); 615 if (tableDatabase.indexOf(".") != -1) { 616 tableDatabase = SQLUtil.quoteDottedName(ModelBindingManager.getGlobalOption().getVendor(), ESQLDataObjectType.dotCatalog, tableDatabase); 617 } 618 tableSchema = schema.getName(); 619 if (tableSchema.indexOf(".") != -1) { 620 tableSchema = SQLUtil.quoteDottedName(ModelBindingManager.getGlobalOption().getVendor(), ESQLDataObjectType.dotSchema, tableSchema); 621 } 622 maxPriority = tsqlTable.getPriority(); 623 if(!databaseSchemasMap.containsKey(maxPriority)) { 624 databaseSchemasMap.put(maxPriority, new ArrayList<String>()); 625 } 626 databaseSchemasMap.get(maxPriority).add(tableDatabase + "___" + tableSchema); 627 } 628 } 629 } 630 } 631 632 if (occurrence == 1 || (maxPriority > 0 && databaseSchemasMap.get(maxPriority).size() == 1)) { 633 if (this.database == null && tableDatabase != null && !TSQLEnv.DEFAULT_DB_NAME.equalsIgnoreCase(tableDatabase)) { 634 this.database = tableDatabase; 635 } 636 if (this.schema == null && tableSchema != null && !TSQLEnv.DEFAULT_SCHEMA_NAME.equalsIgnoreCase(tableSchema)) { 637 this.schema = tableSchema; 638 } 639 } 640 641 if (SQLUtil.isEmpty(this.schema) && !SQLUtil.isEmpty(this.database)) { 642 this.schema = getDefaultSchema(); 643 } 644 } 645 } 646 647 public Table(TObjectName tableName) { 648 if (tableName == null) { 649 throw new IllegalArgumentException("Table arguments can't be null."); 650 } 651 652 id = ++ModelBindingManager.get().TABLE_COLUMN_ID; 653 654 this.viewName = tableName; 655 656 setPosition(tableName); 657 658 this.fullName = tableName.toString(); 659 this.name = tableName.toString(); 660 if (SQLUtil.isEmpty(name)) { 661 name = tableName.toString(); 662 } 663 664 if (name.startsWith("`") && name.endsWith("`") && name.indexOf(".") != -1 665 && ModelBindingManager.getGlobalOption().getVendor() == EDbVendor.dbvbigquery) { 666 this.name = SQLUtil.trimColumnStringQuote(name); 667 this.fullName = SQLUtil.trimColumnStringQuote(fullName); 668 } 669 670 this.server = DlineageUtil.getTableServer(tableName.toString()); 671 this.database = DlineageUtil.getTableDatabase(tableName.toString()); 672 this.schema = DlineageUtil.getTableSchema(tableName.toString()); 673 674 fillSchemaInfo(); 675 676 EDbVendor vendor = ModelBindingManager.getGlobalOption().getVendor(); 677 boolean supportCatalog = TSQLEnv.supportCatalog(vendor); 678 boolean supportSchema = TSQLEnv.supportSchema(vendor); 679 680 if (supportCatalog && SQLUtil.isEmpty(this.database)) { 681 this.database = DlineageUtil.getTableDatabase(tableName.toString()); 682 if (SQLUtil.isEmpty(this.database) && ModelBindingManager.getGlobalOption().isShowImplicitSchema() 683 && !SQLUtil.isEmpty(tableName.getImplictDatabaseString())) { 684 this.database = tableName.getImplictDatabaseString(); 685 } 686 } 687 688 if (supportSchema && SQLUtil.isEmpty(this.schema)) { 689 this.schema = DlineageUtil.getTableSchema(tableName.toString()); 690 if (SQLUtil.isEmpty(this.schema) && ModelBindingManager.getGlobalOption().isShowImplicitSchema() 691 && !SQLUtil.isEmpty(implicitSchemaOf(tableName))) { 692 this.schema = implicitSchemaOf(tableName); 693 } 694 } 695 696 if (supportSchema && !SQLUtil.isEmpty(this.database) && SQLUtil.isEmpty(this.schema)) { 697 if (!SQLUtil.isEmpty(implicitSchemaOf(tableName))) { 698 this.schema = implicitSchemaOf(tableName); 699 } else { 700 this.schema = getDefaultSchema(); 701 } 702 } 703 704 if (!supportCatalog) { 705 this.database = null; 706 } else if (this.database == null && !TSQLEnv.DEFAULT_DB_NAME.equals(getDefaultDatabase())) { 707 this.database = getDefaultDatabase(); 708 } 709 710 if (!supportSchema) { 711 this.schema = null; 712 } else if (this.schema == null && !TSQLEnv.DEFAULT_SCHEMA_NAME.equals(getDefaultSchema())) { 713 this.schema = getDefaultSchema(); 714 } 715 716 updateTableName(supportCatalog, supportSchema); 717 718 if (this.server == null && !TSQLEnv.DEFAULT_SERVER_NAME.equals(getDefaultServer())) { 719 this.server = getDefaultServer(); 720 } 721 } 722 723 protected String getDefaultServer() { 724 String defaultServer = null; 725 if (ModelBindingManager.getGlobalSQLEnv() != null) { 726 defaultServer = ModelBindingManager.getGlobalSQLEnv().getDefaultServerName(); 727 } 728 if (!SQLUtil.isEmpty(defaultServer)) 729 return defaultServer; 730 return TSQLEnv.DEFAULT_SERVER_NAME; 731 } 732 733 /** 734 * The implicit schema stamped on {@code name}, or null when the statement 735 * being analyzed has no current schema because a Snowflake 736 * {@code USE <database>} unset it. 737 * 738 * <p>The stamp is applied by the resolvers from the TSQLEnv's default schema, 739 * which is batch-global: it holds the LAST {@code USE SCHEMA} of the whole 740 * script even for statements that run after a database switch invalidated it. 741 * Reading it unfiltered put the previous database's schema back on names the 742 * rest of this class had already left unqualified - a CREATE VIEW target 743 * reported {@code D2.S1.v} while its own source table reported 744 * {@code D2.DEFAULT.orders}. See GitHub #715 / MantisBT 4677. 745 */ 746 private static String implicitSchemaOf(TObjectName name) { 747 if (name == null || ModelBindingManager.isGlobalSchemaUnset()) { 748 return null; 749 } 750 return name.getImplictSchemaString(); 751 } 752 753 protected String getDefaultSchema() { 754 return ModelBindingManager.getEffectiveDefaultSchema(); 755 } 756 757 protected String getDefaultDatabase() { 758 return ModelBindingManager.getEffectiveDefaultDatabase(); 759 } 760 761 /** 762 * @return true when this one-part reference is ambiguous across two or more 763 * schemas and was intentionally left unqualified (no default schema 764 * stamped). The candidates are available via {@link #getCandidateTables()}. 765 */ 766 public boolean isAmbiguousUnqualifiedTable() { 767 return ambiguousUnqualifiedTable; 768 } 769 770 /** 771 * @return the qualified candidate table names (e.g. {@code testdb.s1.orders}, 772 * {@code testdb.s2.orders}) when this reference is ambiguous across 773 * schemas, or null when the reference resolved normally. 774 */ 775 public List<String> getCandidateTables() { 776 return candidateTables; 777 } 778 779 /** 780 * Quote a catalog/schema segment the same way the surrounding resolution code 781 * does (lines around the occurrence loop), so a name containing a dot is kept 782 * as a single delimited segment. 783 */ 784 private static String quoteDottedSegment(String segment) { 785 if (segment != null && segment.indexOf('.') != -1) { 786 // catalog and schema segments share NAME_GROUP quote rules 787 return SQLUtil.quoteDottedName(ModelBindingManager.getGlobalOption().getVendor(), 788 ESQLDataObjectType.dotSchema, segment); 789 } 790 return segment; 791 } 792 793 public Table(String tableName) { 794 if (tableName == null) { 795 throw new IllegalArgumentException("Table arguments can't be null."); 796 } 797 798 id = ++ModelBindingManager.get().TABLE_COLUMN_ID; 799 800 this.startPosition = new Pair3<Long, Long, String>(-1L, -1L, ModelBindingManager.getGlobalHash()); 801 this.endPosition = new Pair3<Long, Long, String>(-1L, -1L, ModelBindingManager.getGlobalHash()); 802 803 this.fullName = tableName; 804 this.name = tableName; 805 if (SQLUtil.isEmpty(name)) { 806 name = tableName; 807 } 808 809 if (name.startsWith("`") && name.endsWith("`") && name.indexOf(".") != -1 810 && ModelBindingManager.getGlobalOption().getVendor() == EDbVendor.dbvbigquery) { 811 this.name = SQLUtil.trimColumnStringQuote(name); 812 this.fullName = SQLUtil.trimColumnStringQuote(fullName); 813 } 814 815 EDbVendor vendor = ModelBindingManager.getGlobalOption().getVendor(); 816 boolean supportCatalog = TSQLEnv.supportCatalog(vendor); 817 boolean supportSchema = TSQLEnv.supportSchema(vendor); 818 819 fillSchemaInfo(); 820 821 this.server = DlineageUtil.getTableServer(tableName); 822 823 if (supportCatalog && SQLUtil.isEmpty(this.database)) { 824 this.database = DlineageUtil.getTableDatabase(tableName); 825 } 826 if (supportSchema && SQLUtil.isEmpty(this.schema)) { 827 this.schema = DlineageUtil.getTableSchema(tableName); 828 } 829 if (supportSchema && !SQLUtil.isEmpty(this.database) && SQLUtil.isEmpty(this.schema)) { 830 this.schema = getDefaultSchema(); 831 } 832 833 if (!supportCatalog) { 834 this.database = null; 835 } else if (this.database == null && !TSQLEnv.DEFAULT_DB_NAME.equals(getDefaultDatabase())) { 836 this.database = getDefaultDatabase(); 837 } 838 839 if (!supportSchema) { 840 this.schema = null; 841 } else if (this.schema == null && !TSQLEnv.DEFAULT_SCHEMA_NAME.equals(getDefaultSchema())) { 842 this.schema = getDefaultSchema(); 843 } 844 845 updateTableName(supportCatalog, supportSchema); 846 847 if (this.server == null && !TSQLEnv.DEFAULT_SERVER_NAME.equals(getDefaultServer())) { 848 this.server = getDefaultServer(); 849 } 850 } 851 852 protected void updateTableName(boolean supportCatalog, boolean supportSchema) { 853 List<String> segments = SQLUtil.parseNames(this.name); 854 this.name = segments.get(segments.size() - 1); 855 if (supportCatalog && supportSchema) { 856 StringBuilder builder = new StringBuilder(); 857 if (segments.size() > 2) { 858 builder.append(segments.get(segments.size() - 3)).append("."); 859 } else { 860 if (!SQLUtil.isEmpty(this.database) && !TSQLEnv.DEFAULT_DB_NAME.equalsIgnoreCase(this.database)) { 861 builder.append(this.database).append("."); 862 } 863 } 864 if (segments.size() > 1) { 865 builder.append(segments.get(segments.size() - 2)).append("."); 866 } else { 867 if (builder.length() > 0) { 868 if (this.schema == null) { 869 if (ModelBindingManager.getGlobalVendor() == EDbVendor.dbvmssql) { 870 this.schema = "dbo"; 871 } else { 872 this.schema = getDefaultSchema(); 873 } 874 } 875 else if (TSQLEnv.DEFAULT_SCHEMA_NAME.equalsIgnoreCase(this.schema) 876 && ModelBindingManager.getGlobalVendor() == EDbVendor.dbvmssql) { 877 this.schema = "dbo"; 878 } 879 builder.append(this.schema).append("."); 880 } else { 881 if (!SQLUtil.isEmpty(this.schema) && !TSQLEnv.DEFAULT_SCHEMA_NAME.equalsIgnoreCase(this.schema)) { 882 builder.append(this.schema).append("."); 883 } 884 } 885 } 886 builder.append(this.name); 887 this.name = builder.toString(); 888 } else if (supportCatalog) { 889 if (segments.size() > 1) { 890 this.name = segments.get(segments.size() - 2) + "." + this.name; 891 } 892 else { 893 if (!SQLUtil.isEmpty(this.database) && !TSQLEnv.DEFAULT_DB_NAME.equalsIgnoreCase(this.database)) { 894 this.name = this.database + "." + this.name; 895 } 896 } 897 } else if (supportSchema) { 898 if (segments.size() > 1) { 899 this.name = segments.get(segments.size() - 2) + "." + this.name; 900 } else { 901 if (!SQLUtil.isEmpty(this.schema) && !TSQLEnv.DEFAULT_SCHEMA_NAME.equalsIgnoreCase(this.schema)) { 902 this.name = this.schema + "." + this.name; 903 } 904 } 905 } 906 907 if (this.server != null && !this.server.equals(getDefaultServer())) { 908 this.name = this.server + "." + this.name; 909 } 910 } 911 912 public long getId() { 913 return id; 914 } 915 916 public String getName() { 917 return name; 918 } 919 920 public void setName(String name) { 921 EDbVendor vendor = ModelBindingManager.getGlobalOption().getVendor(); 922 boolean supportCatalog = TSQLEnv.supportCatalog(vendor); 923 boolean supportSchema = TSQLEnv.supportSchema(vendor); 924 if (!supportCatalog) { 925 this.database = null; 926 } else if (this.database == null && !TSQLEnv.DEFAULT_DB_NAME.equals(getDefaultDatabase())) { 927 this.database = getDefaultDatabase(); 928 } 929 930 if (!supportSchema) { 931 this.schema = null; 932 } else if (this.schema == null && !TSQLEnv.DEFAULT_SCHEMA_NAME.equals(getDefaultSchema())) { 933 this.schema = getDefaultSchema(); 934 } 935 936 this.name = name; 937 938 updateTableName(supportCatalog, supportSchema); 939 } 940 941 public Pair3<Long, Long, String> getStartPosition() { 942 return startPosition; 943 } 944 945 public Pair3<Long, Long, String> getEndPosition() { 946 return endPosition; 947 } 948 949 public List<TableColumn> getColumns() { 950 return columns; 951 } 952 953 public void addColumn(TableColumn column) { 954 if (column != null && !this.columns.contains(column)) { 955 this.columns.add(column); 956 } 957 } 958 959 public TTable getTableObject() { 960 return tableObject; 961 } 962 963 public String getAlias() { 964 String aliasString = Arrays.toString(this.alias.toArray(new String[0])); 965 return aliasString.substring(1, aliasString.length() - 1); 966 } 967 968 public void setAlias(String alias) { 969 if (!SQLUtil.isEmpty(alias)) { 970 this.alias.add(alias); 971 } 972 } 973 974 public String getFullName() { 975 return fullName; 976 } 977 978 979 public boolean hasSubquery() { 980 return subquery; 981 } 982 983 public String getParent() { 984 return parent; 985 } 986 987 public void setParent(String parent) { 988 this.parent = parent; 989 } 990 991// public TObjectName getTableName() { 992// return tableName; 993// } 994 995 public String getDatabase() { 996 return database; 997 } 998 999 public String getSchema() { 1000 return schema; 1001 } 1002 1003 public TableRelationRows getRelationRows() { 1004 return relationRows; 1005 } 1006 1007 public boolean isCreateTable() { 1008 return isCreateTable; 1009 } 1010 1011 public void setCreateTable(boolean isCreateTable, boolean fromDDL) { 1012 Stack<TCustomSqlStatement> stmtStack = ModelBindingManager.getGlobalStmtStack(); 1013 TCustomSqlStatement stmt = null; 1014 if (stmtStack != null && !stmtStack.isEmpty()) { 1015 stmt = stmtStack.peek(); 1016 } 1017 if (stmt != null && stmt.getClass().getSimpleName().startsWith("TCreate")) { 1018 Pair<Pair3<Long, Long, String>, Pair3<Long, Long, String>> position = setPosition(stmt); 1019 if (position != null) { 1020 this.startPosition = position.first; 1021 this.endPosition = position.second; 1022 } 1023 } 1024 this.isCreateTable = isCreateTable; 1025 if (isCreateTable) { 1026 for (TableColumn column : columns) { 1027 column.setShowStar(false); 1028 } 1029 setDetermined(true); 1030 if (fromDDL) { 1031 setFromDDL(true); 1032 } 1033 } 1034 } 1035 1036 public void setCreateTable(boolean isCreateTable) { 1037 setCreateTable(isCreateTable, false); 1038 } 1039 1040 public boolean isView() { 1041 return isView; 1042 } 1043 1044 public void setView(boolean isView) { 1045 this.isView = isView; 1046 } 1047 1048 public boolean isPseudo() { 1049 return isPseudo; 1050 } 1051 1052 public void setPseudo(boolean isPseudo) { 1053 this.isPseudo = isPseudo; 1054 } 1055 1056 public boolean isExternal() { 1057 return isExternal; 1058 } 1059 1060 public void setExternal(boolean isExternal) { 1061 this.isExternal = isExternal; 1062 } 1063 1064 public boolean isStage() { 1065 return isStage; 1066 } 1067 1068 public void setStage(boolean isStage) { 1069 this.isStage = isStage; 1070 } 1071 1072 public boolean isSequence() { 1073 return isSequence; 1074 } 1075 1076 public void setSequence(boolean sequence) { 1077 isSequence = sequence; 1078 } 1079 1080 public boolean isVariable() { 1081 return isVariable; 1082 } 1083 1084 public void setVariable(boolean isVariable) { 1085 this.isVariable = isVariable; 1086 } 1087 1088 public boolean isConstant() { 1089 return isConstant; 1090 } 1091 1092 public void setConstant(boolean isConstant) { 1093 this.isConstant = isConstant; 1094 } 1095 1096 public boolean isTarget() { 1097 return isTarget; 1098 } 1099 1100 public void setTarget(boolean isTarget) { 1101 this.isTarget = isTarget; 1102 } 1103 1104 public String getProcedureId() { 1105 return procedureId; 1106 } 1107 1108 public void setProcedureId(String procedureId) { 1109 this.procedureId = procedureId; 1110 } 1111 1112 public boolean isPath() { 1113 return isPath; 1114 } 1115 1116 public void setPath(boolean isPath) { 1117 this.isPath = isPath; 1118 if(isPath) { 1119 String filePathDatabase = ModelBindingManager.getGlobalOption().getFilePathDatabase(); 1120 String filePathSchema = ModelBindingManager.getGlobalOption().getFilePathSchema(); 1121 if(SQLUtil.isEmpty(filePathDatabase)) { 1122 this.database = null; 1123 } 1124 else { 1125 this.database = filePathDatabase; 1126 } 1127 if(SQLUtil.isEmpty(filePathSchema)) { 1128 this.schema = null; 1129 } 1130 else { 1131 this.schema = filePathSchema; 1132 } 1133 1134 EDbVendor vendor = ModelBindingManager.getGlobalOption().getVendor(); 1135 boolean supportCatalog = TSQLEnv.supportCatalog(vendor); 1136 boolean supportSchema = TSQLEnv.supportSchema(vendor); 1137 if (!supportCatalog) { 1138 this.database = null; 1139 } else if (this.database == null && !TSQLEnv.DEFAULT_DB_NAME.equals(getDefaultDatabase())) { 1140 this.database = getDefaultDatabase(); 1141 } 1142 1143 if (!supportSchema) { 1144 this.schema = null; 1145 } else if (this.schema == null && !TSQLEnv.DEFAULT_SCHEMA_NAME.equals(getDefaultSchema())) { 1146 this.schema = getDefaultSchema(); 1147 } 1148 1149 updateTableName(supportCatalog, supportSchema); 1150 } 1151 } 1152 1153 public boolean isCursor() { 1154 return isVariable && getSubType() == SubType.cursor; 1155 } 1156 1157 public String getLocation() { 1158 return location; 1159 } 1160 1161 public void setLocation(String location) { 1162 this.location = location; 1163 } 1164 1165 public String getFileType() { 1166 return fileType; 1167 } 1168 1169 public void setFileType(String fileType) { 1170 this.fileType = fileType; 1171 } 1172 1173 public String getDisplayName() { 1174 return displayName; 1175 } 1176 1177 public void setDisplayName(String displayName) { 1178 this.displayName = displayName; 1179 } 1180 1181 public String getFileFormat() { 1182 return fileFormat; 1183 } 1184 1185 public void setFileFormat(String fileFormat) { 1186 this.fileFormat = fileFormat; 1187 } 1188 1189 public List<Process> getProcesses() { 1190 return processes; 1191 } 1192 1193 public void addProcess(Process process) { 1194 if (processes == null) { 1195 processes = new ArrayList<Process>(); 1196 } 1197 if (process != null && !processes.contains(process)) { 1198 processes.add(process); 1199 } 1200 } 1201 1202 public void removeProcess(Process process) { 1203 if (processes == null) { 1204 processes = new ArrayList<Process>(); 1205 } 1206 if (process != null && processes.contains(process)) { 1207 processes.remove(process); 1208 } 1209 } 1210 1211 public void setSubType(SubType subType) { 1212 if (this.subType == null || subType == SubType.record_type) { 1213 this.subType = subType; 1214 } 1215 } 1216 1217 public SubType getSubType() { 1218 return subType; 1219 } 1220 1221 public boolean isStream() { 1222 return isStream; 1223 } 1224 1225 public void setStream(boolean isStream) { 1226 this.isStream = isStream; 1227 } 1228 1229 public boolean isSchema() { 1230 return isSchema; 1231 } 1232 1233 public void setSchema(boolean isSchema) { 1234 this.isSchema = isSchema; 1235 } 1236 1237 public void setDatabase(String database) { 1238 this.database = database; 1239 } 1240 1241 public boolean isDatabase() { 1242 return isDatabase; 1243 } 1244 1245 public void setDatabase(boolean isDatabase) { 1246 this.isDatabase = isDatabase; 1247 } 1248 1249 public boolean isDataSource() { 1250 return isDataSource; 1251 } 1252 1253 public void setDataSource(boolean dataSource) { 1254 isDataSource = dataSource; 1255 } 1256 1257 public String getStarStmt() { 1258 return starStmt; 1259 } 1260 1261 public void setStarStmt(String starStmt) { 1262 this.starStmt = starStmt; 1263 } 1264 1265 public void setHasSQLEnv(boolean hasSQLEnv) { 1266 this.hasSQLEnv = hasSQLEnv; 1267 } 1268 1269 public boolean hasSQLEnv() { 1270 return hasSQLEnv; 1271 } 1272 1273 public String getCurrentAlias() { 1274 return currentAlias; 1275 } 1276 1277 public void setCurrentAlias(String currentAlias) { 1278 if (SQLUtil.isEmpty(currentAlias)) { 1279 this.currentAlias = null; 1280 } else 1281 this.currentAlias = currentAlias; 1282 } 1283 1284 public Set<String> getAliasList() { 1285 return alias; 1286 } 1287 1288 public String getServer() { 1289 return server; 1290 } 1291 1292 public boolean isDetermined() { 1293 return isDetermined; 1294 } 1295 1296 public void setDetermined(boolean isDetermined) { 1297 this.isDetermined = isDetermined; 1298 } 1299 1300 public boolean isFromDDL() { 1301 return fromDDL; 1302 } 1303 1304 public void setFromDDL(boolean fromDDL) { 1305 this.fromDDL = fromDDL; 1306 } 1307 1308 /** 1309 * Whether this endpoint is created by the analyzed SQL. A parse fact, NOT gated on 1310 * {@link #isDetermined()}; true for CREATE TABLE / SELECT INTO / CTAS / clone 1311 * targets even when the defining query's schema could not be resolved. 1312 */ 1313 public boolean isCreatedInSql() { 1314 return createdInSql; 1315 } 1316 1317 public void setCreatedInSql(boolean createdInSql) { 1318 this.createdInSql = createdInSql; 1319 } 1320 1321 public EndpointIntroduction getEndpointIntroduction() { 1322 return endpointIntroduction; 1323 } 1324 1325 /** 1326 * Records how the endpoint was introduced. First non-UNKNOWN writer wins, so the 1327 * structural creation site (set early at build time) is not later clobbered. 1328 */ 1329 public void setEndpointIntroduction(EndpointIntroduction endpointIntroduction) { 1330 if (endpointIntroduction == null) { 1331 return; 1332 } 1333 if (this.endpointIntroduction == null || this.endpointIntroduction == EndpointIntroduction.UNKNOWN) { 1334 this.endpointIntroduction = endpointIntroduction; 1335 } 1336 } 1337 1338 /** 1339 * Existing schema/model resolution state, exposed under the endpoint-classification 1340 * name. Mirrors {@link #isDetermined()} exactly; introduces no new resolution logic. 1341 */ 1342 public boolean isResolved() { 1343 return isDetermined; 1344 } 1345 1346 /** 1347 * Delimiter-normalized, dialect-aware form of {@link #getName()} (brackets / quotes 1348 * stripped per identifier segment). Computed on access from the current name; does 1349 * NOT mutate {@link #getName()}, which stays raw. Returns the raw name when no 1350 * normalization is possible (e.g. no global vendor context). 1351 */ 1352 public String getNormalizedName() { 1353 if (name == null) { 1354 return null; 1355 } 1356 try { 1357 String normalized = DlineageUtil.getIdentifierNormalTableName(name); 1358 return normalized == null ? name : normalized; 1359 } catch (Exception e) { 1360 return name; 1361 } 1362 } 1363 1364 /** 1365 * Records that this endpoint's identifier was assembled at runtime by a dynamic-SQL 1366 * fold. {@code parts} is the outermost-first, pipe-joined set of affected name 1367 * segments; {@code identityTemplated} says the hole reaches the leading character of 1368 * the object-name segment (so a {@code #}/{@code @} prefix there is fold text, not a 1369 * real temp / table-variable marker). Additive across repeated marks: parts union, 1370 * identity ORs — one folded statement can reach the same endpoint twice. 1371 * 1372 * <p>Callers MUST only invoke this from inside a dynamic-fold context; see 1373 * {@link EndpointKind#DYNAMIC_TEMPLATE}. 1374 */ 1375 public void markDynamicTemplate(String parts, boolean identityTemplated) { 1376 if (parts != null && parts.length() > 0) { 1377 if (this.templatedParts == null) { 1378 this.templatedParts = parts; 1379 } else if (!this.templatedParts.equals(parts)) { 1380 java.util.LinkedHashSet<String> merged = new java.util.LinkedHashSet<String>(); 1381 for (String part : TEMPLATED_PART_ORDER) { 1382 if (containsPart(this.templatedParts, part) || containsPart(parts, part)) { 1383 merged.add(part); 1384 } 1385 } 1386 StringBuilder sb = new StringBuilder(); 1387 for (String part : merged) { 1388 if (sb.length() > 0) { 1389 sb.append('|'); 1390 } 1391 sb.append(part); 1392 } 1393 this.templatedParts = sb.toString(); 1394 } 1395 } 1396 this.templatedIdentity = this.templatedIdentity || identityTemplated; 1397 } 1398 1399 /** Canonical outermost-first ordering of the segment labels used by {@link #getTemplatedParts()}. */ 1400 private static final String[] TEMPLATED_PART_ORDER = { "server", "db", "schema", "name" }; 1401 1402 private static boolean containsPart(String parts, String part) { 1403 if (parts == null) { 1404 return false; 1405 } 1406 for (String candidate : parts.split("\\|")) { 1407 // non-identifier-compare: fixed internal label vocabulary, not a DB object name 1408 if (candidate.equals(part)) { 1409 return true; 1410 } 1411 } 1412 return false; 1413 } 1414 1415 /** 1416 * Which identifier segments of {@link #getName()} were assembled at runtime, pipe-joined 1417 * outermost-first ({@code "db"}, {@code "db|name"}, ...); {@code null} when this endpoint 1418 * is not runtime-templated. Lets a consumer match a templated skeleton against a 1419 * later-resolved concrete name without re-parsing the name text. 1420 */ 1421 public String getTemplatedParts() { 1422 return templatedParts; 1423 } 1424 1425 /** True when this endpoint's identifier was assembled at runtime by a dynamic-SQL fold. */ 1426 public boolean isDynamicTemplate() { 1427 return templatedParts != null; 1428 } 1429 1430 /** 1431 * Withdraws a dynamic-template mark. Used when the SAME node turns out to also be 1432 * referenced by a STATIC statement (by-name model reuse): the static reference provably 1433 * names a real object, so publishing the template claim on the shared node would make 1434 * that reference wrong — see the shared-node contract in 1435 * docs/tmp/dynamic-template-endpoint-provenance.md. 1436 */ 1437 public void clearDynamicTemplate() { 1438 this.templatedParts = null; 1439 this.templatedIdentity = false; 1440 } 1441 1442 /** 1443 * Authoritative {@link EndpointKind}, computed on access from the current (raw) 1444 * name and the active dialect. Quote-robust: each identifier segment is delimiter- 1445 * stripped before the {@code #}/{@code ##}/{@code @}/{@code tempdb} tests. This 1446 * reads only the new classification; it never inspects or mutates {@link #subType}. 1447 * 1448 * <p>§5.6 minimum scope: distinguishes T-SQL temp / global-temp / table-variable / 1449 * tempdb objects from plain catalog objects. CTE / derived / TVF / constant kinds 1450 * are deferred and reported as {@link EndpointKind#UNKNOWN} here. 1451 */ 1452 public static boolean isTsqlFamilyVendor(EDbVendor vendor) { 1453 return vendor == EDbVendor.dbvmssql || vendor == EDbVendor.dbvazuresql 1454 || vendor == EDbVendor.dbvsybase || vendor == EDbVendor.dbvsybasease 1455 || vendor == EDbVendor.dbvsqlanywhere || vendor == EDbVendor.dbvsybaseiq; 1456 } 1457 1458 public EndpointKind getEndpointKind() { 1459 if (SQLUtil.isEmpty(name)) { 1460 return EndpointKind.UNKNOWN; 1461 } 1462 1463 EDbVendor vendor = null; 1464 if (ModelBindingManager.getGlobalOption() != null) { 1465 vendor = ModelBindingManager.getGlobalOption().getVendor(); 1466 } 1467 1468 if (isTsqlFamilyVendor(vendor)) { 1469 List<String> segments = SQLUtil.parseNames(name); 1470 if (!segments.isEmpty()) { 1471 String last = SQLUtil.trimColumnStringQuote(segments.get(segments.size() - 1).trim()); 1472 // A '#'/'##'/'@' prefix that came out of a dynamic fold hole is runtime text, 1473 // not evidence of a temp table / table variable: 'INSERT INTO ' + QUOTENAME(@tab) 1474 // folds to a table literally named @tab, which is a runtime-built CATALOG name. 1475 // Only then does DYNAMIC_TEMPLATE displace the temp family; a genuinely literal 1476 // '#tmp_' prefix keeps its temp kind (the templated segments stay readable via 1477 // getTemplatedParts()), because losing TEMP would send a consumer to the catalog. 1478 if (last.startsWith("##")) { 1479 return templatedIdentity ? EndpointKind.DYNAMIC_TEMPLATE : EndpointKind.GLOBAL_TEMP_TABLE; 1480 } 1481 if (last.startsWith("#")) { 1482 return templatedIdentity ? EndpointKind.DYNAMIC_TEMPLATE : EndpointKind.LOCAL_TEMP_TABLE; 1483 } 1484 if (last.startsWith("@")) { 1485 return templatedIdentity ? EndpointKind.DYNAMIC_TEMPLATE : EndpointKind.TABLE_VARIABLE; 1486 } 1487 String first = SQLUtil.trimColumnStringQuote(segments.get(0).trim()); 1488 if (segments.size() > 1 && "tempdb".equalsIgnoreCase(first)) { 1489 // A literal leading "tempdb" proves the object lives in tempdb whatever the 1490 // later segments template to, so the kind stands and templatedParts carries 1491 // the rest of the story. 1492 return EndpointKind.TEMPDB_OBJECT; 1493 } 1494 } 1495 } 1496 1497 // A runtime-assembled identifier is never a catalog object name: report the 1498 // template, so a consumer does not bind it (or, worse, match an unrelated object 1499 // that happens to share the folded spelling). Marked only inside a fold context. 1500 if (templatedParts != null) { 1501 return EndpointKind.DYNAMIC_TEMPLATE; 1502 } 1503 1504 // Only a plain real table is a catalog object here. Resultsets / derived / 1505 // views / variables / stages etc. are deferred kinds — leave them UNKNOWN 1506 // rather than mislabel them CATALOG_OBJECT. 1507 if (!subquery && !isView && !isPseudo && !isStage && !isSequence && !isStream 1508 && !isVariable && !isConstant && !isDatabase && !isSchema && !isDataSource 1509 && subType != SubType.alias_table && subType != SubType.values_table 1510 && subType != SubType.synonym && subType != SubType.function 1511 && subType != SubType.unnest) { 1512 return EndpointKind.CATALOG_OBJECT; 1513 } 1514 1515 return EndpointKind.UNKNOWN; 1516 } 1517 1518 private Pair<Pair3<Long, Long, String>, Pair3<Long, Long, String>> setPositionInternal(TSourceToken startToken, TSourceToken endToken) { 1519 if (startToken == null || endToken == null) { 1520 return null; 1521 } 1522 1523 Pair3<Long, Long, String> startPosition = new Pair3<Long, Long, String>(startToken.lineNo, startToken.columnNo, 1524 ModelBindingManager.getGlobalHash()); 1525 Pair3<Long, Long, String> endPosition = new Pair3<Long, Long, String>(endToken.lineNo, endToken.columnNo + SQLUtil.endTrim(endToken.getAstext()).length(), 1526 ModelBindingManager.getGlobalHash()); 1527 1528 if(this.startPosition == null && this.endPosition == null) { 1529 this.startPosition = startPosition; 1530 this.endPosition = endPosition; 1531 } 1532 1533 boolean traceTablePosition = ModelBindingManager.getGlobalOption().isTraceTablePosition(); 1534 1535 1536 Pair<Pair3<Long, Long, String>, Pair3<Long, Long, String>> position = Pair.create(startPosition, endPosition); 1537 1538 if (traceTablePosition) { 1539 positions.add(position); 1540 } 1541 1542 return position; 1543 } 1544 1545 public Pair<Pair3<Long, Long, String>, Pair3<Long, Long, String>> setPosition(TTable table) { 1546 if (table == null) { 1547 return null; 1548 } 1549 1550 TSourceToken startToken = table.getStartToken(); 1551 TSourceToken endToken = table.getEndToken(); 1552 if (startToken == null && table.getSubquery() != null) { 1553 startToken = table.getSubquery().getStartToken(); 1554 endToken = table.getSubquery().getEndToken(); 1555 } 1556 1557 return setPositionInternal(startToken, endToken); 1558 } 1559 1560 public Pair<Pair3<Long, Long, String>, Pair3<Long, Long, String>> setPosition(TObjectName tableName) { 1561 if (tableName == null) { 1562 return null; 1563 } 1564 1565 TSourceToken startToken = tableName.getStartToken(); 1566 TSourceToken endToken = tableName.getEndToken(); 1567 1568 return setPositionInternal(startToken, endToken); 1569 } 1570 1571 public Pair<Pair3<Long, Long, String>, Pair3<Long, Long, String>> setPosition(TCustomSqlStatement stmt) { 1572 if (stmt == null) { 1573 return null; 1574 } 1575 1576 TSourceToken startToken = stmt.getStartToken(); 1577 TSourceToken endToken = stmt.getEndToken(); 1578 1579 return setPositionInternal(startToken, endToken); 1580 } 1581 1582 public Set<Pair<Pair3<Long, Long, String>, Pair3<Long, Long, String>>> getPositions() { 1583 return positions; 1584 } 1585 1586 public TableColumn findColumn(String columnName) { 1587 if (columns == null) { 1588 return null; 1589 } 1590 for (TableColumn column : columns) { 1591 if (DlineageUtil.compareColumnIdentifier(column.getName(), columnName)) { 1592 return column; 1593 } 1594 } 1595 return null; 1596 } 1597 1598 public void removeColumn(TableColumn column) { 1599 if (columns != null && columns.contains(column)) { 1600 columns.remove(column); 1601 } 1602 } 1603 1604 public void removeColumn(String columnName) { 1605 TableColumn column = findColumn(columnName); 1606 if (column != null) { 1607 removeColumn(column); 1608 } 1609 } 1610}