001 002package gudusoft.gsqlparser.dlineage.dataflow.model; 003 004import gudusoft.gsqlparser.EDbVendor; 005import gudusoft.gsqlparser.ETableEffectType; 006import gudusoft.gsqlparser.ETableKind; 007import gudusoft.gsqlparser.ETableSource; 008import gudusoft.gsqlparser.TCustomSqlStatement; 009import gudusoft.gsqlparser.TSourceToken; 010import gudusoft.gsqlparser.dlineage.util.DlineageUtil; 011import gudusoft.gsqlparser.dlineage.util.LegacyDlineageWireNamesV1; 012import gudusoft.gsqlparser.dlineage.util.TableIdentityKey; 013import gudusoft.gsqlparser.dlineage.util.Pair; 014import gudusoft.gsqlparser.dlineage.util.Pair3; 015import gudusoft.gsqlparser.nodes.TFunctionCall; 016import gudusoft.gsqlparser.nodes.TObjectName; 017import gudusoft.gsqlparser.nodes.TTable; 018import gudusoft.gsqlparser.sqlenv.*; 019import gudusoft.gsqlparser.stmt.TCreateTableSqlStatement; 020import gudusoft.gsqlparser.stmt.TCreateViewSqlStatement; 021import gudusoft.gsqlparser.util.Logger; 022import gudusoft.gsqlparser.util.LoggerFactory; 023import gudusoft.gsqlparser.util.SQLUtil; 024 025import java.io.ByteArrayInputStream; 026import java.io.IOException; 027import java.util.*; 028 029public class Table { 030 031 private static final Logger logger = LoggerFactory.getLogger(Table.class); 032 033 private long id; 034 protected String server; 035 protected String database; 036 protected String schema; 037 protected String name; 038 protected String displayName; 039 protected String fullName; 040 // Captured in the occurrence's context, before rendering/quote removal. 041 private QualifiedCanonKey identifierKey; 042 043 public QualifiedCanonKey getIdentifierKey() { 044 return identifierKey; 045 } 046 047 private void captureIdentifierKey(String lexicalName, TSQLTable resolved) { 048 if (ModelBindingManager.getGlobalVendor() != EDbVendor.dbvbigquery) return; 049 identifierKey = resolved == null || !inSpelledProject(lexicalName, resolved) 050 ? (QualifiedCanonKey) DlineageUtil.getTableIdentityKey(lexicalName) 051 : IdentifierService.qualifiedCanonKeyFromStoredStatic(EDbVendor.dbvbigquery, 052 ESQLDataObjectType.dotTable, Arrays.asList(resolved.getSchema().getCatalog().getName(), 053 resolved.getSchema().getName(), resolved.getName())); 054 } 055 056 /** 057 * Whether the catalog's table is in the project the name spells, when it spells one. The catalog lends a 058 * reference that names a project the columns of a table declared without one (Mantis 4751), not that table's 059 * identity: {@code db1.s1.t1} and {@code db2.s1.t1} stay two tables beside the declared {@code s1.t1}. 060 */ 061 private static boolean inSpelledProject(String lexicalName, TSQLTable resolved) { 062 List<String> segments = QualifiedNameHierarchy.split(EDbVendor.dbvbigquery, lexicalName); 063 if (segments.size() < 3) return true; 064 IdentifierService service = ModelBindingManager.getGlobalIdentifierService(); 065 if (service == null) service = IdentifierService.defaultServiceFor(EDbVendor.dbvbigquery); 066 return service.canonKeyOf(segments.get(0), ESQLDataObjectType.dotCatalog, IdentifierInputForm.SQL_LEXICAL) 067 .equals(service.canonKeyOf(resolved.getSchema().getCatalog().getName(), ESQLDataObjectType.dotCatalog, 068 IdentifierInputForm.CATALOG_STORED)); 069 } 070 071 /** 072 * Binding identity, separate from legacy display getters: the table's identity under the 073 * analysis's identifier rules ({@link #getCanonicalIdentity()}) with the legacy folded name bound 074 * alongside, or the legacy name alone for a table the rules give no identity. 075 */ 076 public Object getBindingKey() { 077 if (ModelBindingManager.getGlobalVendor() == EDbVendor.dbvbigquery) { 078 return getIdentifierKey(); 079 } 080 String legacyKey = DlineageUtil.getTableFullName(getName()); 081 QualifiedCanonKey identity = getCanonicalIdentity(); 082 return identity == null ? legacyKey : new TableIdentityKey(identity, legacyKey); 083 } 084 085 /** 086 * The form each level of the name that the reference did not spell arrived in, recorded where 087 * the level is produced. Null is a SQL spelling: a session default, a {@code USE}, a resolver's 088 * stamp, a file-path option. {@code CATALOG_STORED} is a name the catalog holds: the schema a 089 * catalog search found an unqualified name in ({@link TObjectName#getImplictSchemaForm()}), or a 090 * catalog or synonym match in {@link #fillSchemaInfo()}. The identity is minted from these, never 091 * by re-reading the display name, which shows a stored name bare. 092 */ 093 private IdentifierInputForm databaseForm; 094 private IdentifierInputForm schemaForm; 095 /** How many trailing segments of the composed name the reference itself spelled; -1 until composed. */ 096 private int spelledSegments = -1; 097 /** The identity last minted, and the name it was minted for. */ 098 private QualifiedCanonKey mintedIdentity; 099 private String mintedFor; 100 101 private void assignDatabase(String database, IdentifierInputForm form) { 102 this.database = database; 103 // The global default is handed over as the very object ModelBindingManager holds (by 104 // DlineageUtil.getTableDatabase, or through the session default the bootstrap set from it), 105 // and a stored one keeps its form: a reference check, provenance rather than name equality. 106 if (database != null && form == null && database == ModelBindingManager.getGlobalDatabase()) { 107 form = ModelBindingManager.getGlobalDatabaseForm(); 108 } 109 this.databaseForm = database == null ? null : form; 110 } 111 112 private void assignSchema(String schema, IdentifierInputForm form) { 113 this.schema = schema; 114 if (schema != null && form == null && schema == ModelBindingManager.getGlobalSchema()) { 115 form = ModelBindingManager.getGlobalSchemaForm(); 116 } 117 this.schemaForm = schema == null ? null : form; 118 } 119 120 private static IdentifierInputForm implicitSchemaForm(TObjectName name) { 121 return name != null && name.getImplictSchemaForm() == IdentifierInputForm.CATALOG_STORED 122 ? IdentifierInputForm.CATALOG_STORED : null; 123 } 124 125 private static IdentifierInputForm implicitDatabaseForm(TObjectName name) { 126 return name != null && name.getImplictDatabaseForm() == IdentifierInputForm.CATALOG_STORED 127 ? IdentifierInputForm.CATALOG_STORED : null; 128 } 129 130 /** 131 * A catalog's stored name as it appears in the composed name: stored, unless the caller already 132 * delimited it ({@code SQLUtil.quoteDottedName} for a stored name with a dot), which makes it the SQL 133 * spelling of that name. 134 */ 135 private static IdentifierInputForm storedUnlessDelimited(ESQLDataObjectType role, String name) { 136 if (name == null) { 137 return null; 138 } 139 IdentifierService service = ModelBindingManager.getGlobalIdentifierService(); 140 IdentifierProfile profile = service != null ? service.getProfile() 141 : IdentifierProfile.forVendor(ModelBindingManager.getGlobalVendor(), IdentifierProfile.VendorFlags.defaults()); 142 return IdentifierCodec.isQuoted(profile, role, name) ? null : IdentifierInputForm.CATALOG_STORED; 143 } 144 145 /** 146 * The table's identity under the analysis's identifier rules: BigQuery's captured key, else a key 147 * minted from the composed name's segments, each read in the form it arrived in. Levels the 148 * reference spelled, and levels a SQL spelling supplied, are read as SQL; a level the catalog 149 * supplied is read as the stored name it is. Minted during analysis and kept until the name 150 * changes, so the output merge sees the identity the model was bound under. Null for a name the 151 * rules give no identity: a database link, a shape the vendor's hierarchy cannot split, or a 152 * segment that is not an identifier. 153 */ 154 public QualifiedCanonKey getCanonicalIdentity() { 155 if (identifierKey != null) { 156 return identifierKey; 157 } 158 if (name == null) { 159 return null; 160 } 161 if (mintedFor != name) { // reference check: re-mint only when the name itself changed 162 mintedIdentity = mintIdentity(); 163 mintedFor = name; 164 } 165 return mintedIdentity; 166 } 167 168 /** 169 * The table's identity read in another object role of the same level ({@code dotSynonyms} for a name that may 170 * name a synonym): the same segments, forms and rules as {@link #getCanonicalIdentity()}, the last segment under 171 * that role's rules. A per-object profile may give synonyms rules of their own, so a synonym is matched in its 172 * own role. Null when the rules give the name no identity. 173 */ 174 public QualifiedCanonKey getCanonicalIdentityAs(ESQLDataObjectType role) { 175 if (role == null || role == ESQLDataObjectType.dotTable) { 176 return getCanonicalIdentity(); 177 } 178 return name == null ? null : mintIdentity(role); 179 } 180 181 private QualifiedCanonKey mintIdentity() { 182 return mintIdentity(ESQLDataObjectType.dotTable); 183 } 184 185 private QualifiedCanonKey mintIdentity(ESQLDataObjectType role) { 186 IdentifierService service = ModelBindingManager.getGlobalIdentifierService(); 187 if (service == null || subType == SubType.dblink 188 || (ModelBindingManager.get() != null && ModelBindingManager.get().isDblinkTable(name))) { 189 return null; 190 } 191 IdentifierProfile profile = service.getProfile(); 192 EDbVendor vendor = profile.getVendor(); 193 if (!QualifiedNameHierarchy.hasHierarchyMapping(vendor, role)) { 194 return null; 195 } 196 List<QualifiedNameHierarchy.Segment> plan = QualifiedNameHierarchy.plan(vendor, role, 197 QualifiedNameHierarchy.split(vendor, DlineageUtil.getTableFullNameAsWritten(name))); 198 if (plan == null) { 199 return null; 200 } 201 int spelled = spelledSegments < 0 ? plan.size() : Math.min(spelledSegments, plan.size()); 202 List<String> texts = new ArrayList<String>(plan.size()); 203 List<IdentifierInputForm> forms = new ArrayList<IdentifierInputForm>(plan.size()); 204 for (int i = 0; i < plan.size(); i++) { 205 QualifiedNameHierarchy.Segment segment = plan.get(i); 206 IdentifierInputForm form = IdentifierInputForm.SQL_LEXICAL; 207 if (i < plan.size() - spelled && !IdentifierCodec.isQuoted(profile, segment.getType(), segment.getText())) { 208 IdentifierInputForm produced = segment.getType() == ESQLDataObjectType.dotSchema ? schemaForm 209 : segment.getType() == ESQLDataObjectType.dotCatalog ? databaseForm : null; 210 if (produced == IdentifierInputForm.CATALOG_STORED) { 211 form = IdentifierInputForm.CATALOG_STORED; 212 } 213 } 214 if (form == IdentifierInputForm.SQL_LEXICAL 215 && !IdentifierCodec.isVendorValidSpelling(profile, segment.getType(), segment.getText())) { 216 return null; 217 } 218 texts.add(segment.getText()); 219 forms.add(form); 220 } 221 try { 222 return service.qualifiedCanonKeyFromSegments(texts, forms, role); 223 } catch (RuntimeException noIdentity) { 224 return null; 225 } 226 } 227 protected Set<String> alias = new LinkedHashSet<String>(); 228 protected Set<Pair<Pair3<Long, Long, String>, Pair3<Long, Long, String>>> positions = new LinkedHashSet<>(); 229 protected String parent; 230 protected Pair3<Long, Long, String> startPosition; 231 protected Pair3<Long, Long, String> endPosition; 232 private List<TableColumn> columns = new ArrayList<TableColumn>(); 233 private boolean subquery = false; 234 235 /** 236 * Set when an unqualified one-part name matches the same base table in two or 237 * more schemas of the (otherwise unambiguous) default catalog and the default 238 * schema holds no such object. In that case GSP must NOT fabricate a 239 * non-existent default-schema object; it leaves the reference unqualified and 240 * exposes the candidate set instead. See 241 * docs/tmp/unqualified-name-default-schema-masks-cross-schema-ambiguity.md. 242 */ 243 private boolean ambiguousUnqualifiedTable = false; 244 private List<String> candidateTables; 245 246 private TTable tableObject; 247 private TObjectName viewName; 248 private boolean isCreateTable; 249 private boolean isView; 250 private boolean isPseudo; 251 private boolean isExternal; 252 private boolean isStage; 253 private boolean isSequence; 254 private boolean isPath; 255 private boolean isStream; 256 private boolean isVariable; 257 private boolean isConstant; 258 private boolean isTarget; 259 private String procedureId; 260 private boolean isDatabase; 261 private boolean isSchema; 262 private boolean isDataSource; 263 private String location; 264 private String dataSourceName; 265 private String pathUri; 266 private String fileType; 267 private String fileFormat; 268 private List<Process> processes; 269 private SubType subType; 270 private String starStmt; 271 private boolean hasSQLEnv = false; 272 private String currentAlias; 273 private boolean isDetermined; 274 275 private boolean fromDDL; 276 277 /** 278 * How this endpoint was introduced in the analyzed SQL. The create-effect values 279 * (CREATE_TABLE_DDL / SELECT_INTO / CTAS / CLONE_TABLE) are set as parse facts at 280 * build time, independent of {@link #isDetermined()} — unlike {@link #fromDDL}. 281 * Strictly additive; never back-fills {@link #subType}. See 282 * docs/tmp/dlineage-authoritative-endpoint-classification.md. 283 */ 284 private EndpointIntroduction endpointIntroduction = EndpointIntroduction.UNKNOWN; 285 286 /** 287 * True when this endpoint is created by the analyzed SQL (CREATE TABLE / SELECT 288 * INTO / CTAS / clone target), set regardless of schema resolution. 289 */ 290 private boolean createdInSql; 291 292 /** 293 * Pipe-joined, outermost-first list of the identifier segments of {@link #name} that 294 * overlap an unresolved dynamic-SQL hole ({@code "db"}, {@code "db|name"}, ...), or 295 * {@code null} when this endpoint is not runtime-templated. Written ONLY from inside a 296 * dynamic-fold context by the analyzer, so a static reference to a legally delimited 297 * {@code [weird@name]} is never marked. See 298 * docs/tmp/dynamic-template-endpoint-provenance.md. 299 */ 300 private String templatedParts; 301 302 /** 303 * True when the hole covers the LEADING character of the object-name segment — i.e. 304 * the {@code #}/{@code ##}/{@code @} prefix that would otherwise classify this endpoint 305 * as a temp table / table variable is itself runtime text, so that classification is a 306 * fold artifact rather than a fact. Drives the {@link EndpointKind#DYNAMIC_TEMPLATE} 307 * precedence in {@link #getEndpointKind()}. 308 */ 309 private boolean templatedIdentity; 310 311 private TableRelationRows relationRows = new TableRelationRows(this); 312 313 private TCustomSqlStatement stmt; 314 315 public Table(TCustomSqlStatement stmt, TObjectName viewName) { 316 this(viewName); 317 this.stmt = stmt; 318 if (stmt != null) { 319 setPosition(stmt); 320 } 321 } 322 323 public TCustomSqlStatement getSqlStatement() { 324 return stmt; 325 } 326 327 public Table(TTable table, String tableName) { 328 if (table == null) { 329 throw new IllegalArgumentException("Table arguments can't be null."); 330 } 331 332 id = ++ModelBindingManager.get().TABLE_COLUMN_ID; 333 334 this.tableObject = table; 335 336 setPosition(table); 337 338 this.name = tableName; 339 this.fullName = tableName; 340 setAlias(table.getAliasName()); 341 setCurrentAlias(table.getAliasName()); 342 343 captureIdentifierKey(tableName, null); 344 345 if (name.startsWith("`") && name.endsWith("`") && name.indexOf(".") != -1 346 && ModelBindingManager.getGlobalOption().getVendor() == EDbVendor.dbvbigquery) { 347 this.name = SQLUtil.trimColumnStringQuote(name); 348 this.fullName = SQLUtil.trimColumnStringQuote(fullName); 349 } 350 351 if (table.getSubquery() != null) { 352 subquery = true; 353 } 354 355 EDbVendor vendor = ModelBindingManager.getGlobalOption().getVendor(); 356 boolean supportCatalog = TSQLEnv.supportCatalog(vendor); 357 boolean supportSchema = TSQLEnv.supportSchema(vendor); 358 359 if (table.getTableName().getDblink() == null) { 360 this.server = DlineageUtil.getTableServer(table.getTableName().toString()); 361 assignDatabase(DlineageUtil.getTableDatabase(table.getTableName().toString()), null); 362 assignSchema(DlineageUtil.getTableSchema(table.getTableName().toString()), null); 363 } 364 else { 365 if (this.server == null && !TSQLEnv.DEFAULT_SERVER_NAME.equals(getDefaultServer())) { 366 this.server = getDefaultServer(); 367 } 368 assignDatabase(LegacyDlineageWireNamesV1.segment(vendor, ESQLDataObjectType.dotDblink, table.getTableName().getDblink().toString()), null); 369 if (!SQLUtil.isEmpty(table.getTableName().getSchemaString())) { 370 assignSchema(table.getTableName().getSchemaString(), null); 371 } 372 this.subType = SubType.dblink; 373 this.name = this.name.replace(table.getTableName().getDblink().toString(), this.database); 374 ModelBindingManager.get().appendDblinkTable(this.name); 375 return; 376 } 377 378 fillSchemaInfo(); 379 380 if (SQLUtil.isEmpty(this.database)) { 381 assignDatabase(DlineageUtil.getTableDatabase(table.getTableName().toString()), null); 382 if (SQLUtil.isEmpty(this.database) && ModelBindingManager.getGlobalOption().isShowImplicitSchema() 383 && !SQLUtil.isEmpty(table.getTableName().getImplictDatabaseString())) { 384 assignDatabase(table.getTableName().getImplictDatabaseString(), implicitDatabaseForm(table.getTableName())); 385 } 386 } 387 388 if (SQLUtil.isEmpty(this.schema)) { 389 assignSchema(DlineageUtil.getTableSchema(table.getTableName().toString()), null); 390 if (SQLUtil.isEmpty(this.schema) && ModelBindingManager.getGlobalOption().isShowImplicitSchema() 391 && !SQLUtil.isEmpty(implicitSchemaOf(table.getTableName()))) { 392 assignSchema(implicitSchemaOf(table.getTableName()), implicitSchemaForm(table.getTableName())); 393 } 394 } 395 396 if (!SQLUtil.isEmpty(this.database) && SQLUtil.isEmpty(this.schema)) { 397 if (!SQLUtil.isEmpty(implicitSchemaOf(table.getTableName()))) { 398 assignSchema(implicitSchemaOf(table.getTableName()), implicitSchemaForm(table.getTableName())); 399 } 400 } 401 402 if (table.getTableType() == ETableSource.function) { 403 subType = SubType.function; 404 } 405 406 if (!supportCatalog) { 407 assignDatabase(null, null); 408 } else if (this.database == null && !TSQLEnv.DEFAULT_DB_NAME.equals(getDefaultDatabase())) { 409 assignDatabase(getDefaultDatabase(), null); 410 } 411 412 if (!supportSchema) { 413 assignSchema(null, null); 414 } else if (this.schema == null && !TSQLEnv.DEFAULT_SCHEMA_NAME.equals(getDefaultSchema())) { 415 assignSchema(getDefaultSchema(), null); 416 } 417 418 updateTableName(supportCatalog, supportSchema); 419 420 if (this.server == null && !TSQLEnv.DEFAULT_SERVER_NAME.equals(getDefaultServer())) { 421 this.server = getDefaultServer(); 422 } 423 } 424 425 public Table(TTable table) { 426 if (table == null) { 427 throw new IllegalArgumentException("Table arguments can't be null."); 428 } 429 430 id = ++ModelBindingManager.get().TABLE_COLUMN_ID; 431 432 this.tableObject = table; 433 434 setPosition(table); 435 436 if (table.getLinkTable() != null) { 437 this.fullName = table.getLinkTable().getFullName(); 438 this.name = table.getLinkTable().getName(); 439 setAlias(table.getName()); 440 setCurrentAlias(table.getName()); 441 } else if (table.getTableType() == ETableSource.function && table.getFuncCall() != null) { 442 TFunctionCall function = table.getFuncCall(); 443 this.fullName = function.getFunctionName().toString(); 444 this.name = function.getFunctionName().toString(); 445 setAlias(table.getAliasName()); 446 setCurrentAlias(table.getAliasName()); 447 } else { 448 if (table.getSubquery() != null && table.getAliasClause() != null) { 449 this.name = table.getAliasClause().getAliasName().toString(); 450 this.subType = SubType.alias_table; 451 } else if (table.getTableName() != null) { 452 this.name = table.getTableName().toString(); 453 } else { 454 this.name = table.getName(); 455 } 456 457 this.fullName = table.getFullName(); 458 if (this.fullName == null) { 459 this.fullName = this.name; 460 } 461 462 setAlias(table.getAliasName()); 463 setCurrentAlias(table.getAliasName()); 464 } 465 466 captureIdentifierKey(table.getLinkTable() == null ? this.name 467 : table.getLinkTable().getTableName().toString(), table.getResolvedTable()); 468 469 if (name.startsWith("`") && name.endsWith("`") && name.indexOf(".") != -1 470 && ModelBindingManager.getGlobalOption().getVendor() == EDbVendor.dbvbigquery) { 471 this.name = SQLUtil.trimColumnStringQuote(name); 472 this.fullName = SQLUtil.trimColumnStringQuote(fullName); 473 } 474 475 if (table.getSubquery() != null) { 476 subquery = true; 477 } 478 479 EDbVendor vendor = ModelBindingManager.getGlobalOption().getVendor(); 480 boolean supportCatalog = TSQLEnv.supportCatalog(vendor); 481 boolean supportSchema = TSQLEnv.supportSchema(vendor); 482 483 if (table.getTableName().getDblink() == null) { 484 this.server = DlineageUtil.getTableServer(table.getTableName().toString()); 485 assignDatabase(DlineageUtil.getTableDatabase(table.getTableName().toString()), null); 486 assignSchema(DlineageUtil.getTableSchema(table.getTableName().toString()), null); 487 } 488 else { 489 if (this.server == null && !TSQLEnv.DEFAULT_SERVER_NAME.equals(getDefaultServer())) { 490 this.server = getDefaultServer(); 491 } 492 assignDatabase(LegacyDlineageWireNamesV1.segment(vendor, ESQLDataObjectType.dotDblink, table.getTableName().getDblink().toString()), null); 493 if (!SQLUtil.isEmpty(table.getTableName().getSchemaString())) { 494 assignSchema(table.getTableName().getSchemaString(), null); 495 } 496 this.subType = SubType.dblink; 497 this.name = this.name.replace(table.getTableName().getDblink().toString(), this.database); 498 ModelBindingManager.get().appendDblinkTable(this.name); 499 return; 500 } 501 502 fillSchemaInfo(); 503 504 if (SQLUtil.isEmpty(this.database)) { 505 assignDatabase(DlineageUtil.getTableDatabase(table.getTableName().toString()), null); 506 if (SQLUtil.isEmpty(this.database) && ModelBindingManager.getGlobalOption().isShowImplicitSchema() 507 && !SQLUtil.isEmpty(table.getTableName().getImplictDatabaseString())) { 508 assignDatabase(table.getTableName().getImplictDatabaseString(), implicitDatabaseForm(table.getTableName())); 509 } 510 } 511 512 if (SQLUtil.isEmpty(this.schema)) { 513 assignSchema(DlineageUtil.getTableSchema(table.getTableName().toString()), null); 514 if (SQLUtil.isEmpty(this.schema) && ModelBindingManager.getGlobalOption().isShowImplicitSchema() 515 && !SQLUtil.isEmpty(implicitSchemaOf(table.getTableName()))) { 516 assignSchema(implicitSchemaOf(table.getTableName()), implicitSchemaForm(table.getTableName())); 517 } 518 } 519 520 if (!SQLUtil.isEmpty(this.database) && SQLUtil.isEmpty(this.schema)) { 521 if (!SQLUtil.isEmpty(implicitSchemaOf(table.getTableName()))) { 522 assignSchema(implicitSchemaOf(table.getTableName()), implicitSchemaForm(table.getTableName())); 523 } 524 } 525 526 if (table.getTableType() == ETableSource.function) { 527 subType = SubType.function; 528 } 529 530 if (!supportCatalog) { 531 assignDatabase(null, null); 532 } else if (this.database == null && !TSQLEnv.DEFAULT_DB_NAME.equals(getDefaultDatabase())) { 533 assignDatabase(getDefaultDatabase(), null); 534 } 535 536 if (!supportSchema) { 537 assignSchema(null, null); 538 } else if (this.schema == null && !TSQLEnv.DEFAULT_SCHEMA_NAME.equals(getDefaultSchema())) { 539 assignSchema(getDefaultSchema(), null); 540 } 541 542 updateTableName(supportCatalog, supportSchema); 543 544 if (this.server == null && !TSQLEnv.DEFAULT_SERVER_NAME.equals(getDefaultServer())) { 545 this.server = getDefaultServer(); 546 } 547 } 548 549 public void addColumnsFromSQLEnv(){ 550 TSQLEnv sqlEnv = ModelBindingManager.getGlobalSQLEnv(); 551 if (sqlEnv == null) { 552 return; 553 } 554 TSQLTable tsqlTable = sqlEnv.searchTable(ModelFactory.getQualifiedTableName(this)); 555 if (tsqlTable != null && tsqlTable.getColumnList()!=null && !tsqlTable.getColumnList().isEmpty()) { 556 for(TSQLColumn tsqlColumn: tsqlTable.getColumnList()){ 557 TObjectName columnName; 558 if (tsqlColumn.isUnresolvedStar()) { 559 columnName = DlineageUtil.syntheticStarName(); 560 } else { 561 columnName = DeclaredColumnName.ofCatalog(tsqlColumn.getName(), 562 TSQLEnv.toLexical(ModelBindingManager.getGlobalVendor(), 563 ESQLDataObjectType.dotColumn, tsqlColumn.getName(), 564 gudusoft.gsqlparser.sqlenv.IdentifierInputForm.CATALOG_STORED)); 565 } 566 TableColumn column = new TableColumn(this, columnName); 567 column.name = tsqlColumn.getName(); 568 } 569 this.setCreateTable(true); 570 this.setHasSQLEnv(true); 571 } 572 } 573 574 private TSQLEnv metadataEnvironment() { 575 if (tableObject != null && tableObject.getSqlEnv() != null && ModelBindingManager.isScopedTemporary(tableObject)) { 576 // SELECT INTO introduces this spelling; its inferred shape does not qualify the name. 577 if (tableObject.getEffectType() == ETableEffectType.tetSelectInto) return null; 578 return tableObject.getSqlEnv(); 579 } 580 return ModelBindingManager.getGlobalSQLEnv(); 581 } 582 583 private void fillSchemaInfo() { 584 Stack<TCustomSqlStatement> stmtStack = ModelBindingManager.getGlobalStmtStack(); 585 if (!stmtStack.isEmpty()) { 586 TCustomSqlStatement currentStmt = stmtStack.peek(); 587 TCustomSqlStatement stmt = DlineageUtil.getTopStmt(stmtStack.peek()); 588 589 String sqlComment = null; 590 try { 591 sqlComment = stmt.getCommentBeforeNode(); 592 } catch (Exception e) { 593 } 594 if (!SQLUtil.isEmpty(sqlComment) && (sqlComment.indexOf("db") != -1 || sqlComment.indexOf("schema") != -1)) { 595 Properties properties = new Properties(); 596 try { 597 properties.load( 598 new ByteArrayInputStream(sqlComment.replace("--", "").trim().replace(",", "\n").getBytes())); 599 if (SQLUtil.isEmpty(this.server) && properties.containsKey("db-instance")) { 600 this.server = properties.getProperty("db-instance"); 601 } 602 if (SQLUtil.isEmpty(this.database) && properties.containsKey("db")) { 603 assignDatabase(properties.getProperty("db"), null); 604 if(this.database.indexOf(".")!=-1) { 605 assignDatabase(SQLUtil.quoteDottedName(ModelBindingManager.getGlobalOption().getVendor(), ESQLDataObjectType.dotCatalog, this.database), null); 606 } 607 } 608 if (SQLUtil.isEmpty(this.schema) && properties.containsKey("schema")) { 609 assignSchema(properties.getProperty("schema"), null); 610 if(this.schema.indexOf(".")!=-1) { 611 assignSchema(SQLUtil.quoteDottedName(ModelBindingManager.getGlobalOption().getVendor(), ESQLDataObjectType.dotSchema, this.schema), null); 612 } 613 } 614 } catch (IOException e) { 615 logger.error("load sql comment properties failed.", e); 616 } 617 } 618 619 if(stmt instanceof TCreateTableSqlStatement) { 620 TCreateTableSqlStatement createTableStmt = (TCreateTableSqlStatement)stmt; 621 if (createTableStmt.getTableKinds() != null 622 && (createTableStmt.getTableKinds().contains(ETableKind.etkTemporary) 623 || createTableStmt.getTableKinds().contains(ETableKind.etkTemp) 624 || createTableStmt.getTableKinds().contains(ETableKind.etkLocalTemporary) 625 || createTableStmt.getTableKinds().contains(ETableKind.etkLocalTemp) 626 || createTableStmt.getTableKinds().contains(ETableKind.etkGlobalTemporary) 627 || createTableStmt.getTableKinds().contains(ETableKind.etkGlobalTemp) 628 || createTableStmt.getTableKinds().contains(ETableKind.etkScopedTemporary))) { 629 if (this.tableObject == createTableStmt.getTargetTable()) { 630 setSubType(SubType.temp_table); 631 return; 632 } 633 } 634 } 635 636 if(currentStmt instanceof TCreateTableSqlStatement) { 637 TCreateTableSqlStatement createTableStmt = (TCreateTableSqlStatement)currentStmt; 638 if (createTableStmt.getTableKinds() != null 639 && (createTableStmt.getTableKinds().contains(ETableKind.etkTemporary) 640 || createTableStmt.getTableKinds().contains(ETableKind.etkTemp) 641 || createTableStmt.getTableKinds().contains(ETableKind.etkLocalTemporary) 642 || createTableStmt.getTableKinds().contains(ETableKind.etkLocalTemp) 643 || createTableStmt.getTableKinds().contains(ETableKind.etkGlobalTemporary) 644 || createTableStmt.getTableKinds().contains(ETableKind.etkGlobalTemp) 645 || createTableStmt.getTableKinds().contains(ETableKind.etkScopedTemporary))) { 646 if (this.tableObject == createTableStmt.getTargetTable()) { 647 setSubType(SubType.temp_table); 648 return; 649 } 650 } 651 } 652 653 if(stmt instanceof TCreateViewSqlStatement) { 654 TCreateViewSqlStatement createViewStmt = (TCreateViewSqlStatement)stmt; 655 if (createViewStmt.getTableKind() != null 656 && (createViewStmt.getTableKind() == (ETableKind.etkTemporary) 657 || createViewStmt.getTableKind() == (ETableKind.etkTemp) 658 || createViewStmt.getTableKind() == (ETableKind.etkLocalTemporary) 659 || createViewStmt.getTableKind() == (ETableKind.etkLocalTemp) 660 || createViewStmt.getTableKind() == (ETableKind.etkGlobalTemporary) 661 || createViewStmt.getTableKind() == (ETableKind.etkGlobalTemp))) { 662 if (this.viewName == createViewStmt.getViewName()) { 663 setSubType(SubType.temp_table); 664 return; 665 } 666 } 667 } 668 669 if(currentStmt instanceof TCreateViewSqlStatement) { 670 TCreateViewSqlStatement createViewStmt = (TCreateViewSqlStatement)currentStmt; 671 if (createViewStmt.getTableKind() != null 672 && (createViewStmt.getTableKind() == (ETableKind.etkTemporary) 673 || createViewStmt.getTableKind() == (ETableKind.etkTemp) 674 || createViewStmt.getTableKind() == (ETableKind.etkLocalTemporary) 675 || createViewStmt.getTableKind() == (ETableKind.etkLocalTemp) 676 || createViewStmt.getTableKind() == (ETableKind.etkGlobalTemporary) 677 || createViewStmt.getTableKind() == (ETableKind.etkGlobalTemp))) { 678 if (this.viewName == createViewStmt.getViewName()) { 679 setSubType(SubType.temp_table); 680 return; 681 } 682 } 683 } 684 } 685 686 687 if (SQLUtil.isEmpty(this.database) || this.database.equals(ModelBindingManager.getGlobalDatabase())) { 688 TSQLEnv sqlEnv = metadataEnvironment(); 689 if (sqlEnv == null) { 690 return; 691 } 692 int occurrence = 0; 693 int maxPriority = -1; 694 String tableDatabase = null; 695 String tableSchema = null; 696 Map<Integer, List<String>> databaseSchemasMap = new HashMap<Integer, List<String>>(); 697 // Every cross-schema hit for the bare name, grouped by priority, so a name 698 // that is ambiguous across schemas (and which the rules below cannot resolve 699 // to a single winner) can be surfaced as a candidate set instead of being 700 // stamped with a non-existent default schema. 701 Map<Integer, List<String[]>> candidatesByPriority = new TreeMap<Integer, List<String[]>>(); 702 for (TSQLCatalog catalog : sqlEnv.getCatalogList()) { 703 if (SQLUtil.isEmpty(schema) || this.schema.equals(ModelBindingManager.getGlobalSchema())) { 704 for (TSQLSchema schema : catalog.getSchemaList()) { 705 TSQLSchemaObject tsqlTable = schema.findTable(DlineageUtil.getSimpleTableName(this.name)); 706 if(tsqlTable == null){ 707 tsqlTable = schema.findSynonyms(DlineageUtil.getSimpleTableName(this.name)); 708 } 709 if (tsqlTable != null) { 710 occurrence += 1; 711 String candDb = quoteDottedSegment(catalog.getName()); 712 String candSchema = quoteDottedSegment(schema.getName()); 713 String candName = candDb + "." + candSchema + "." 714 + DlineageUtil.getSimpleTableName(this.name); 715 int candPriority = tsqlTable.getPriority(); 716 if (!candidatesByPriority.containsKey(candPriority)) { 717 candidatesByPriority.put(candPriority, new ArrayList<String[]>()); 718 } 719 candidatesByPriority.get(candPriority).add(new String[] { candDb, candSchema, candName, schema.getNameKeepCase() }); 720 if (tsqlTable.getPriority() > maxPriority 721 || (maxPriority > 0 && tsqlTable.getPriority() == maxPriority)) { 722 tableDatabase = catalog.getName(); 723 if(tableDatabase.indexOf(".")!=-1) { 724 tableDatabase = SQLUtil.quoteDottedName(ModelBindingManager.getGlobalOption().getVendor(), ESQLDataObjectType.dotCatalog, tableDatabase); 725 } 726 tableSchema = schema.getName(); 727 if(tableSchema.indexOf(".")!=-1) { 728 tableSchema = SQLUtil.quoteDottedName(ModelBindingManager.getGlobalOption().getVendor(), ESQLDataObjectType.dotSchema, tableSchema); 729 } 730 maxPriority = tsqlTable.getPriority(); 731 if(!databaseSchemasMap.containsKey(maxPriority)) { 732 databaseSchemasMap.put(maxPriority, new ArrayList<String>()); 733 } 734 databaseSchemasMap.get(maxPriority).add(tableDatabase + "___" + tableSchema); 735 } 736 } 737 } 738// if (occurrence > 1) { 739// break; 740// } 741 } else { 742 TSQLSchema schema = catalog.getSchema(this.schema, false); 743 if (schema != null) { 744 TSQLSchemaObject tsqlTable = schema.findTable(DlineageUtil.getSimpleTableName(this.name)); 745 if(tsqlTable == null){ 746 tsqlTable = schema.findSynonyms(DlineageUtil.getSimpleTableName(this.name)); 747 } 748 if (tsqlTable != null) { 749 occurrence += 1; 750 if (tsqlTable.getPriority() > maxPriority 751 || (maxPriority > 0 && tsqlTable.getPriority() == maxPriority)) { 752 tableDatabase = catalog.getName(); 753 if(tableDatabase.indexOf(".")!=-1) { 754 tableDatabase = SQLUtil.quoteDottedName(ModelBindingManager.getGlobalOption().getVendor(), ESQLDataObjectType.dotCatalog, tableDatabase); 755 } 756 tableSchema = schema.getName(); 757 if(tableSchema.indexOf(".")!=-1) { 758 tableSchema = SQLUtil.quoteDottedName(ModelBindingManager.getGlobalOption().getVendor(), ESQLDataObjectType.dotSchema, tableSchema); 759 } 760 maxPriority = tsqlTable.getPriority(); 761 if(!databaseSchemasMap.containsKey(maxPriority)) { 762 databaseSchemasMap.put(maxPriority, new ArrayList<String>()); 763 } 764 databaseSchemasMap.get(maxPriority).add(tableDatabase + "___" + tableSchema); 765 } 766 } 767 } 768 } 769 } 770 if (occurrence == 1 || (maxPriority > 0 && databaseSchemasMap.get(maxPriority).size() == 1)) { 771 if (this.database == null && tableDatabase != null && !TSQLEnv.DEFAULT_DB_NAME.equalsIgnoreCase(tableDatabase)) { 772 assignDatabase(tableDatabase, storedUnlessDelimited(ESQLDataObjectType.dotCatalog, tableDatabase)); 773 } 774 if (this.schema == null && tableSchema != null && !TSQLEnv.DEFAULT_SCHEMA_NAME.equalsIgnoreCase(tableSchema)) { 775 assignSchema(tableSchema, storedUnlessDelimited(ESQLDataObjectType.dotSchema, tableSchema)); 776 } 777 } else if ((this.schema == null || this.schema.equals(ModelBindingManager.getGlobalSchema())) 778 && !candidatesByPriority.isEmpty()) { 779 // The existing rules could not pick a single winner and the schema is 780 // still only a default (null or the global default). Look at the 781 // top-priority group: when it holds two or more schemas AND none of 782 // them is the default schema, the unqualified name is genuinely 783 // ambiguous. Flag it and record the candidate set so the output layer 784 // can surface the reference unqualified instead of fabricating a 785 // non-existent default-schema object. A match in the default schema 786 // (e.g. an existing dbo.orders) is left to win through the normal 787 // stamping path. 788 int maxCandPriority = Collections.max(candidatesByPriority.keySet()); 789 List<String[]> topCandidates = candidatesByPriority.get(maxCandPriority); 790 String defaultSchema = getDefaultSchema(); 791 boolean defaultSchemaHasObject = false; 792 for (String[] cand : topCandidates) { 793 if (cand[1] != null && DlineageUtil.sameName(ESQLDataObjectType.dotSchema, cand[3], defaultSchema)) { 794 defaultSchemaHasObject = true; 795 break; 796 } 797 } 798 if (!defaultSchemaHasObject && topCandidates.size() >= 2) { 799 List<String> candidates = new ArrayList<String>(); 800 for (String[] cand : topCandidates) { 801 candidates.add(cand[2]); 802 } 803 this.candidateTables = candidates; 804 this.ambiguousUnqualifiedTable = true; 805 } 806 } 807 } else if (SQLUtil.isEmpty(this.schema) || this.schema.equals(ModelBindingManager.getGlobalSchema())) { 808 TSQLEnv sqlEnv = metadataEnvironment(); 809 if (sqlEnv == null) { 810 return; 811 } 812 int occurrence = 0; 813 int maxPriority = -1; 814 String tableDatabase = null; 815 String tableSchema = null; 816 Map<Integer, List<String>> databaseSchemasMap = new HashMap<Integer, List<String>>(); 817 TSQLCatalog catalog = sqlEnv.searchCatalog(this.database); 818 if (catalog != null) { 819 for (TSQLSchema schema : catalog.getSchemaList()) { 820 TSQLSchemaObject tsqlTable = schema.findTable(DlineageUtil.getSimpleTableName(this.name)); 821 if(tsqlTable == null){ 822 tsqlTable = schema.findSynonyms(DlineageUtil.getSimpleTableName(this.name)); 823 } 824 if (tsqlTable != null) { 825 occurrence += 1; 826 if (tsqlTable.getPriority() > maxPriority 827 || (maxPriority > 0 && tsqlTable.getPriority() == maxPriority)) { 828 tableDatabase = catalog.getName(); 829 if (tableDatabase.indexOf(".") != -1) { 830 tableDatabase = SQLUtil.quoteDottedName(ModelBindingManager.getGlobalOption().getVendor(), ESQLDataObjectType.dotCatalog, tableDatabase); 831 } 832 tableSchema = schema.getName(); 833 if (tableSchema.indexOf(".") != -1) { 834 tableSchema = SQLUtil.quoteDottedName(ModelBindingManager.getGlobalOption().getVendor(), ESQLDataObjectType.dotSchema, tableSchema); 835 } 836 maxPriority = tsqlTable.getPriority(); 837 if(!databaseSchemasMap.containsKey(maxPriority)) { 838 databaseSchemasMap.put(maxPriority, new ArrayList<String>()); 839 } 840 databaseSchemasMap.get(maxPriority).add(tableDatabase + "___" + tableSchema); 841 } 842 } 843 } 844 } 845 846 if (occurrence == 1 || (maxPriority > 0 && databaseSchemasMap.get(maxPriority).size() == 1)) { 847 if (this.database == null && tableDatabase != null && !TSQLEnv.DEFAULT_DB_NAME.equalsIgnoreCase(tableDatabase)) { 848 assignDatabase(tableDatabase, storedUnlessDelimited(ESQLDataObjectType.dotCatalog, tableDatabase)); 849 } 850 if (this.schema == null && tableSchema != null && !TSQLEnv.DEFAULT_SCHEMA_NAME.equalsIgnoreCase(tableSchema)) { 851 assignSchema(tableSchema, storedUnlessDelimited(ESQLDataObjectType.dotSchema, tableSchema)); 852 } 853 } 854 855 if (SQLUtil.isEmpty(this.schema) && !SQLUtil.isEmpty(this.database)) { 856 assignSchema(getDefaultSchema(), null); 857 } 858 } 859 } 860 861 public Table(TObjectName tableName) { 862 if (tableName == null) { 863 throw new IllegalArgumentException("Table arguments can't be null."); 864 } 865 866 id = ++ModelBindingManager.get().TABLE_COLUMN_ID; 867 868 this.viewName = tableName; 869 870 setPosition(tableName); 871 872 this.fullName = tableName.toString(); 873 this.name = tableName.toString(); 874 if (SQLUtil.isEmpty(name)) { 875 name = tableName.toString(); 876 } 877 878 captureIdentifierKey(tableName.toString(), null); 879 880 if (name.startsWith("`") && name.endsWith("`") && name.indexOf(".") != -1 881 && ModelBindingManager.getGlobalOption().getVendor() == EDbVendor.dbvbigquery) { 882 this.name = SQLUtil.trimColumnStringQuote(name); 883 this.fullName = SQLUtil.trimColumnStringQuote(fullName); 884 } 885 886 this.server = DlineageUtil.getTableServer(tableName.toString()); 887 assignDatabase(DlineageUtil.getTableDatabase(tableName.toString()), null); 888 assignSchema(DlineageUtil.getTableSchema(tableName.toString()), null); 889 890 fillSchemaInfo(); 891 892 EDbVendor vendor = ModelBindingManager.getGlobalOption().getVendor(); 893 boolean supportCatalog = TSQLEnv.supportCatalog(vendor); 894 boolean supportSchema = TSQLEnv.supportSchema(vendor); 895 896 if (supportCatalog && SQLUtil.isEmpty(this.database)) { 897 assignDatabase(DlineageUtil.getTableDatabase(tableName.toString()), null); 898 if (SQLUtil.isEmpty(this.database) && ModelBindingManager.getGlobalOption().isShowImplicitSchema() 899 && !SQLUtil.isEmpty(tableName.getImplictDatabaseString())) { 900 assignDatabase(tableName.getImplictDatabaseString(), implicitDatabaseForm(tableName)); 901 } 902 } 903 904 if (supportSchema && SQLUtil.isEmpty(this.schema)) { 905 assignSchema(DlineageUtil.getTableSchema(tableName.toString()), null); 906 if (SQLUtil.isEmpty(this.schema) && ModelBindingManager.getGlobalOption().isShowImplicitSchema() 907 && !SQLUtil.isEmpty(implicitSchemaOf(tableName))) { 908 assignSchema(implicitSchemaOf(tableName), implicitSchemaForm(tableName)); 909 } 910 } 911 912 if (supportSchema && !SQLUtil.isEmpty(this.database) && SQLUtil.isEmpty(this.schema)) { 913 if (!SQLUtil.isEmpty(implicitSchemaOf(tableName))) { 914 assignSchema(implicitSchemaOf(tableName), implicitSchemaForm(tableName)); 915 } else { 916 assignSchema(getDefaultSchema(), null); 917 } 918 } 919 920 if (!supportCatalog) { 921 assignDatabase(null, null); 922 } else if (this.database == null && !TSQLEnv.DEFAULT_DB_NAME.equals(getDefaultDatabase())) { 923 assignDatabase(getDefaultDatabase(), null); 924 } 925 926 if (!supportSchema) { 927 assignSchema(null, null); 928 } else if (this.schema == null && !TSQLEnv.DEFAULT_SCHEMA_NAME.equals(getDefaultSchema())) { 929 assignSchema(getDefaultSchema(), null); 930 } 931 932 updateTableName(supportCatalog, supportSchema); 933 934 if (this.server == null && !TSQLEnv.DEFAULT_SERVER_NAME.equals(getDefaultServer())) { 935 this.server = getDefaultServer(); 936 } 937 } 938 939 protected String getDefaultServer() { 940 String defaultServer = null; 941 if (ModelBindingManager.getGlobalSQLEnv() != null) { 942 defaultServer = ModelBindingManager.getGlobalSQLEnv().getDefaultServerName(); 943 } 944 if (!SQLUtil.isEmpty(defaultServer)) 945 return defaultServer; 946 return TSQLEnv.DEFAULT_SERVER_NAME; 947 } 948 949 /** 950 * The implicit schema stamped on {@code name}, or null when the statement 951 * being analyzed has no current schema because a Snowflake 952 * {@code USE <database>} unset it. 953 * 954 * <p>The stamp is applied by the resolvers from the TSQLEnv's default schema, 955 * which is batch-global: it holds the LAST {@code USE SCHEMA} of the whole 956 * script even for statements that run after a database switch invalidated it. 957 * Reading it unfiltered put the previous database's schema back on names the 958 * rest of this class had already left unqualified - a CREATE VIEW target 959 * reported {@code D2.S1.v} while its own source table reported 960 * {@code D2.DEFAULT.orders}. See GitHub #715 / MantisBT 4677. 961 */ 962 private static String implicitSchemaOf(TObjectName name) { 963 if (name == null || ModelBindingManager.isGlobalSchemaUnset()) { 964 return null; 965 } 966 return name.getImplictSchemaString(); 967 } 968 969 protected String getDefaultSchema() { 970 return ModelBindingManager.getEffectiveDefaultSchema(); 971 } 972 973 protected String getDefaultDatabase() { 974 return ModelBindingManager.getEffectiveDefaultDatabase(); 975 } 976 977 /** 978 * @return true when this one-part reference is ambiguous across two or more 979 * schemas and was intentionally left unqualified (no default schema 980 * stamped). The candidates are available via {@link #getCandidateTables()}. 981 */ 982 public boolean isAmbiguousUnqualifiedTable() { 983 return ambiguousUnqualifiedTable; 984 } 985 986 /** 987 * @return the qualified candidate table names (e.g. {@code testdb.s1.orders}, 988 * {@code testdb.s2.orders}) when this reference is ambiguous across 989 * schemas, or null when the reference resolved normally. 990 */ 991 public List<String> getCandidateTables() { 992 return candidateTables; 993 } 994 995 /** 996 * Quote a catalog/schema segment the same way the surrounding resolution code 997 * does (lines around the occurrence loop), so a name containing a dot is kept 998 * as a single delimited segment. 999 */ 1000 private static String quoteDottedSegment(String segment) { 1001 if (segment != null && segment.indexOf('.') != -1) { 1002 // catalog and schema segments share NAME_GROUP quote rules 1003 return SQLUtil.quoteDottedName(ModelBindingManager.getGlobalOption().getVendor(), 1004 ESQLDataObjectType.dotSchema, segment); 1005 } 1006 return segment; 1007 } 1008 1009 /** Drops the model's position: a table no SQL named has none to publish. */ 1010 void dropPosition() { 1011 startPosition = null; 1012 endPosition = null; 1013 positions.clear(); 1014 } 1015 1016 public Table(String tableName) { 1017 if (tableName == null) { 1018 throw new IllegalArgumentException("Table arguments can't be null."); 1019 } 1020 1021 id = ++ModelBindingManager.get().TABLE_COLUMN_ID; 1022 1023 this.startPosition = new Pair3<Long, Long, String>(-1L, -1L, ModelBindingManager.getGlobalHash()); 1024 this.endPosition = new Pair3<Long, Long, String>(-1L, -1L, ModelBindingManager.getGlobalHash()); 1025 1026 this.fullName = tableName; 1027 this.name = tableName; 1028 if (SQLUtil.isEmpty(name)) { 1029 name = tableName; 1030 } 1031 1032 captureIdentifierKey(tableName, null); 1033 1034 if (name.startsWith("`") && name.endsWith("`") && name.indexOf(".") != -1 1035 && ModelBindingManager.getGlobalOption().getVendor() == EDbVendor.dbvbigquery) { 1036 this.name = SQLUtil.trimColumnStringQuote(name); 1037 this.fullName = SQLUtil.trimColumnStringQuote(fullName); 1038 } 1039 1040 EDbVendor vendor = ModelBindingManager.getGlobalOption().getVendor(); 1041 boolean supportCatalog = TSQLEnv.supportCatalog(vendor); 1042 boolean supportSchema = TSQLEnv.supportSchema(vendor); 1043 1044 fillSchemaInfo(); 1045 1046 this.server = DlineageUtil.getTableServer(tableName); 1047 1048 if (supportCatalog && SQLUtil.isEmpty(this.database)) { 1049 assignDatabase(DlineageUtil.getTableDatabase(tableName), null); 1050 } 1051 if (supportSchema && SQLUtil.isEmpty(this.schema)) { 1052 assignSchema(DlineageUtil.getTableSchema(tableName), null); 1053 } 1054 if (supportSchema && !SQLUtil.isEmpty(this.database) && SQLUtil.isEmpty(this.schema)) { 1055 assignSchema(getDefaultSchema(), null); 1056 } 1057 1058 if (!supportCatalog) { 1059 assignDatabase(null, null); 1060 } else if (this.database == null && !TSQLEnv.DEFAULT_DB_NAME.equals(getDefaultDatabase())) { 1061 assignDatabase(getDefaultDatabase(), null); 1062 } 1063 1064 if (!supportSchema) { 1065 assignSchema(null, null); 1066 } else if (this.schema == null && !TSQLEnv.DEFAULT_SCHEMA_NAME.equals(getDefaultSchema())) { 1067 assignSchema(getDefaultSchema(), null); 1068 } 1069 1070 updateTableName(supportCatalog, supportSchema); 1071 1072 if (this.server == null && !TSQLEnv.DEFAULT_SERVER_NAME.equals(getDefaultServer())) { 1073 this.server = getDefaultServer(); 1074 } 1075 } 1076 1077 protected void updateTableName(boolean supportCatalog, boolean supportSchema) { 1078 List<String> segments = SQLUtil.parseNames(this.name); 1079 spelledSegments = segments.size(); 1080 this.name = segments.get(segments.size() - 1); 1081 if (supportCatalog && supportSchema) { 1082 StringBuilder builder = new StringBuilder(); 1083 if (segments.size() > 2) { 1084 builder.append(segments.get(segments.size() - 3)).append("."); 1085 } else { 1086 if (!SQLUtil.isEmpty(this.database) && !TSQLEnv.DEFAULT_DB_NAME.equalsIgnoreCase(this.database)) { 1087 builder.append(this.database).append("."); 1088 } 1089 } 1090 if (segments.size() > 1) { 1091 builder.append(segments.get(segments.size() - 2)).append("."); 1092 } else { 1093 if (builder.length() > 0) { 1094 if (this.schema == null) { 1095 if (ModelBindingManager.getGlobalVendor() == EDbVendor.dbvmssql) { 1096 assignSchema("dbo", null); 1097 } else { 1098 assignSchema(getDefaultSchema(), null); 1099 } 1100 } 1101 else if (TSQLEnv.DEFAULT_SCHEMA_NAME.equalsIgnoreCase(this.schema) 1102 && ModelBindingManager.getGlobalVendor() == EDbVendor.dbvmssql) { 1103 assignSchema("dbo", null); 1104 } 1105 builder.append(this.schema).append("."); 1106 } else { 1107 if (!SQLUtil.isEmpty(this.schema) && !TSQLEnv.DEFAULT_SCHEMA_NAME.equalsIgnoreCase(this.schema)) { 1108 builder.append(this.schema).append("."); 1109 } 1110 } 1111 } 1112 builder.append(this.name); 1113 this.name = builder.toString(); 1114 } else if (supportCatalog) { 1115 if (segments.size() > 1) { 1116 this.name = segments.get(segments.size() - 2) + "." + this.name; 1117 } 1118 else { 1119 if (!SQLUtil.isEmpty(this.database) && !TSQLEnv.DEFAULT_DB_NAME.equalsIgnoreCase(this.database)) { 1120 this.name = this.database + "." + this.name; 1121 } 1122 } 1123 } else if (supportSchema) { 1124 if (segments.size() > 1) { 1125 this.name = segments.get(segments.size() - 2) + "." + this.name; 1126 } else { 1127 if (!SQLUtil.isEmpty(this.schema) && !TSQLEnv.DEFAULT_SCHEMA_NAME.equalsIgnoreCase(this.schema)) { 1128 this.name = this.schema + "." + this.name; 1129 } 1130 } 1131 } 1132 1133 if (this.server != null && !this.server.equals(getDefaultServer())) { 1134 this.name = this.server + "." + this.name; 1135 } 1136 } 1137 1138 public long getId() { 1139 return id; 1140 } 1141 1142 public String getName() { 1143 return name; 1144 } 1145 1146 public void setName(String name) { 1147 EDbVendor vendor = ModelBindingManager.getGlobalOption().getVendor(); 1148 boolean supportCatalog = TSQLEnv.supportCatalog(vendor); 1149 boolean supportSchema = TSQLEnv.supportSchema(vendor); 1150 if (!supportCatalog) { 1151 assignDatabase(null, null); 1152 } else if (this.database == null && !TSQLEnv.DEFAULT_DB_NAME.equals(getDefaultDatabase())) { 1153 assignDatabase(getDefaultDatabase(), null); 1154 } 1155 1156 if (!supportSchema) { 1157 assignSchema(null, null); 1158 } else if (this.schema == null && !TSQLEnv.DEFAULT_SCHEMA_NAME.equals(getDefaultSchema())) { 1159 assignSchema(getDefaultSchema(), null); 1160 } 1161 1162 this.name = name; 1163 1164 updateTableName(supportCatalog, supportSchema); 1165 } 1166 1167 public Pair3<Long, Long, String> getStartPosition() { 1168 return startPosition; 1169 } 1170 1171 public Pair3<Long, Long, String> getEndPosition() { 1172 return endPosition; 1173 } 1174 1175 public List<TableColumn> getColumns() { 1176 return columns; 1177 } 1178 1179 public void addColumn(TableColumn column) { 1180 if (column != null && !this.columns.contains(column)) { 1181 this.columns.add(column); 1182 } 1183 } 1184 1185 /** 1186 * STRUCT tree: the field path nodes below this table's columns ({@code customer.address}, 1187 * {@code customer.address.city}, {@code items[*]}), in the order they were made. They are not columns of their own, 1188 * so they are kept out of {@link #getColumns()}, which star expansion and positional writes walk; the output lists 1189 * them with their columns ({@link #getColumnsWithFieldPaths()}). 1190 */ 1191 private List<TableColumn> fieldPathColumns = new ArrayList<TableColumn>(); 1192 1193 /** The field path nodes, read-only: they are added by the field-path constructor only, which keeps the index. */ 1194 public List<TableColumn> getFieldPathColumns() { 1195 return java.util.Collections.unmodifiableList(fieldPathColumns); 1196 } 1197 1198 /** The field path nodes by their structural identity ({@link FieldNodeKey}), in the order they were made. */ 1199 private final java.util.Map<FieldNodeKey, TableColumn> fieldPathIndex = 1200 new java.util.LinkedHashMap<FieldNodeKey, TableColumn>(); 1201 1202 /** Makes {@code column}, a field path node, one of {@link #getFieldPathColumns()} rather than a column. */ 1203 void addFieldPathColumn(TableColumn column) { 1204 columns.remove(column); 1205 if (!fieldPathColumns.contains(column)) { 1206 fieldPathColumns.add(column); 1207 FieldNodeKey key = FieldNodeKey.of(column); 1208 if (!fieldPathIndex.containsKey(key)) { 1209 fieldPathIndex.put(key, column); 1210 } 1211 } 1212 } 1213 1214 /** Whether this is the relation a table-generating construct (a LATERAL VIEW) adds to one SELECT. */ 1215 private boolean arrayRelation; 1216 1217 public boolean isArrayRelation() { 1218 return arrayRelation; 1219 } 1220 1221 public void setArrayRelation(boolean arrayRelation) { 1222 this.arrayRelation = arrayRelation; 1223 } 1224 1225 /** The field path node with identity {@code key}, or null. */ 1226 public TableColumn getFieldPathNode(FieldNodeKey key) { 1227 return fieldPathIndex.get(key); 1228 } 1229 1230 /** Plan rule R10: the nested-field declarations of this table, one per CREATE TABLE, in the order made. */ 1231 private final List<FieldDeclaration> fieldDeclarations = new ArrayList<FieldDeclaration>(0); 1232 1233 /** The declaration of the CREATE TABLE being analyzed; null between statements. */ 1234 private FieldDeclaration buildingFieldDeclaration; 1235 1236 /** 1237 * Starts the declaration of the CREATE TABLE statement {@code created}, which the DROP TABLE {@code dropped} 1238 * ends ({@link FieldDeclaration#NEVER_DROPPED} when none does). Until {@link #endFieldDeclaration()}, the 1239 * declared fields recorded are this declaration's. 1240 */ 1241 public FieldDeclaration openFieldDeclaration(long created, long dropped) { 1242 FieldDeclaration declaration = new FieldDeclaration(created, dropped); 1243 fieldDeclarations.add(declaration); 1244 buildingFieldDeclaration = declaration; 1245 return declaration; 1246 } 1247 1248 /** Ends the declaration {@link #openFieldDeclaration} started. */ 1249 public void endFieldDeclaration() { 1250 buildingFieldDeclaration = null; 1251 } 1252 1253 /** The declaration declared fields are recorded in: the one being built, else the active one, else a new one. */ 1254 FieldDeclaration fieldDeclarationForWrite() { 1255 if (buildingFieldDeclaration != null) { 1256 return buildingFieldDeclaration; 1257 } 1258 FieldDeclaration active = getActiveFieldDeclaration(); 1259 if (active != null) { 1260 return active; 1261 } 1262 FieldDeclaration declaration = new FieldDeclaration(currentStatementOrdinal(), FieldDeclaration.NEVER_DROPPED); 1263 fieldDeclarations.add(declaration); 1264 return declaration; 1265 } 1266 1267 /** The declaration a read of the statement being analyzed is validated against; null when there is none. */ 1268 public FieldDeclaration getActiveFieldDeclaration() { 1269 if (buildingFieldDeclaration != null) { 1270 return buildingFieldDeclaration; 1271 } 1272 return activeFieldDeclaration(currentStatementOrdinal()); 1273 } 1274 1275 private static long currentStatementOrdinal() { 1276 ModelBindingManager manager = ModelBindingManager.get(); 1277 return manager == null ? -1 : manager.getCurrentStatementOrdinal(); 1278 } 1279 1280 /** 1281 * The declaration in force at the statement {@code at}: the latest one made before it that no DROP ended before 1282 * it. A statement before every CREATE of the table (a script that reads a table before its DDL) and a statement 1283 * after a DROP that no CREATE followed yet use the next declaration; an unknown position uses the latest. 1284 */ 1285 FieldDeclaration activeFieldDeclaration(long at) { 1286 if (fieldDeclarations.isEmpty()) { 1287 return null; 1288 } 1289 if (at < 0) { 1290 return fieldDeclarations.get(fieldDeclarations.size() - 1); 1291 } 1292 FieldDeclaration inForce = null; 1293 FieldDeclaration next = null; 1294 for (FieldDeclaration declaration : fieldDeclarations) { 1295 if (declaration.getCreated() <= at) { 1296 if (at < declaration.getDropped() 1297 && (inForce == null || declaration.getCreated() >= inForce.getCreated())) { 1298 inForce = declaration; 1299 } 1300 } else if (next == null || declaration.getCreated() < next.getCreated()) { 1301 next = declaration; 1302 } 1303 } 1304 if (inForce != null) { 1305 return inForce; 1306 } 1307 return next != null ? next : fieldDeclarations.get(fieldDeclarations.size() - 1); 1308 } 1309 1310 /** The columns as the output lists them: each column followed by the field path nodes below it. */ 1311 public List<TableColumn> getColumnsWithFieldPaths() { 1312 if (fieldPathColumns.isEmpty() && loadColumn == null) { 1313 return columns; 1314 } 1315 List<TableColumn> published = new ArrayList<TableColumn>(columns.size() + fieldPathColumns.size() + 1); 1316 for (TableColumn column : columns) { 1317 published.add(column); 1318 for (TableColumn node : fieldPathColumns) { 1319 if (node.getDeclaredColumn() == column) { 1320 published.add(node); 1321 } 1322 } 1323 } 1324 for (TableColumn node : fieldPathColumns) { 1325 if (!published.contains(node)) { 1326 published.add(node); 1327 } 1328 } 1329 if (loadColumn != null) { 1330 published.add(loadColumn); 1331 } 1332 return published; 1333 } 1334 1335 /** 1336 * The {@code *} a bulk load (SQL Server BULK INSERT, Mantis 4962) feeds the table through as a whole, or null. 1337 * Like a field path node it is no column of the table: it is kept out of {@link #getColumns()}, which star 1338 * expansion, positional writes and column lookups walk, so a load changes no other statement's lineage and no 1339 * other statement's star, expansion or pruning reaches the load; the output lists it after the columns. 1340 */ 1341 private TableColumn loadColumn; 1342 1343 public TableColumn getLoadColumn() { 1344 return loadColumn; 1345 } 1346 1347 /** Makes {@code column} the table's load column rather than one of its columns. */ 1348 public void setLoadColumn(TableColumn column) { 1349 columns.remove(column); 1350 this.loadColumn = column; 1351 } 1352 1353 public TTable getTableObject() { 1354 return tableObject; 1355 } 1356 1357 public String getAlias() { 1358 String aliasString = Arrays.toString(this.alias.toArray(new String[0])); 1359 return aliasString.substring(1, aliasString.length() - 1); 1360 } 1361 1362 public void setAlias(String alias) { 1363 if (!SQLUtil.isEmpty(alias)) { 1364 this.alias.add(alias); 1365 } 1366 } 1367 1368 public String getFullName() { 1369 return fullName; 1370 } 1371 1372 1373 public boolean hasSubquery() { 1374 return subquery; 1375 } 1376 1377 public String getParent() { 1378 return parent; 1379 } 1380 1381 public void setParent(String parent) { 1382 this.parent = parent; 1383 } 1384 1385// public TObjectName getTableName() { 1386// return tableName; 1387// } 1388 1389 public String getDatabase() { 1390 return database; 1391 } 1392 1393 public String getSchema() { 1394 return schema; 1395 } 1396 1397 public TableRelationRows getRelationRows() { 1398 return relationRows; 1399 } 1400 1401 public boolean isCreateTable() { 1402 return isCreateTable; 1403 } 1404 1405 public void setCreateTable(boolean isCreateTable, boolean fromDDL) { 1406 Stack<TCustomSqlStatement> stmtStack = ModelBindingManager.getGlobalStmtStack(); 1407 TCustomSqlStatement stmt = null; 1408 if (stmtStack != null && !stmtStack.isEmpty()) { 1409 stmt = stmtStack.peek(); 1410 } 1411 if (stmt != null && stmt.getClass().getSimpleName().startsWith("TCreate")) { 1412 Pair<Pair3<Long, Long, String>, Pair3<Long, Long, String>> position = setPosition(stmt); 1413 if (position != null) { 1414 this.startPosition = position.first; 1415 this.endPosition = position.second; 1416 } 1417 } 1418 this.isCreateTable = isCreateTable; 1419 if (isCreateTable) { 1420 for (TableColumn column : columns) { 1421 column.setShowStar(false); 1422 } 1423 setDetermined(true); 1424 if (fromDDL) { 1425 setFromDDL(true); 1426 } 1427 } 1428 } 1429 1430 public void setCreateTable(boolean isCreateTable) { 1431 setCreateTable(isCreateTable, false); 1432 } 1433 1434 public boolean isView() { 1435 return isView; 1436 } 1437 1438 public void setView(boolean isView) { 1439 this.isView = isView; 1440 } 1441 1442 public boolean isPseudo() { 1443 return isPseudo; 1444 } 1445 1446 public void setPseudo(boolean isPseudo) { 1447 this.isPseudo = isPseudo; 1448 } 1449 1450 public boolean isExternal() { 1451 return isExternal; 1452 } 1453 1454 public void setExternal(boolean isExternal) { 1455 this.isExternal = isExternal; 1456 } 1457 1458 public boolean isStage() { 1459 return isStage; 1460 } 1461 1462 public void setStage(boolean isStage) { 1463 this.isStage = isStage; 1464 } 1465 1466 public boolean isSequence() { 1467 return isSequence; 1468 } 1469 1470 public void setSequence(boolean sequence) { 1471 isSequence = sequence; 1472 } 1473 1474 public boolean isVariable() { 1475 return isVariable; 1476 } 1477 1478 public void setVariable(boolean isVariable) { 1479 this.isVariable = isVariable; 1480 } 1481 1482 public boolean isConstant() { 1483 return isConstant; 1484 } 1485 1486 public void setConstant(boolean isConstant) { 1487 this.isConstant = isConstant; 1488 } 1489 1490 public boolean isTarget() { 1491 return isTarget; 1492 } 1493 1494 public void setTarget(boolean isTarget) { 1495 this.isTarget = isTarget; 1496 } 1497 1498 public String getProcedureId() { 1499 return procedureId; 1500 } 1501 1502 public void setProcedureId(String procedureId) { 1503 this.procedureId = procedureId; 1504 } 1505 1506 public boolean isPath() { 1507 return isPath; 1508 } 1509 1510 public void setPath(boolean isPath) { 1511 setPath(isPath, true); 1512 } 1513 1514 /** 1515 * Marks a file path. With {@code defaultQualifiers} false the analysis defaults (the database and schema a USE or 1516 * an Option sets) stay off it, since a file is no object of a database (SQL Server BULK INSERT, Mantis 4962); the 1517 * file-path database and schema options still apply. 1518 */ 1519 public void setPath(boolean isPath, boolean defaultQualifiers) { 1520 this.isPath = isPath; 1521 if(isPath && !defaultQualifiers) { 1522 String filePathDatabase = ModelBindingManager.getGlobalOption().getFilePathDatabase(); 1523 String filePathSchema = ModelBindingManager.getGlobalOption().getFilePathSchema(); 1524 EDbVendor vendor = ModelBindingManager.getGlobalOption().getVendor(); 1525 boolean supportCatalog = TSQLEnv.supportCatalog(vendor); 1526 boolean supportSchema = TSQLEnv.supportSchema(vendor); 1527 assignDatabase(supportCatalog && !SQLUtil.isEmpty(filePathDatabase) ? filePathDatabase : null, null); 1528 assignSchema(supportSchema && !SQLUtil.isEmpty(filePathSchema) ? filePathSchema : null, null); 1529 // the constructor already folded the defaults into the name; start again from the name as written 1530 if (this.fullName != null) { 1531 this.name = this.fullName; 1532 } 1533 updateTableName(supportCatalog, supportSchema); 1534 return; 1535 } 1536 if(isPath) { 1537 String filePathDatabase = ModelBindingManager.getGlobalOption().getFilePathDatabase(); 1538 String filePathSchema = ModelBindingManager.getGlobalOption().getFilePathSchema(); 1539 if(SQLUtil.isEmpty(filePathDatabase)) { 1540 assignDatabase(null, null); 1541 } 1542 else { 1543 assignDatabase(filePathDatabase, null); 1544 } 1545 if(SQLUtil.isEmpty(filePathSchema)) { 1546 assignSchema(null, null); 1547 } 1548 else { 1549 assignSchema(filePathSchema, null); 1550 } 1551 1552 EDbVendor vendor = ModelBindingManager.getGlobalOption().getVendor(); 1553 boolean supportCatalog = TSQLEnv.supportCatalog(vendor); 1554 boolean supportSchema = TSQLEnv.supportSchema(vendor); 1555 if (!supportCatalog) { 1556 assignDatabase(null, null); 1557 } else if (this.database == null && !TSQLEnv.DEFAULT_DB_NAME.equals(getDefaultDatabase())) { 1558 assignDatabase(getDefaultDatabase(), null); 1559 } 1560 1561 if (!supportSchema) { 1562 assignSchema(null, null); 1563 } else if (this.schema == null && !TSQLEnv.DEFAULT_SCHEMA_NAME.equals(getDefaultSchema())) { 1564 assignSchema(getDefaultSchema(), null); 1565 } 1566 1567 updateTableName(supportCatalog, supportSchema); 1568 } 1569 } 1570 1571 public boolean isCursor() { 1572 return isVariable && getSubType() == SubType.cursor; 1573 } 1574 1575 public String getLocation() { 1576 return location; 1577 } 1578 1579 public void setLocation(String location) { 1580 this.location = location; 1581 } 1582 1583 /** The external data source a file path is read through (BULK INSERT ... WITH (DATA_SOURCE = ...)), or null. */ 1584 public String getDataSourceName() { 1585 return dataSourceName; 1586 } 1587 1588 public void setDataSourceName(String dataSourceName) { 1589 this.dataSourceName = dataSourceName; 1590 } 1591 1592 /** 1593 * The URI of a file path when it differs from its name: the data source's LOCATION joined with the path the 1594 * statement names; null when the URI is the name. 1595 */ 1596 public String getPathUri() { 1597 return pathUri; 1598 } 1599 1600 public void setPathUri(String pathUri) { 1601 this.pathUri = pathUri; 1602 } 1603 1604 public String getFileType() { 1605 return fileType; 1606 } 1607 1608 public void setFileType(String fileType) { 1609 this.fileType = fileType; 1610 } 1611 1612 public String getDisplayName() { 1613 return displayName; 1614 } 1615 1616 public void setDisplayName(String displayName) { 1617 this.displayName = displayName; 1618 } 1619 1620 public String getFileFormat() { 1621 return fileFormat; 1622 } 1623 1624 public void setFileFormat(String fileFormat) { 1625 this.fileFormat = fileFormat; 1626 } 1627 1628 public List<Process> getProcesses() { 1629 return processes; 1630 } 1631 1632 public void addProcess(Process process) { 1633 if (processes == null) { 1634 processes = new ArrayList<Process>(); 1635 } 1636 if (process != null && !processes.contains(process)) { 1637 processes.add(process); 1638 } 1639 } 1640 1641 public void removeProcess(Process process) { 1642 if (processes == null) { 1643 processes = new ArrayList<Process>(); 1644 } 1645 if (process != null && processes.contains(process)) { 1646 processes.remove(process); 1647 } 1648 } 1649 1650 public void setSubType(SubType subType) { 1651 if (this.subType == null || subType == SubType.record_type) { 1652 this.subType = subType; 1653 } 1654 } 1655 1656 public SubType getSubType() { 1657 return subType; 1658 } 1659 1660 public boolean isStream() { 1661 return isStream; 1662 } 1663 1664 public void setStream(boolean isStream) { 1665 this.isStream = isStream; 1666 } 1667 1668 public boolean isSchema() { 1669 return isSchema; 1670 } 1671 1672 public void setSchema(boolean isSchema) { 1673 this.isSchema = isSchema; 1674 } 1675 1676 public void setDatabase(String database) { 1677 assignDatabase(database, null); 1678 } 1679 1680 public boolean isDatabase() { 1681 return isDatabase; 1682 } 1683 1684 public void setDatabase(boolean isDatabase) { 1685 this.isDatabase = isDatabase; 1686 } 1687 1688 public boolean isDataSource() { 1689 return isDataSource; 1690 } 1691 1692 public void setDataSource(boolean dataSource) { 1693 isDataSource = dataSource; 1694 } 1695 1696 public String getStarStmt() { 1697 return starStmt; 1698 } 1699 1700 public void setStarStmt(String starStmt) { 1701 this.starStmt = starStmt; 1702 } 1703 1704 public void setHasSQLEnv(boolean hasSQLEnv) { 1705 this.hasSQLEnv = hasSQLEnv; 1706 } 1707 1708 public boolean hasSQLEnv() { 1709 return hasSQLEnv; 1710 } 1711 1712 public String getCurrentAlias() { 1713 return currentAlias; 1714 } 1715 1716 public void setCurrentAlias(String currentAlias) { 1717 if (SQLUtil.isEmpty(currentAlias)) { 1718 this.currentAlias = null; 1719 } else 1720 this.currentAlias = currentAlias; 1721 } 1722 1723 public Set<String> getAliasList() { 1724 return alias; 1725 } 1726 1727 public String getServer() { 1728 return server; 1729 } 1730 1731 public boolean isDetermined() { 1732 return isDetermined; 1733 } 1734 1735 public void setDetermined(boolean isDetermined) { 1736 this.isDetermined = isDetermined; 1737 } 1738 1739 public boolean isFromDDL() { 1740 return fromDDL; 1741 } 1742 1743 public void setFromDDL(boolean fromDDL) { 1744 this.fromDDL = fromDDL; 1745 } 1746 1747 /** 1748 * Whether this endpoint is created by the analyzed SQL. A parse fact, NOT gated on 1749 * {@link #isDetermined()}; true for CREATE TABLE / SELECT INTO / CTAS / clone 1750 * targets even when the defining query's schema could not be resolved. 1751 */ 1752 public boolean isCreatedInSql() { 1753 return createdInSql; 1754 } 1755 1756 public void setCreatedInSql(boolean createdInSql) { 1757 this.createdInSql = createdInSql; 1758 } 1759 1760 public EndpointIntroduction getEndpointIntroduction() { 1761 return endpointIntroduction; 1762 } 1763 1764 /** 1765 * Records how the endpoint was introduced. First non-UNKNOWN writer wins, so the 1766 * structural creation site (set early at build time) is not later clobbered. 1767 */ 1768 public void setEndpointIntroduction(EndpointIntroduction endpointIntroduction) { 1769 if (endpointIntroduction == null) { 1770 return; 1771 } 1772 if (this.endpointIntroduction == null || this.endpointIntroduction == EndpointIntroduction.UNKNOWN) { 1773 this.endpointIntroduction = endpointIntroduction; 1774 } 1775 } 1776 1777 /** 1778 * Existing schema/model resolution state, exposed under the endpoint-classification 1779 * name. Mirrors {@link #isDetermined()} exactly; introduces no new resolution logic. 1780 */ 1781 public boolean isResolved() { 1782 return isDetermined; 1783 } 1784 1785 /** 1786 * Delimiter-normalized, dialect-aware form of {@link #getName()} (brackets / quotes 1787 * stripped per identifier segment). Computed on access from the current name; does 1788 * NOT mutate {@link #getName()}, which stays raw. Returns the raw name when no 1789 * normalization is possible (e.g. no global vendor context). 1790 */ 1791 public String getNormalizedName() { 1792 if (name == null) { 1793 return null; 1794 } 1795 try { 1796 String normalized = gudusoft.gsqlparser.dlineage.util.LegacyDlineageWireNamesV1.tableName(name); 1797 return normalized == null ? name : normalized; 1798 } catch (Exception e) { 1799 return name; 1800 } 1801 } 1802 1803 /** 1804 * Records that this endpoint's identifier was assembled at runtime by a dynamic-SQL 1805 * fold. {@code parts} is the outermost-first, pipe-joined set of affected name 1806 * segments; {@code identityTemplated} says the hole reaches the leading character of 1807 * the object-name segment (so a {@code #}/{@code @} prefix there is fold text, not a 1808 * real temp / table-variable marker). Additive across repeated marks: parts union, 1809 * identity ORs — one folded statement can reach the same endpoint twice. 1810 * 1811 * <p>Callers MUST only invoke this from inside a dynamic-fold context; see 1812 * {@link EndpointKind#DYNAMIC_TEMPLATE}. 1813 */ 1814 public void markDynamicTemplate(String parts, boolean identityTemplated) { 1815 if (parts != null && parts.length() > 0) { 1816 if (this.templatedParts == null) { 1817 this.templatedParts = parts; 1818 } else if (!this.templatedParts.equals(parts)) { 1819 java.util.LinkedHashSet<String> merged = new java.util.LinkedHashSet<String>(); 1820 for (String part : TEMPLATED_PART_ORDER) { 1821 if (containsPart(this.templatedParts, part) || containsPart(parts, part)) { 1822 merged.add(part); 1823 } 1824 } 1825 StringBuilder sb = new StringBuilder(); 1826 for (String part : merged) { 1827 if (sb.length() > 0) { 1828 sb.append('|'); 1829 } 1830 sb.append(part); 1831 } 1832 this.templatedParts = sb.toString(); 1833 } 1834 } 1835 this.templatedIdentity = this.templatedIdentity || identityTemplated; 1836 } 1837 1838 /** Canonical outermost-first ordering of the segment labels used by {@link #getTemplatedParts()}. */ 1839 private static final String[] TEMPLATED_PART_ORDER = { "server", "db", "schema", "name" }; 1840 1841 private static boolean containsPart(String parts, String part) { 1842 if (parts == null) { 1843 return false; 1844 } 1845 for (String candidate : parts.split("\\|")) { 1846 // non-identifier-compare: fixed internal label vocabulary, not a DB object name 1847 if (candidate.equals(part)) { 1848 return true; 1849 } 1850 } 1851 return false; 1852 } 1853 1854 /** 1855 * Which identifier segments of {@link #getName()} were assembled at runtime, pipe-joined 1856 * outermost-first ({@code "db"}, {@code "db|name"}, ...); {@code null} when this endpoint 1857 * is not runtime-templated. Lets a consumer match a templated skeleton against a 1858 * later-resolved concrete name without re-parsing the name text. 1859 */ 1860 public String getTemplatedParts() { 1861 return templatedParts; 1862 } 1863 1864 /** True when this endpoint's identifier was assembled at runtime by a dynamic-SQL fold. */ 1865 public boolean isDynamicTemplate() { 1866 return templatedParts != null; 1867 } 1868 1869 /** 1870 * Withdraws a dynamic-template mark. Used when the SAME node turns out to also be 1871 * referenced by a STATIC statement (by-name model reuse): the static reference provably 1872 * names a real object, so publishing the template claim on the shared node would make 1873 * that reference wrong — see the shared-node contract in 1874 * docs/tmp/dynamic-template-endpoint-provenance.md. 1875 */ 1876 public void clearDynamicTemplate() { 1877 this.templatedParts = null; 1878 this.templatedIdentity = false; 1879 } 1880 1881 /** 1882 * Authoritative {@link EndpointKind}, computed on access from the current (raw) 1883 * name and the active dialect. Quote-robust: each identifier segment is delimiter- 1884 * stripped before the {@code #}/{@code ##}/{@code @}/{@code tempdb} tests. This 1885 * reads only the new classification; it never inspects or mutates {@link #subType}. 1886 * 1887 * <p>§5.6 minimum scope: distinguishes T-SQL temp / global-temp / table-variable / 1888 * tempdb objects from plain catalog objects. CTE / derived / TVF / constant kinds 1889 * are deferred and reported as {@link EndpointKind#UNKNOWN} here. 1890 */ 1891 public static boolean isTsqlFamilyVendor(EDbVendor vendor) { 1892 return vendor == EDbVendor.dbvmssql || vendor == EDbVendor.dbvazuresql 1893 || vendor == EDbVendor.dbvsybase || vendor == EDbVendor.dbvsybasease 1894 || vendor == EDbVendor.dbvsqlanywhere || vendor == EDbVendor.dbvsybaseiq; 1895 } 1896 1897 public EndpointKind getEndpointKind() { 1898 if (SQLUtil.isEmpty(name)) { 1899 return EndpointKind.UNKNOWN; 1900 } 1901 1902 EDbVendor vendor = null; 1903 if (ModelBindingManager.getGlobalOption() != null) { 1904 vendor = ModelBindingManager.getGlobalOption().getVendor(); 1905 } 1906 1907 if (isTsqlFamilyVendor(vendor)) { 1908 List<String> segments = SQLUtil.parseNames(name); 1909 if (!segments.isEmpty()) { 1910 String last = SQLUtil.trimColumnStringQuote(segments.get(segments.size() - 1).trim()); 1911 // A '#'/'##'/'@' prefix that came out of a dynamic fold hole is runtime text, 1912 // not evidence of a temp table / table variable: 'INSERT INTO ' + QUOTENAME(@tab) 1913 // folds to a table literally named @tab, which is a runtime-built CATALOG name. 1914 // Only then does DYNAMIC_TEMPLATE displace the temp family; a genuinely literal 1915 // '#tmp_' prefix keeps its temp kind (the templated segments stay readable via 1916 // getTemplatedParts()), because losing TEMP would send a consumer to the catalog. 1917 if (last.startsWith("##")) { 1918 return templatedIdentity ? EndpointKind.DYNAMIC_TEMPLATE : EndpointKind.GLOBAL_TEMP_TABLE; 1919 } 1920 if (last.startsWith("#")) { 1921 return templatedIdentity ? EndpointKind.DYNAMIC_TEMPLATE : EndpointKind.LOCAL_TEMP_TABLE; 1922 } 1923 if (last.startsWith("@")) { 1924 return templatedIdentity ? EndpointKind.DYNAMIC_TEMPLATE : EndpointKind.TABLE_VARIABLE; 1925 } 1926 String first = SQLUtil.trimColumnStringQuote(segments.get(0).trim()); 1927 if (segments.size() > 1 && "tempdb".equalsIgnoreCase(first)) { 1928 // A literal leading "tempdb" proves the object lives in tempdb whatever the 1929 // later segments template to, so the kind stands and templatedParts carries 1930 // the rest of the story. 1931 return EndpointKind.TEMPDB_OBJECT; 1932 } 1933 } 1934 } 1935 1936 // A runtime-assembled identifier is never a catalog object name: report the 1937 // template, so a consumer does not bind it (or, worse, match an unrelated object 1938 // that happens to share the folded spelling). Marked only inside a fold context. 1939 if (templatedParts != null) { 1940 return EndpointKind.DYNAMIC_TEMPLATE; 1941 } 1942 1943 // Only a plain real table is a catalog object here. Resultsets / derived / 1944 // views / variables / stages etc. are deferred kinds — leave them UNKNOWN 1945 // rather than mislabel them CATALOG_OBJECT. 1946 if (!subquery && !isView && !isPseudo && !isStage && !isSequence && !isStream 1947 && !isVariable && !isConstant && !isDatabase && !isSchema && !isDataSource 1948 && subType != SubType.alias_table && subType != SubType.values_table 1949 && subType != SubType.synonym && subType != SubType.function 1950 && subType != SubType.unnest) { 1951 return EndpointKind.CATALOG_OBJECT; 1952 } 1953 1954 return EndpointKind.UNKNOWN; 1955 } 1956 1957 private Pair<Pair3<Long, Long, String>, Pair3<Long, Long, String>> setPositionInternal(TSourceToken startToken, TSourceToken endToken) { 1958 if (startToken == null || endToken == null) { 1959 return null; 1960 } 1961 1962 Pair3<Long, Long, String> startPosition = new Pair3<Long, Long, String>(startToken.lineNo, startToken.columnNo, 1963 ModelBindingManager.getGlobalHash()); 1964 Pair3<Long, Long, String> endPosition = new Pair3<Long, Long, String>(endToken.lineNo, endToken.columnNo + SQLUtil.endTrim(endToken.getAstext()).length(), 1965 ModelBindingManager.getGlobalHash()); 1966 1967 if(this.startPosition == null && this.endPosition == null) { 1968 this.startPosition = startPosition; 1969 this.endPosition = endPosition; 1970 } 1971 1972 boolean traceTablePosition = ModelBindingManager.getGlobalOption().isTraceTablePosition(); 1973 1974 1975 Pair<Pair3<Long, Long, String>, Pair3<Long, Long, String>> position = Pair.create(startPosition, endPosition); 1976 1977 if (traceTablePosition) { 1978 positions.add(position); 1979 } 1980 1981 return position; 1982 } 1983 1984 public Pair<Pair3<Long, Long, String>, Pair3<Long, Long, String>> setPosition(TTable table) { 1985 if (table == null) { 1986 return null; 1987 } 1988 1989 TSourceToken startToken = table.getStartToken(); 1990 TSourceToken endToken = table.getEndToken(); 1991 if (startToken == null && table.getSubquery() != null) { 1992 startToken = table.getSubquery().getStartToken(); 1993 endToken = table.getSubquery().getEndToken(); 1994 } 1995 1996 return setPositionInternal(startToken, endToken); 1997 } 1998 1999 public Pair<Pair3<Long, Long, String>, Pair3<Long, Long, String>> setPosition(TObjectName tableName) { 2000 if (tableName == null) { 2001 return null; 2002 } 2003 2004 TSourceToken startToken = tableName.getStartToken(); 2005 TSourceToken endToken = tableName.getEndToken(); 2006 2007 return setPositionInternal(startToken, endToken); 2008 } 2009 2010 public Pair<Pair3<Long, Long, String>, Pair3<Long, Long, String>> setPosition(TCustomSqlStatement stmt) { 2011 if (stmt == null) { 2012 return null; 2013 } 2014 2015 TSourceToken startToken = stmt.getStartToken(); 2016 TSourceToken endToken = stmt.getEndToken(); 2017 2018 return setPositionInternal(startToken, endToken); 2019 } 2020 2021 public Set<Pair<Pair3<Long, Long, String>, Pair3<Long, Long, String>>> getPositions() { 2022 return positions; 2023 } 2024 2025 public TableColumn findColumn(String columnName) { 2026 if (columns == null) { 2027 return null; 2028 } 2029 for (TableColumn column : columns) { 2030 if (DlineageUtil.compareColumnIdentifier(column.getName(), columnName)) { 2031 return column; 2032 } 2033 } 2034 return null; 2035 } 2036 2037 public void removeColumn(TableColumn column) { 2038 if (columns != null && columns.contains(column)) { 2039 columns.remove(column); 2040 } 2041 } 2042 2043 public void removeColumn(String columnName) { 2044 TableColumn column = findColumn(columnName); 2045 if (column != null) { 2046 removeColumn(column); 2047 } 2048 } 2049}