001 002package gudusoft.gsqlparser.dlineage.dataflow.model; 003 004import gudusoft.gsqlparser.dlineage.symbol.IdentityScope; 005import gudusoft.gsqlparser.dlineage.symbol.LocalScopes; 006import gudusoft.gsqlparser.dlineage.symbol.LocalSymbolKey; 007import gudusoft.gsqlparser.dlineage.symbol.LocalSymbolNames; 008import gudusoft.gsqlparser.dlineage.routine.RoutineAliasKey; 009import gudusoft.gsqlparser.dlineage.routine.RoutineAliases; 010import gudusoft.gsqlparser.EDbObjectType; 011import gudusoft.gsqlparser.EDbVendor; 012import gudusoft.gsqlparser.TSourceToken; 013import gudusoft.gsqlparser.resolver2.metadata.StatementMetadata.ProcedureEnvironment; 014import gudusoft.gsqlparser.ESqlStatementType; 015import gudusoft.gsqlparser.ETableSource; 016import gudusoft.gsqlparser.ETableEffectType; 017import gudusoft.gsqlparser.TCustomSqlStatement; 018import gudusoft.gsqlparser.dlineage.util.DlineageUtil; 019import gudusoft.gsqlparser.dlineage.util.Pair; 020import gudusoft.gsqlparser.dlineage.dynamicsql.RoutineIdentity; 021import gudusoft.gsqlparser.dlineage.dynamicsql.RoutineKind; 022import gudusoft.gsqlparser.nodes.TAliasClause; 023import gudusoft.gsqlparser.nodes.TCaseExpression; 024import gudusoft.gsqlparser.nodes.TConstant; 025import gudusoft.gsqlparser.nodes.TDeclareVariable; 026import gudusoft.gsqlparser.nodes.TExpression; 027import gudusoft.gsqlparser.nodes.TFunctionCall; 028import gudusoft.gsqlparser.nodes.TObjectName; 029import gudusoft.gsqlparser.nodes.TObjectNameList; 030import gudusoft.gsqlparser.nodes.TParameterDeclaration; 031import gudusoft.gsqlparser.nodes.TParseTreeNode; 032import gudusoft.gsqlparser.nodes.TPivotClause; 033import gudusoft.gsqlparser.nodes.TResultColumn; 034import gudusoft.gsqlparser.nodes.TTable; 035import gudusoft.gsqlparser.nodes.TWhenClauseItemList; 036import gudusoft.gsqlparser.sqlenv.ESQLDataObjectType; 037import gudusoft.gsqlparser.sqlenv.TSQLColumn; 038import gudusoft.gsqlparser.sqlenv.TSQLEnv; 039import gudusoft.gsqlparser.sqlenv.TSQLTable; 040import gudusoft.gsqlparser.stmt.TCreateTableSqlStatement; 041import gudusoft.gsqlparser.stmt.TCreateViewSqlStatement; 042import gudusoft.gsqlparser.stmt.TCursorDeclStmt; 043import gudusoft.gsqlparser.stmt.TForStmt; 044import gudusoft.gsqlparser.stmt.TInsertSqlStatement; 045import gudusoft.gsqlparser.stmt.TLoopStmt; 046import gudusoft.gsqlparser.stmt.TMergeSqlStatement; 047import gudusoft.gsqlparser.stmt.TOpenforStmt; 048import gudusoft.gsqlparser.stmt.TSelectSqlStatement; 049import gudusoft.gsqlparser.stmt.TStoredProcedureSqlStatement; 050import gudusoft.gsqlparser.stmt.mssql.TMssqlDeclare; 051import gudusoft.gsqlparser.stmt.oracle.TPlsqlCreatePackage; 052import gudusoft.gsqlparser.util.SQLUtil; 053 054import java.util.ArrayList; 055import java.util.LinkedHashMap; 056import java.util.List; 057import java.util.Map; 058 059public class ModelFactory { 060 061 private ModelBindingManager modelManager; 062 063 public ModelFactory(ModelBindingManager modelManager) { 064 this.modelManager = modelManager; 065 } 066 067 public ResultSet createResultSet(TSelectSqlStatement select, boolean isTarget) { 068 if (select.getResultColumnList() != null) { 069 if (modelManager.getModel(select.getResultColumnList()) instanceof ResultSet) { 070 return (ResultSet) modelManager.getModel(select.getResultColumnList()); 071 } 072 SelectResultSet resultSet = new SelectResultSet(select, isTarget); 073 modelManager.bindModel(select.getResultColumnList(), resultSet); 074 return resultSet; 075 } else if (select.getTransformClause() != null) { 076 if (modelManager.getModel(select.getTransformClause()) instanceof ResultSet) { 077 return (ResultSet) modelManager.getModel(select.getTransformClause()); 078 } 079 SelectResultSet resultSet = new SelectResultSet(select, isTarget); 080 modelManager.bindModel(select.getTransformClause(), resultSet); 081 return resultSet; 082 } else 083 return null; 084 } 085 086 public ResultSet createResultSet(TParseTreeNode gspObject, boolean isTarget) { 087 if (modelManager.getModel(gspObject) instanceof ResultSet) { 088 return (ResultSet) modelManager.getModel(gspObject); 089 } 090 ResultSet resultSet = new ResultSet(gspObject, isTarget); 091 modelManager.bindModel(gspObject, resultSet); 092 return resultSet; 093 } 094 095 public ResultColumn createResultColumn(ResultSet resultSet, TResultColumn resultColumn) { 096 if (modelManager.getModel(resultColumn) instanceof ResultColumn) { 097 return (ResultColumn) modelManager.getModel(resultColumn); 098 } 099 ResultColumn column = new ResultColumn(resultSet, resultColumn); 100 modelManager.bindModel(resultColumn, column); 101 return column; 102 } 103 104 /** 105 * STRUCT tree: the result column of {@code customer.*} when the fields of {@code customer} are not known, one 106 * column for all of them. It is not a wildcard over a relation, so no relation's columns are expanded into it. 107 */ 108 /** 109 * STRUCT tree: the view column of a select item {@code customer.*} whose fields are unknown, one column standing for 110 * every field of {@code customer} like its result column ({@link #createStructStarResultColumn}), not a wildcard 111 * over a relation (Mantis 4827). 112 */ 113 public TableColumn createStructStarViewColumn(Table viewModel, TObjectName column, int index, boolean determined) { 114 TableColumn viewColumn = createViewColumn(viewModel, column, index, determined); 115 if (viewColumn != null) { 116 viewColumn.setStarColumn(false); 117 } 118 return viewColumn; 119 } 120 121 public ResultColumn createStructStarResultColumn(ResultSet resultSet, TResultColumn resultColumn) { 122 ResultColumn column = createResultColumn(resultSet, resultColumn); 123 column.setStarColumn(false); 124 return column; 125 } 126 127 /** 128 * STRUCT tree: the result column of one field of {@code customer.*} when the fields of {@code customer} are known, 129 * named after the field. Like {@link #createStructStarResultColumn}, it is not a wildcard over a relation. 130 */ 131 public ResultColumn createStructFieldResultColumn(ResultSet resultSet, TResultColumn resultColumn, String field) { 132 ResultColumn column = createStarResultColumn(resultSet, resultColumn, field); 133 column.setStarColumn(false); 134 column.setStruct(true); 135 return column; 136 } 137 138 public ResultColumn createStarResultColumn(ResultSet resultSetModel, TResultColumn resultColumn, 139 String refColumnName, Column source) { 140 ResultColumn result = createStarResultColumn(resultSetModel, resultColumn, refColumnName); 141 result.inheritIdentifier(source); 142 result.inheritStarColumn(source); 143 return result; 144 } 145 146 public ResultColumn createStarResultColumn(ResultSet resultSetModel, TResultColumn resultColumn, 147 String refColumnName) { 148 if (modelManager.getModel(resultColumn) instanceof LinkedHashMap) { 149 LinkedHashMap<String, ResultColumn> resultColumns = (LinkedHashMap<String, ResultColumn>) modelManager 150 .getModel(resultColumn); 151 if (resultColumns.containsKey(refColumnName)) { 152 return resultColumns.get(refColumnName); 153 } 154 155 ResultColumn column = new ResultColumn(resultSetModel, resultColumn, refColumnName); 156 resultColumns.put(refColumnName, column); 157 return column; 158 } else { 159 LinkedHashMap<String, ResultColumn> resultColumns = new LinkedHashMap<String, ResultColumn>(); 160 ResultColumn column = new ResultColumn(resultSetModel, resultColumn, refColumnName); 161 resultColumns.put(refColumnName, column); 162 modelManager.bindModel(resultColumn, resultColumns); 163 return column; 164 } 165 } 166 167 public ResultColumn createSelectSetResultColumn(ResultSet resultSet, ResultColumn resultColumn) { 168 if (modelManager.getModel(resultColumn) instanceof ResultColumn) { 169 return (ResultColumn) modelManager.getModel(resultColumn); 170 } 171 for (int i = 0; i < resultSet.getColumns().size(); i++) { 172 ResultColumn columnModel = resultSet.getColumns().get(i); 173 if (columnModel.getIdentifierKey() != null && resultColumn.getIdentifierKey() != null 174 ? columnModel.getIdentifierKey().equals(resultColumn.getIdentifierKey()) 175 : DlineageUtil.sameColumnPath(columnModel.getName(), resultColumn.getName())) { 176 modelManager.bindModel(resultColumn, columnModel); 177 return columnModel; 178 } 179 } 180 ResultColumn column = new SelectSetResultColumn(resultSet, resultColumn); 181 if(resultColumn.isStruct()) { 182 column.setStruct(true); 183 } 184 modelManager.bindModel(resultColumn, column); 185 return column; 186 } 187 188 public ResultColumn createSelectSetResultColumn(ResultSet resultSet, TResultColumn resultColumn, int index) { 189 190 for (int i = 0; i < resultSet.getColumns().size(); i++) { 191 ResultColumn columnModel = resultSet.getColumns().get(i); 192 if(columnModel.getColumnObject() == resultColumn) { 193 return columnModel; 194 } 195 } 196 197 SelectSetResultColumn column = new SelectSetResultColumn(resultSet, resultColumn, index); 198 modelManager.bindModel(resultColumn, column); 199 return column; 200 } 201 202 public ResultColumn createAndBindingSelectSetResultColumn(ResultSet resultSet, TResultColumn resultColumn, 203 int index) { 204 if (modelManager.getModel(resultColumn) instanceof ResultColumn) { 205 return (ResultColumn) modelManager.getModel(resultColumn); 206 } 207 ResultColumn column = new SelectSetResultColumn(resultSet, resultColumn, index); 208 modelManager.bindModel(resultColumn, column); 209 return column; 210 } 211 212 public ResultColumn createResultColumn(ResultSet resultSet, TObjectName resultColumn) { 213 if (modelManager.getModel(resultColumn) instanceof ResultColumn) { 214 return (ResultColumn) modelManager.getModel(resultColumn); 215 } 216 if(EDbVendor.dbvoracle == ModelBindingManager.getGlobalVendor()){ 217 ResultColumn column = new ResultColumn(resultSet, resultColumn); 218 modelManager.bindModel(resultColumn, column); 219 return column; 220 } 221 else { 222 String columnName = resultColumn.toString(); 223 for (int i = 0; i < resultSet.getColumns().size(); i++) { 224 ResultColumn columnModel = resultSet.getColumns().get(i); 225 if (matchesColumn(columnModel, columnModel.getName(), resultColumn, columnName)) { 226 modelManager.bindModel(resultColumn, columnModel); 227 return columnModel; 228 } 229 } 230 ResultColumn column = new ResultColumn(resultSet, resultColumn); 231 modelManager.bindModel(resultColumn, column); 232 return column; 233 } 234 } 235 236 public ResultColumn createDeterminedResultColumn(ResultSet resultSet, TObjectName resultColumn) { 237 ResultColumn column = new ResultColumn(resultSet, resultColumn); 238 modelManager.bindModel(resultColumn, column); 239 return column; 240 } 241 242 public ResultColumn createResultColumn(ResultSet resultSet, TExpression expression) { 243 if (modelManager.getModel(expression) instanceof ResultColumn) { 244 return (ResultColumn) modelManager.getModel(expression); 245 } 246 ResultColumn column = new ResultColumn(resultSet, expression); 247 modelManager.bindModel(expression, column); 248 return column; 249 } 250 251 public ResultColumn createResultColumn(ResultSet resultSet, TObjectName resultColumn, boolean forceCreate) { 252 if (!forceCreate) { 253 if (modelManager.getModel(resultColumn) instanceof ResultColumn) { 254 return (ResultColumn) modelManager.getModel(resultColumn); 255 } 256 } 257 258 String columnName = resultColumn.toString(); 259 for (int i = 0; i < resultSet.getColumns().size(); i++) { 260 ResultColumn columnModel = resultSet.getColumns().get(i); 261 if (matchesColumn(columnModel, columnModel.getName(), resultColumn, columnName)) { 262 modelManager.bindModel(resultColumn, columnModel); 263 return columnModel; 264 } 265 } 266 267 ResultColumn column = new ResultColumn(resultSet, resultColumn); 268 modelManager.bindModel(resultColumn, column); 269 return column; 270 } 271 272 public FunctionResultColumn createFunctionResultColumn(Function function, TObjectName functionName) { 273 if (modelManager.getModel(functionName) instanceof FunctionResultColumn) { 274 return (FunctionResultColumn) modelManager.getModel(functionName); 275 } 276 FunctionResultColumn column = new FunctionResultColumn(function, function.getFunctionObject(), functionName); 277 modelManager.bindModel(functionName, column); 278 return column; 279 } 280 281 public FunctionResultColumn createFunctionResultColumn(Function function, TWhenClauseItemList caseFunction) { 282 if (modelManager.getModel(caseFunction) instanceof FunctionResultColumn) { 283 return (FunctionResultColumn) modelManager.getModel(caseFunction); 284 } 285 FunctionResultColumn column = new FunctionResultColumn(function, function.getFunctionObject(), caseFunction); 286 modelManager.bindModel(caseFunction, column); 287 return column; 288 } 289 290 public ResultColumn createMergeResultColumn(ResultSet resultSet, TObjectName resultColumn) { 291 if (modelManager.getMergeModel(resultColumn) instanceof ResultColumn) { 292 return (ResultColumn) modelManager.getMergeModel(resultColumn); 293 } 294 ResultColumn column = new ResultColumn(resultSet, resultColumn); 295 column.nameAfterWrittenAttribute(resultColumn); 296 modelManager.bindMergeModel(resultColumn, column); 297 return column; 298 } 299 300 public ResultColumn createUpdateResultColumn(ResultSet resultSet, TObjectName resultColumn) { 301 if (modelManager.getUpdateModel(resultColumn) instanceof ResultColumn) { 302 return (ResultColumn) modelManager.getUpdateModel(resultColumn); 303 } 304 ResultColumn column = new ResultColumn(resultSet, resultColumn); 305 column.nameAfterWrittenAttribute(resultColumn); 306 modelManager.bindUpdateModel(resultColumn, column); 307 return column; 308 } 309 310 public ResultColumn createResultColumn(QueryTable queryTableModel, TResultColumn resultColumn) { 311 if (modelManager.getModel(resultColumn) instanceof ResultColumn) { 312 return (ResultColumn) modelManager.getModel(resultColumn); 313 } 314 ResultColumn column = new ResultColumn(queryTableModel, resultColumn); 315 modelManager.bindModel(resultColumn, column); 316 return column; 317 } 318 319 public Table createTableFromCreateDDL(TTable table, boolean fromDDL, String tableName) { 320 if (modelManager.getCreateModel(table) instanceof Table) { 321 Table tableModel = (Table) modelManager.getCreateModel(table); 322 tableModel.setPosition(table); 323 return tableModel; 324 } 325 if (modelManager.getModel(table) instanceof Table) { 326 Table tableModel = (Table) modelManager.getModel(table); 327 tableModel.setPosition(table); 328 return tableModel; 329 } 330 if (modelManager 331 .getTableByName(DlineageUtil.getTableIdentityKey(tableName.toString())) instanceof Table) { 332 Table tableModel = (Table) modelManager.getTableByName(DlineageUtil.getTableIdentityKey(tableName.toString())); 333 tableModel.setPosition(table); 334 return tableModel; 335 } 336 if (modelManager.getTableByName( 337 DlineageUtil.getTableIdentityKeyWithDefaultSchema(tableName.toString())) instanceof Table) { 338 Table tableModel = (Table) modelManager 339 .getTableByName(DlineageUtil.getTableIdentityKeyWithDefaultSchema(tableName.toString())); 340 tableModel.setPosition(table); 341 return tableModel; 342 } 343 Table tableModel = new Table(table, tableName); 344 tableModel.setCreateTable(fromDDL); 345 if (!fromDDL) { 346 Table existTable = modelManager.getTableByName(tableModel.getBindingKey()); 347 if (existTable != null) { 348 return existTable; 349 } 350 } 351 modelManager.bindCreateModel(table, tableModel); 352 if(table.getTableType() == ETableSource.unnest) { 353 354 } 355 else { 356 modelManager.bindTableByName(DlineageUtil.getTableIdentityKey(tableName.toString()), tableModel); 357 } 358 return tableModel; 359 } 360 361 /** 362 * The table of a Hive-family LATERAL VIEW whose alias names the table of another view: a table of the free name 363 * {@code tableName} ({@code tmp1-1}), bound to this view, which a lookup of the view finds before its alias 364 * (Mantis 4845). 365 */ 366 /** 367 * Plan section 4.3: the table of a relation a table-generating construct adds to a SELECT, a Hive-family 368 * {@code LATERAL VIEW} here, keyed by its parse node and published under {@code name}. It is never looked up by 369 * that name nor bound to it, so a table of the script with the same name stays a table of its own (Mantis 4890); 370 * the output never merges the two ({@code xml.table.isArrayRelation}). 371 */ 372 public Table createArrayRelationTable(TTable table, String name) { 373 if (modelManager.getBoundModel(table) instanceof Table) { 374 return (Table) modelManager.getBoundModel(table); 375 } 376 Table tableModel = new Table(name); 377 tableModel.dropPosition(); // a table built from a name has none; this one has the view's 378 tableModel.setPosition(table); 379 tableModel.setArrayRelation(true); 380 modelManager.bindModel(table, tableModel); 381 return tableModel; 382 } 383 384 public Table createTableFromCreateDDL(TTable table, boolean fromDDL) { 385 if (modelManager.getCreateModel(table) instanceof Table) { 386 Table tableModel = (Table) modelManager.getCreateModel(table); 387 tableModel.setPosition(table); 388 return tableModel; 389 } 390 if (ModelBindingManager.getGlobalProcedure() != null && ModelBindingManager.isScopedTemporary(table)) { 391 // A declaration in this routine creates its own instance, even when a session 392 // table with the same name is visible. The caller's model must not be mutated. 393 Table tableModel = modelManager.getTemporaryMetadataModel(table); 394 if (tableModel == null) tableModel = new Table(table); 395 else tableModel.setPosition(table); 396 tableModel.setCreateTable(fromDDL, fromDDL); 397 modelManager.bindCreateModel(table, tableModel); 398 modelManager.bindTableByName(DlineageUtil.getTableIdentityKey(table.getTableName().toString()), tableModel); 399 modelManager.bindTableByName(tableModel.getBindingKey(), tableModel); 400 return tableModel; 401 } 402 if (modelManager.getModel(table) instanceof Table) { 403 Table tableModel = (Table) modelManager.getModel(table); 404 tableModel.setPosition(table); 405 return tableModel; 406 } 407 if (modelManager 408 .getTableByName(DlineageUtil.getTableIdentityKey(table.getTableName().toString())) instanceof Table) { 409 Table tableModel = (Table) modelManager.getTableByName(DlineageUtil.getTableIdentityKey(table.getTableName().toString())); 410 tableModel.setPosition(table); 411 return tableModel; 412 } 413 if (modelManager.getTableByName( 414 DlineageUtil.getTableIdentityKeyWithDefaultSchema(table.getTableName().toString())) instanceof Table) { 415 Table tableModel = (Table) modelManager 416 .getTableByName(DlineageUtil.getTableIdentityKeyWithDefaultSchema(table.getTableName().toString())); 417 tableModel.setPosition(table); 418 return tableModel; 419 } 420 Table tableModel = new Table(table); 421 tableModel.setCreateTable(fromDDL, fromDDL); 422 if (!fromDDL) { 423 Table existTable = modelManager.getTableByName(tableModel.getBindingKey()); 424 if (existTable != null) { 425 return existTable; 426 } 427 } 428 modelManager.bindCreateModel(table, tableModel); 429 modelManager.bindTableByName(DlineageUtil.getTableIdentityKey(table.getTableName().toString()), tableModel); 430 modelManager.bindTableByName(tableModel.getBindingKey(), tableModel); 431 return tableModel; 432 } 433 434 public Table createJsonTable(TTable table) { 435 String tableName = table.toString(); 436 if (tableName.toLowerCase(java.util.Locale.ROOT).indexOf("with") != -1) { 437 tableName = table.toString().substring(0, tableName.toLowerCase(java.util.Locale.ROOT).indexOf("with")).trim(); 438 } 439 440 if (table.isLinkTable()) { 441 table = table.getLinkTable(); 442 } 443 444 if (modelManager.getCreateTable(table) != null) { 445 Table tableModel = modelManager.getCreateTable(table); 446 tableModel.setPosition(table); 447 return tableModel; 448 } 449 if (modelManager.getModel(table) instanceof Table) { 450 Table tableModel = (Table) modelManager.getModel(table); 451 tableModel.setPosition(table); 452 return tableModel; 453 } 454 if (modelManager.getTableByName(DlineageUtil.getTableIdentityKey(tableName)) instanceof Table) { 455 Table tableModel = (Table) modelManager.getTableByName(DlineageUtil.getTableIdentityKey(tableName)); 456 tableModel.setPosition(table); 457 return tableModel; 458 } 459 460 if (modelManager.getTableByName(DlineageUtil.getTableIdentityKeyWithDefaultSchema(tableName)) instanceof Table) { 461 Table tableModel = (Table) modelManager.getTableByName(DlineageUtil.getTableIdentityKeyWithDefaultSchema(tableName)); 462 tableModel.setPosition(table); 463 return tableModel; 464 } 465 466 Table tableModel = new Table(table); 467 tableModel.setName(tableName); 468 modelManager.bindModel(table, tableModel); 469 modelManager.bindTableByName(DlineageUtil.getTableIdentityKey(tableName), tableModel); 470 return tableModel; 471 } 472 473 public Table createXmlTable(TTable table) { 474 String tableName = table.toString(); 475 if (tableName.toLowerCase(java.util.Locale.ROOT).indexOf("with") != -1) { 476 tableName = table.toString().substring(0, tableName.toLowerCase(java.util.Locale.ROOT).indexOf("with")).trim(); 477 } 478 479 if (table.isLinkTable()) { 480 table = table.getLinkTable(); 481 } 482 483 if (modelManager.getCreateTable(table) != null) { 484 Table tableModel = modelManager.getCreateTable(table); 485 tableModel.setPosition(table); 486 return tableModel; 487 } 488 if (modelManager.getModel(table) instanceof Table) { 489 Table tableModel = (Table) modelManager.getModel(table); 490 tableModel.setPosition(table); 491 return tableModel; 492 } 493 if (modelManager.getTableByName(DlineageUtil.getTableIdentityKey(tableName)) instanceof Table) { 494 Table tableModel = (Table) modelManager.getTableByName(DlineageUtil.getTableIdentityKey(tableName)); 495 tableModel.setPosition(table); 496 return tableModel; 497 } 498 499 if (modelManager.getTableByName(DlineageUtil.getTableIdentityKeyWithDefaultSchema(tableName)) instanceof Table) { 500 Table tableModel = (Table) modelManager.getTableByName(DlineageUtil.getTableIdentityKeyWithDefaultSchema(tableName)); 501 tableModel.setPosition(table); 502 return tableModel; 503 } 504 505 Table tableModel = new Table(table); 506 tableModel.setName(tableName); 507 modelManager.bindModel(table, tableModel); 508 modelManager.bindTableByName(DlineageUtil.getTableIdentityKey(tableName), tableModel); 509 return tableModel; 510 } 511 512 public Table createTable(TTable table, TObjectName tableBinding) { 513 Table tableModel = createTable(table); 514 modelManager.bindModel(table, tableModel); 515 modelManager.bindTableByName(DlineageUtil.getTableIdentityKey(tableBinding.toString()), tableModel); 516 return tableModel; 517 } 518 519 public Table createTable(TTable table) { 520 TTable occurrence = table; 521 if (table.isLinkTable()) { 522 table = table.getLinkTable(); 523 } 524 525 Table tableModel = null; 526 if (modelManager.getCreateTable(table) != null) { 527 tableModel = modelManager.getCreateTable(table); 528 } 529 if (tableModel == null && modelManager.getModel(table) instanceof Table) { 530 tableModel = (Table) modelManager.getModel(table); 531 } 532 533 String tableName = table.getTableName().toString(); 534 if (table.getSubquery() != null && table.getAliasClause() != null) { 535 tableName = table.getAliasClause().toString(); 536 } 537 538 if (tableModel == null 539 && modelManager.getTableByName(DlineageUtil.getTableIdentityKey(tableName)) instanceof Table) { 540 tableModel = modelManager.visibleTemporaryModel(table, modelManager.getTableByName(DlineageUtil.getTableIdentityKey(tableName))); 541 } 542 543 if (tableModel == null && modelManager 544 .getTableByName(DlineageUtil.getTableIdentityKeyWithDefaultSchema(tableName)) instanceof Table) { 545 tableModel = modelManager.visibleTemporaryModel(table, modelManager.getTableByName(DlineageUtil.getTableIdentityKeyWithDefaultSchema(tableName))); 546 } 547 548 if (tableModel != null) { 549 if (table.getAliasClause() != null) { 550 tableModel.setAlias(table.getAliasName()); 551 } 552 tableModel.setCurrentAlias(table.getAliasName()); 553 modelManager.updateTableAliasMap(table); 554 tableModel.setPosition(table); 555 return modelManager.observeCommentOccurrence(occurrence, tableModel); 556 } 557 558 tableModel = new Table(table); 559 Table existTable = modelManager.visibleTemporaryModel(table, 560 modelManager.getTableByName(tableModel.getBindingKey())); 561 if (existTable != null) { 562 return modelManager.observeCommentOccurrence(occurrence, existTable); 563 } 564 modelManager.bindModel(table, tableModel); 565 modelManager.bindTableByName(DlineageUtil.getTableIdentityKey(tableName), tableModel); 566 modelManager.bindTableByName(tableModel.getBindingKey(), tableModel); 567 568 appendTableColumns(tableModel); 569 570 return modelManager.observeCommentOccurrence(occurrence, tableModel); 571 } 572 573 protected void appendTableColumns(Table tableModel) { 574 TCustomSqlStatement stmt = ModelBindingManager.getGlobalStmtStack().peek(); 575 if (stmt instanceof TCreateTableSqlStatement || stmt instanceof TCreateViewSqlStatement) { 576 return; 577 } 578 if (stmt instanceof TInsertSqlStatement && ((TInsertSqlStatement) stmt).getColumnList() != null) { 579 return; 580 } 581 TTable origin = tableModel.getTableObject(); 582 if (origin != null && origin.getEffectType() == ETableEffectType.tetSelectInto 583 && origin.getSqlEnv() instanceof ProcedureEnvironment) return; 584 if (origin != null && origin.isResolved() && origin.getSqlEnv() instanceof ProcedureEnvironment) { 585 TSQLTable metadata = origin.getResolvedTable(); 586 if (metadata != null && !metadata.getColumnList().isEmpty()) { 587 for (String column : metadata.getColumns(true)) { 588 TableColumn model = createCatalogTableColumn(tableModel, column); 589 // Catalog column spellings have no token span. Use a real reference 590 // (including the star being expanded), or leave the position unknown. 591 model.setStartPosition(null); 592 model.setEndPosition(null); 593 for (TObjectName reference : origin.getLinkedColumns()) { 594 boolean star = reference.getPartToken() != null 595 && reference.getPartToken().tokentype == gudusoft.gsqlparser.ETokenType.ttasterisk; 596 if (star || SQLUtil.sameName(origin.dbvendor, 597 gudusoft.gsqlparser.sqlenv.ESQLDataObjectType.dotColumn, column, reference.getColumnNameOnly())) { 598 TableColumnRelationshipElement occurrence = new TableColumnRelationshipElement(model, null, reference); 599 model.setStartPosition(occurrence.getReferenceStartPosition()); 600 model.setEndPosition(occurrence.getReferenceEndPosition()); 601 if (model.getStartPosition() != null) break; 602 } 603 } 604 } 605 tableModel.setFromDDL(true); 606 tableModel.setCreateTable(true); 607 } 608 return; 609 } 610 if (ModelBindingManager.getGlobalSQLEnv() != null) { 611 // Ask the environment with the name as WRITTEN: same qualification, delimiters kept. 612 // getTableFullName is dlineage's display/matching name and removes delimiters, so it 613 // must never be a catalog fallback. If "Orders" is absent, retrying as the unquoted 614 // word Orders changes its Oracle identity to ORDERS and attaches another table's 615 // columns to a reference that the catalog does not contain. 616 ArrayList<String> columns = ModelBindingManager.getGlobalSQLEnv() 617 .getColumnsInTable(DlineageUtil.getTableFullNameAsWritten(tableModel.getName()), true); 618 if (columns != null && !columns.isEmpty()) { 619 for (String column : columns) { 620 if (SQLUtil.isEmpty(column)) { 621 continue; 622 } 623 createCatalogTableColumn(tableModel, column); 624 } 625 tableModel.setFromDDL(true); 626 tableModel.setCreateTable(true); 627 } 628 } 629 } 630 631 public Table createTriggerOnTable(TTable table) { 632 if (modelManager.getCreateTable(table) != null) { 633 Table tableModel = modelManager.getCreateTable(table); 634 tableModel.setPosition(table); 635 return tableModel; 636 } 637 if (modelManager.getModel(table) instanceof Table) { 638 Table tableModel = (Table) modelManager.getModel(table); 639 tableModel.setPosition(table); 640 return tableModel; 641 } 642 if (modelManager 643 .getTableByName(DlineageUtil.getTableIdentityKey(table.getTableName().toString())) instanceof Table) { 644 Table tableModel = (Table) modelManager.getTableByName(DlineageUtil.getTableIdentityKey(table.getTableName().toString())); 645 tableModel.setPosition(table); 646 return tableModel; 647 } 648 if (modelManager.getTableByName( 649 DlineageUtil.getTableIdentityKeyWithDefaultSchema(table.getTableName().toString())) instanceof Table) { 650 Table tableModel = (Table) modelManager.getTableByName(DlineageUtil.getTableIdentityKeyWithDefaultSchema(table.getTableName().toString())); 651 tableModel.setPosition(table); 652 return tableModel; 653 } 654 Table tableModel = new Table(table); 655 Table existTable = modelManager.getTableByName(tableModel.getBindingKey()); 656 if (existTable != null) { 657 return existTable; 658 } 659 modelManager.bindModel(table, tableModel); 660 modelManager.bindTableByName(DlineageUtil.getTableIdentityKey(table.getTableName().toString()), tableModel); 661 modelManager.bindTableByName(tableModel.getBindingKey(), tableModel); 662 return tableModel; 663 } 664 665 public Table createTableByName(TObjectName tableName) { 666 return createTableByName(tableName, false); 667 } 668 669 /** 670 * The relation a {@code %TYPE} anchor's qualifier names ({@code rec.field%TYPE}, {@code tab.col%TYPE}): a one-part 671 * qualifier may be a record or cursor declared in scope, which answers before a table of the same name, as PL/SQL 672 * scoping does. 673 */ 674 public Table createTypeAnchor(TObjectName qualifier) { 675 return createTableByName(qualifier, true, qualifier.getSchemaToken() == null); 676 } 677 678 public Table createTableByName(TObjectName tableName, boolean create) { 679 return createTableByName(tableName, create, false); 680 } 681 682 private Table createTableByName(TObjectName tableName, boolean create, boolean anchor) { 683 if (!ModelBindingManager.getGlobalStmtStack().isEmpty()) { 684 for (TTable target : ModelBindingManager.getGlobalStmtStack().peek().tables) { 685 if (target.getTableName() == tableName 686 && target.getSqlEnv() instanceof ProcedureEnvironment 687 && target.getEffectType() == ETableEffectType.tetSelectInto) { 688 return createTable(target); 689 } 690 } 691 } 692 // A relation name: the table path, else a local relation (UPDATE @t, OUTPUT INTO @t); an anchor's local first. 693 Table bound = modelManager.resolveMixedRelation(tableName.toString(), 694 modelManager.getTableByName(DlineageUtil.getTableIdentityKey(tableName.toString())), anchor); 695 if (bound != null) { 696 Table tableModel = bound; 697 tableModel.setPosition(tableName); 698 return tableModel; 699 } 700 Table tableModel = new Table(tableName); 701 Table existTable = modelManager.getTableByName(tableModel.getBindingKey()); 702 if (existTable != null) { 703 existTable.setPosition(tableName); 704 return existTable; 705 } 706 if (tableModel.getColumns() == null || tableModel.getColumns().isEmpty()) { 707 tableModel.addColumnsFromSQLEnv(); 708 } 709 modelManager.bindTableByName(DlineageUtil.getTableIdentityKey(tableName.toString()), tableModel); 710 modelManager.bindTableByName(tableModel.getBindingKey(), tableModel); 711 if (create) { 712 modelManager.bindCreateModel(tableName, tableModel); 713 } 714 return tableModel; 715 } 716 717 /** 718 * The relation a {@code %ROWTYPE} anchor names: a cursor, a record or a table. The visible local answers before a 719 * table of the same name, as PL/SQL scoping does. 720 */ 721 public Table createRowTypeAnchor(String anchorName) { 722 return createTableByName(anchorName, false, true); 723 } 724 725 public Table createTableByName(String tableName, boolean create) { 726 return createTableByName(tableName, create, false); 727 } 728 729 /** 730 * The path node named {@code name} that {@code identity} identifies, made as needed. A file a BULK INSERT names 731 * by the same text in two contexts -- under two external data sources, or built by a runtime template -- is two 732 * files, so its node is bound by that identity, not by its name (Mantis 4962). 733 */ 734 public Table createPathModel(String name, Object identity) { 735 Table bound = modelManager.getTableByName(identity); 736 if (bound != null) { 737 return bound; 738 } 739 Table tableModel = new Table(name); 740 modelManager.bindTableByName(identity, tableModel); 741 return tableModel; 742 } 743 744 private Table createTableByName(String tableName, boolean create, boolean anchor) { 745 Table bound = modelManager.resolveMixedRelation(tableName, 746 modelManager.getTableByName(DlineageUtil.getTableIdentityKey(tableName.toString())), anchor); 747 if (bound != null) { 748 return bound; 749 } 750 Table tableModel = new Table(tableName); 751 Table existTable = modelManager.getTableByName(tableModel.getBindingKey()); 752 if (existTable != null) { 753 return existTable; 754 } 755 if (tableModel.getColumns() == null || tableModel.getColumns().isEmpty()) { 756 tableModel.addColumnsFromSQLEnv(); 757 } 758 modelManager.bindTableByName(DlineageUtil.getTableIdentityKey(tableName.toString()), tableModel); 759 modelManager.bindTableByName(tableModel.getBindingKey(), tableModel); 760 if (create) { 761 modelManager.bindCreateModel(tableName, tableModel); 762 } 763 return tableModel; 764 } 765 766 /** 767 * The model of a table named by the catalog rather than by the SQL (the base table of a catalog synonym): the 768 * table the analysis holds under that name, else a new one, bound like any table. A table no SQL named has no 769 * position, so a new one publishes none, for itself or for the columns the catalog gives it: not the unknown 770 * position of a name-only model, not the position a synthesized column name would claim (line 1). Merged with the 771 * same table from the metadata, the metadata's own coordinates stand. 772 */ 773 public Table createCatalogNamedTable(String tableName) { 774 long lastId = modelManager.TABLE_COLUMN_ID; 775 Table table = createTableByName(tableName, false); 776 if (table != null && table.getId() > lastId) { 777 table.dropPosition(); 778 for (TableColumn column : table.getColumns()) { 779 column.setStartPosition(null); 780 column.setEndPosition(null); 781 } 782 } 783 return table; 784 } 785 786 public Table createStage(TObjectName stageName) { 787 // Named like a table, and found by the table references that name it (SELECT FROM a stream, @stage). 788 Object key = DlineageUtil.getTableIdentityKey(stageName.toString()); 789 if (modelManager.getTableByName(key) instanceof Table) { 790 return (Table) modelManager.getTableByName(key); 791 } 792 Table tableModel = new Table(stageName); 793 modelManager.bindTableByName(key, tableModel); 794 modelManager.bindCreateModel(stageName, tableModel); 795 796 TObjectName stageVariableName = new TObjectName(); 797 stageVariableName.setString("@" + stageName.getColumnNameOnly()); 798 modelManager.bindCreateModel(stageVariableName, tableModel); 799 800 return tableModel; 801 } 802 803 public Table createDataSource(TObjectName dataSourceName) { 804 // Named like a table, and found by the table references that name it (SELECT FROM a stream, @stage). 805 Object key = DlineageUtil.getTableIdentityKey(dataSourceName.toString()); 806 if (modelManager.getTableByName(key) instanceof Table) { 807 return (Table) modelManager.getTableByName(key); 808 } 809 Table tableModel = new Table(dataSourceName); 810 tableModel.setDataSource(true); 811 modelManager.bindTableByName(key, tableModel); 812 modelManager.bindCreateModel(dataSourceName, tableModel); 813 814 return tableModel; 815 } 816 817 public Table createStream(TObjectName streamName) { 818 // Named like a table, and found by the table references that name it (SELECT FROM a stream, @stage). 819 Object key = DlineageUtil.getTableIdentityKey(streamName.toString()); 820 if (modelManager.getTableByName(key) instanceof Table) { 821 return (Table) modelManager.getTableByName(key); 822 } 823 Table tableModel = new Table(streamName); 824 tableModel.setStream(true); 825 modelManager.bindTableByName(key, tableModel); 826 modelManager.bindCreateModel(streamName, tableModel); 827 return tableModel; 828 } 829 830 public Table createConstantsTable(TCustomSqlStatement stmt) { 831 stmt = DlineageUtil.getTopStmt(stmt); 832 String sqlHash = String.valueOf(stmt.hashCode()); 833 if (modelManager.getTableByName(sqlHash) instanceof Table) { 834 return (Table) modelManager.getTableByName(sqlHash); 835 } 836 Table tableModel = new Table("SQL_CONSTANTS"); 837 tableModel.setConstant(true); 838 modelManager.bindTableByName(sqlHash, tableModel); 839 modelManager.bindCreateModel(sqlHash, tableModel); 840 return tableModel; 841 } 842 843 public Table createConstantsTable(String sqlHash) { 844 if (modelManager.getTableByName(sqlHash) instanceof Table) { 845 return (Table) modelManager.getTableByName(sqlHash); 846 } 847 Table tableModel = new Table("SQL_CONSTANTS"); 848 tableModel.setConstant(true); 849 modelManager.bindTableByName(sqlHash, tableModel); 850 modelManager.bindCreateModel(sqlHash, tableModel); 851 return tableModel; 852 } 853 854 /** 855 * The relation of a declared JSON variable OPENJSON reads: the variable already declared in the declaration's own 856 * scope, else a new relation there. 857 */ 858 public Table createJsonVariable(TObjectName jsonVariable) { 859 LocalSymbolKey key = localKey(LocalSymbolNames.baseName(jsonVariable), LocalSymbolKey.Kind.VARIABLE); 860 Table existing = modelManager.getLocalSymbol(key); 861 if (existing != null) { 862 return existing; 863 } 864 Table tableModel = new Table(jsonVariable); 865 modelManager.publishLocalSymbol(key, tableModel); 866 modelManager.bindCreateModel(jsonVariable, tableModel); 867 return tableModel; 868 } 869 870 public Alias createAlias(TAliasClause aliasClause) { 871 if (modelManager.getModel(aliasClause) instanceof Alias) { 872 return (Alias) modelManager.getModel(aliasClause); 873 } 874 875 Alias alias = new Alias(aliasClause); 876 modelManager.bindModel(aliasClause, alias); 877 878 return alias; 879 } 880 881 public Function createFunction(TFunctionCall functionCall) { 882 if (modelManager.getModel(functionCall) instanceof Function) { 883 return (Function) modelManager.getModel(functionCall); 884 } 885 886 Function function = new Function(functionCall); 887 function.setDetermined(false); 888 modelManager.bindModel(functionCall, function); 889 890 return function; 891 } 892 893 public Function createFunction(TObjectName functionName) { 894 if (modelManager.getModel(functionName) instanceof Function) { 895 return (Function) modelManager.getModel(functionName); 896 } 897 898 Function function = new Function(functionName); 899 function.setDetermined(false); 900 modelManager.bindModel(functionName, function); 901 902 return function; 903 } 904 905 public Function createFunction(TCaseExpression caseExpression) { 906 if (modelManager.getModel(caseExpression) instanceof Function) { 907 return (Function) modelManager.getModel(caseExpression); 908 } 909 910 Function function = new Function(caseExpression); 911 modelManager.bindModel(caseExpression, function); 912 913 return function; 914 } 915 916 public QueryTable createQueryTable(TTable table) { 917 QueryTable tableModel = null; 918 919 if (table.getCTE() != null) { 920 if (modelManager.getModel(table.getCTE()) instanceof QueryTable) { 921 return (QueryTable) modelManager.getModel(table.getCTE()); 922 } 923 924 tableModel = new QueryTable(table); 925 926 modelManager.bindModel(table.getCTE(), tableModel); 927 } else if (table.getAliasClause() != null && table.getAliasClause().getColumns() != null) { 928 if (modelManager.getModel(table.getAliasClause().getColumns()) instanceof QueryTable) { 929 return (QueryTable) modelManager.getModel(table.getAliasClause().getColumns()); 930 } 931 932 tableModel = new QueryTable(table); 933 TObjectNameList columns = table.getAliasClause().getColumns(); 934 modelManager.bindModel(columns, tableModel); 935 for (int i = 0; i < columns.size(); i++) { 936 createResultColumn(tableModel, columns.getObjectName(i)); 937 } 938 modelManager.bindModel(table, tableModel); 939 } else if (table.getSubquery() != null && !table.getSubquery().isCombinedQuery()) { 940 if (modelManager.getModel(table.getSubquery().getResultColumnList()) instanceof QueryTable) { 941 return (QueryTable) modelManager.getModel(table.getSubquery().getResultColumnList()); 942 } 943 944 tableModel = new QueryTable(table); 945 if (table.getSubquery().getResultColumnList() != null) { 946 modelManager.bindModel(table.getSubquery().getResultColumnList(), tableModel); 947 } else if (table.getSubquery().getTransformClause() != null) { 948 modelManager.bindModel(table.getSubquery().getTransformClause(), tableModel); 949 } 950 modelManager.bindModel(table, tableModel); 951 } else if (table.getOutputMerge() != null) { 952 if (modelManager 953 .getModel(table.getOutputMerge().getOutputClause().getSelectItemList()) instanceof QueryTable) { 954 return (QueryTable) modelManager.getModel(table.getOutputMerge().getOutputClause().getSelectItemList()); 955 } 956 957 tableModel = new QueryTable(table); 958 modelManager.bindModel(table.getOutputMerge().getOutputClause().getSelectItemList(), tableModel); 959 modelManager.bindModel(table, tableModel); 960 } else { 961 if (modelManager.getModel(table) instanceof QueryTable) { 962 return (QueryTable) modelManager.getModel(table); 963 } 964 965 if(ModelBindingManager.getGlobalStmtStack().peek() instanceof TMergeSqlStatement) { 966 TMergeSqlStatement mergeStmt = (TMergeSqlStatement)ModelBindingManager.getGlobalStmtStack().peek(); 967 if (modelManager.getModel(mergeStmt) instanceof QueryTable) { 968 return (QueryTable) modelManager.getModel(mergeStmt); 969 } 970 tableModel = new QueryTable(mergeStmt); 971 modelManager.bindModel(mergeStmt, tableModel); 972 } 973 else { 974 tableModel = new QueryTable(table); 975 modelManager.bindModel(table, tableModel); 976 } 977 } 978 return tableModel; 979 } 980 981 /** 982 * Check if a column reference is a standard qualified table.column reference 983 * (not a struct field access). Returns true when tableToken matches the source table's name. 984 */ 985 private boolean isQualifiedColumnReference(TObjectName column) { 986 if (column.getSourceTable() == null || column.getTableToken() == null) { 987 return false; 988 } 989 String tableTokenStr = column.getTableToken().getAstext(); 990 TTable src = column.getSourceTable(); 991 String tableName = src.getTableName() != null ? src.getTableName().getTableString() : ""; 992 java.util.List<String> parts = gudusoft.gsqlparser.util.SQLUtil.parseNames(tableName); 993 String simpleTableName = parts.isEmpty() ? tableName : parts.get(parts.size() - 1); 994 if (tableTokenStr.equalsIgnoreCase(simpleTableName)) { 995 return true; 996 } 997 String aliasName = src.getAliasName(); 998 if (aliasName != null && tableTokenStr.equalsIgnoreCase(aliasName)) { 999 return true; 1000 } 1001 return false; 1002 } 1003 1004 /** 1005 * The field path node a read of a field binds to: under the column the read names, the node for its path. The 1006 * column is the one a whole-column read of it would bind to, declared or, without DDL, inferred; the node and those 1007 * above it are created when the table does not have them yet, so a read binds to the same node with and without 1008 * DDL (Mantis 4822, STRUCT tree plan rule R1). Null when the table has no such column. 1009 */ 1010 private TableColumn createFieldPathRead(Table table, TObjectName column, TSourceToken baseToken, 1011 gudusoft.gsqlparser.common.structured.StructuredColumnPath read) { 1012 if (baseToken == null) { 1013 return null; 1014 } 1015 TObjectName base = TObjectName.createObjectName(ModelBindingManager.getGlobalVendor(), EDbObjectType.column, 1016 baseToken); 1017 TableColumn declared = createTableColumn(table, base, false); 1018 if (declared == null) { 1019 return null; 1020 } 1021 return createFieldPath(declared.getDeclaredColumn(), read.getSegments(), column); 1022 } 1023 1024 /** 1025 * STRUCT tree, BigQuery: a read of a field of the element of {@code UNNEST(items) AS i}, {@code i.item_name}, 1026 * reads {@code items[*].item_name} of the table {@code items} belongs to. The unnest table's one column, the 1027 * element, comes from the element node {@code items[*]}; only BigQuery's UNNEST is linked that way 1028 * ({@code DataFlowAnalyzer.linkUnnestElement}). Null for any other read, and for the element itself. 1029 */ 1030 private TableColumn createUnnestElementRead(Table table, TObjectName column) { 1031 boolean lateralView = isLateralViewElementTable(table); 1032 if ((table.getSubType() != SubType.unnest && !lateralView) 1033 || (!lateralView && table.getColumns().size() != 1)) { 1034 return null; 1035 } 1036 TableColumn unnestColumn = lateralView ? lateralViewColumnRead(table, column) : table.getColumns().get(0); 1037 if (unnestColumn == null) { 1038 return null; 1039 } 1040 TableColumn element = unnestColumn.getSourceColumn(); 1041 if (element == null || !element.isFieldPathNode()) { 1042 // The array comes from a CTE or a derived table, whose result set carries no field nodes (Mantis 4836): 1043 // a field read through the element's name, BigQuery i.item_name over UNNEST(c.items) AS i, reads the 1044 // whole element i (Mantis 4835). Anything else is not placed here. 1045 gudusoft.gsqlparser.common.structured.StructuredColumnPath read = column.getFieldPathRead(); 1046 if (read != null && read.getRootColumn() != null && !read.getSegments().isEmpty() 1047 && !DlineageUtil.isStarName(column) 1048 && DlineageUtil.sameName(ESQLDataObjectType.dotColumn, read.getRootColumn(), unnestColumn.getName())) { 1049 return unnestColumn; 1050 } 1051 return null; 1052 } 1053 List<gudusoft.gsqlparser.common.structured.StructuredPathSegment> elementPath = element.getFieldPath().getSegments(); 1054 if (elementPath.get(elementPath.size() - 1).getKind() 1055 != gudusoft.gsqlparser.common.structured.StructuredPathSegmentKind.ARRAY_ELEMENT) { 1056 return null; 1057 } 1058 gudusoft.gsqlparser.common.structured.StructuredColumnPath read = column.getFieldPathRead(); 1059 String head = read != null ? read.getRootColumn() : DlineageUtil.rawColumnName(column); 1060 if (head == null || head.isEmpty() || DlineageUtil.isStarName(column) || namesUnnestOffset(table, head)) { 1061 return null; 1062 } 1063 List<gudusoft.gsqlparser.common.structured.StructuredPathSegment> segments = 1064 new ArrayList<gudusoft.gsqlparser.common.structured.StructuredPathSegment>(elementPath); 1065 if (DlineageUtil.sameName(ESQLDataObjectType.dotColumn, head, unnestColumn.getName())) { 1066 // the element itself (i), or a field of it read through the element's name (BigQuery i.item_name); a 1067 // LATERAL VIEW's element read whole reads the view's column, as Mantis 4845 decided 1068 if (read == null) { 1069 return null; 1070 } 1071 segments.addAll(read.getSegments()); 1072 } else { 1073 segments.add(gudusoft.gsqlparser.common.structured.StructuredPathSegment.field(head)); 1074 if (read != null) { 1075 segments.addAll(read.getSegments()); 1076 } 1077 } 1078 return createFieldPath(element.getDeclaredColumn(), segments, column); 1079 } 1080 1081 /** 1082 * The field path node of {@code declared} at {@code segments}, with every node above it: {@code customer}, 1083 * {@code customer.address}, {@code customer.address.city}. Existing nodes are found by their structure, the parent 1084 * node and the segment, each field name compared by the analysis's identifier rules; missing ones are created with 1085 * the position of {@code position}. 1086 * 1087 * @param declared the declared column the path belongs to 1088 * @param segments the path under it 1089 * @param position the name whose position created nodes report 1090 * @return the node, {@code declared} itself for an empty path 1091 */ 1092 public TableColumn createFieldPath(TableColumn declared, 1093 List<gudusoft.gsqlparser.common.structured.StructuredPathSegment> segments, TObjectName position) { 1094 return createFieldPath(declared, segments, position, false); 1095 } 1096 1097 /** 1098 * {@link #createFieldPath} for the DDL that declares the path: a node is made even where the declaration in force 1099 * would refuse it, because this statement is the new declaration (plan rule R10). 1100 */ 1101 public TableColumn createDeclaredFieldPath(TableColumn declared, 1102 List<gudusoft.gsqlparser.common.structured.StructuredPathSegment> segments, TObjectName position) { 1103 return createFieldPath(declared, segments, position, true); 1104 } 1105 1106 private TableColumn createFieldPath(TableColumn declared, 1107 List<gudusoft.gsqlparser.common.structured.StructuredPathSegment> segments, TObjectName position, 1108 boolean declaring) { 1109 TableColumn node = declared; 1110 List<gudusoft.gsqlparser.common.structured.StructuredPathSegment> refined = 1111 new ArrayList<gudusoft.gsqlparser.common.structured.StructuredPathSegment>(segments.size()); 1112 for (int i = 0; i < segments.size(); i++) { 1113 gudusoft.gsqlparser.common.structured.StructuredPathSegment segment = refineByDeclaredType(node, 1114 segments.get(i)); 1115 if (segment == null) { 1116 // a subscript of a column or field the DDL declares as a STRUCT or a scalar (JSON): no element is 1117 // proven, the read is of the node itself, as before rule R6 1118 return node; 1119 } 1120 refined.add(segment); 1121 TableColumn child = findFieldPathChild(declared.getTable(), node, segment); 1122 if (!declaring && node.isFieldsDeclared() && (child == null || !child.isDeclaredField())) { 1123 // the declared STRUCT type in force has no such field: no node, as for a column a declared table 1124 // lacks; a field only an earlier CREATE TABLE of the name declared is not a field any more 1125 return null; 1126 } 1127 if (child == null) { 1128 child = new TableColumn(declared.getTable(), position, node, 1129 new gudusoft.gsqlparser.common.structured.StructuredColumnPath(declared.getName(), 1130 new ArrayList<gudusoft.gsqlparser.common.structured.StructuredPathSegment>(refined))); 1131 } 1132 node = child; 1133 } 1134 return node; 1135 } 1136 1137 /** 1138 * Plan rule R6: a subscript of a column or field the DDL declares as a MAP reads its value, of one declared as an 1139 * ARRAY its element; of one declared as a STRUCT or a scalar (BigQuery {@code payload[OFFSET(0)]} of a STRUCT, 1140 * {@code json_col['a']} of a JSON) it proves no element, and null tells the caller to stop at the node. A subscript 1141 * of a node no DDL declares keeps the vendor's rule; other steps are kept. 1142 */ 1143 private static gudusoft.gsqlparser.common.structured.StructuredPathSegment refineByDeclaredType(TableColumn node, 1144 gudusoft.gsqlparser.common.structured.StructuredPathSegment segment) { 1145 gudusoft.gsqlparser.common.structured.StructuredPathSegmentKind kind = segment.getKind(); 1146 if (kind != gudusoft.gsqlparser.common.structured.StructuredPathSegmentKind.ARRAY_ELEMENT 1147 && kind != gudusoft.gsqlparser.common.structured.StructuredPathSegmentKind.SUBSCRIPT) { 1148 return segment; 1149 } 1150 gudusoft.gsqlparser.common.structured.DeclaredTypeNode.Kind declared = node.getDeclaredKind(); 1151 if (declared == gudusoft.gsqlparser.common.structured.DeclaredTypeNode.Kind.MAP) { 1152 return gudusoft.gsqlparser.common.structured.StructuredPathSegment.mapValue(); 1153 } 1154 if (declared == gudusoft.gsqlparser.common.structured.DeclaredTypeNode.Kind.STRUCT 1155 || declared == gudusoft.gsqlparser.common.structured.DeclaredTypeNode.Kind.SCALAR) { 1156 return null; 1157 } 1158 if (kind == gudusoft.gsqlparser.common.structured.StructuredPathSegmentKind.SUBSCRIPT) { 1159 return gudusoft.gsqlparser.common.structured.StructuredPathSegment.arrayElement(); 1160 } 1161 return segment; 1162 } 1163 1164 /** Whether {@code name} is the WITH OFFSET alias of the UNNEST {@code table} models, a column of its own. */ 1165 private static boolean namesUnnestOffset(Table table, String name) { 1166 TTable unnest = table.getTableObject(); 1167 if (unnest == null || unnest.getUnnestClause() == null || unnest.getUnnestClause().getWithOffsetAlais() == null 1168 || unnest.getUnnestClause().getWithOffsetAlais().getAliasName() == null) { 1169 return false; 1170 } 1171 return DlineageUtil.sameName(ESQLDataObjectType.dotColumn, 1172 unnest.getUnnestClause().getWithOffsetAlais().getAliasName().toString(), name); 1173 } 1174 1175 /** 1176 * Whether {@code table} is a Hive-family LATERAL VIEW whose one column is the element of an array, 1177 * {@code item} in {@code LATERAL VIEW explode(items) lv AS item} ({@code DataFlowAnalyzer.linkLateralViewElement}): 1178 * a read of a field of it reads the element's field of the array's table, as for an UNNEST (Mantis 4845). 1179 */ 1180 private static boolean isLateralViewElementTable(Table table) { 1181 if (!table.isArrayRelation()) { 1182 return false; 1183 } 1184 for (TableColumn column : table.getColumns()) { 1185 if (isElementColumn(column)) { 1186 return true; 1187 } 1188 } 1189 return false; 1190 } 1191 1192 /** Whether {@code column} of a LATERAL VIEW is linked to an array's element node ({@code items[*]}). */ 1193 private static boolean isElementColumn(TableColumn column) { 1194 TableColumn element = column.getSourceColumn(); 1195 if (element == null || !element.isFieldPathNode()) { 1196 return false; 1197 } 1198 List<gudusoft.gsqlparser.common.structured.StructuredPathSegment> path = element.getFieldPath().getSegments(); 1199 return !path.isEmpty() && path.get(path.size() - 1).getKind() 1200 == gudusoft.gsqlparser.common.structured.StructuredPathSegmentKind.ARRAY_ELEMENT; 1201 } 1202 1203 /** The column of a LATERAL VIEW whose field {@code column} reads, by the head of its field path; else null. */ 1204 private static TableColumn lateralViewColumnOfFieldRead(Table table, TObjectName column) { 1205 gudusoft.gsqlparser.common.structured.StructuredColumnPath read = column.getFieldPathRead(); 1206 if (read == null || read.getRootColumn() == null || DlineageUtil.isStarName(column)) { 1207 return null; 1208 } 1209 for (TableColumn candidate : table.getColumns()) { 1210 if (DlineageUtil.sameName(ESQLDataObjectType.dotColumn, read.getRootColumn(), candidate.getName())) { 1211 return candidate; 1212 } 1213 } 1214 return null; 1215 } 1216 1217 /** 1218 * The element column of a LATERAL VIEW that {@code column} reads: the one its name (or the head of its field path) 1219 * names, when that column is linked to an element node; null otherwise (a position or a whole column). 1220 */ 1221 private static TableColumn lateralViewColumnRead(Table table, TObjectName column) { 1222 gudusoft.gsqlparser.common.structured.StructuredColumnPath read = column.getFieldPathRead(); 1223 String head = read != null ? read.getRootColumn() : DlineageUtil.rawColumnName(column); 1224 if (head == null) { 1225 return null; 1226 } 1227 for (TableColumn candidate : table.getColumns()) { 1228 if (isElementColumn(candidate) 1229 && DlineageUtil.sameName(ESQLDataObjectType.dotColumn, head, candidate.getName())) { 1230 return candidate; 1231 } 1232 } 1233 return null; 1234 } 1235 1236 /** Whether {@code table} models an UNNEST, whichever path of the analysis modelled it. */ 1237 private static boolean isUnnestTable(Table table) { 1238 return table.getSubType() == SubType.unnest || (table.getTableObject() != null 1239 && table.getTableObject().getTableType() == ETableSource.unnest); 1240 } 1241 1242 /** 1243 * The field path node of {@code table} directly below {@code parent} at {@code segment}, or null: found by its 1244 * structural identity ({@link FieldNodeKey}), the declared column and the path with each field name keyed by the 1245 * analysis's identifier rules. 1246 */ 1247 private static TableColumn findFieldPathChild(Table table, TableColumn parent, 1248 gudusoft.gsqlparser.common.structured.StructuredPathSegment segment) { 1249 List<gudusoft.gsqlparser.common.structured.StructuredPathSegment> path = 1250 new ArrayList<gudusoft.gsqlparser.common.structured.StructuredPathSegment>(); 1251 if (parent.isFieldPathNode()) { 1252 path.addAll(parent.getFieldPath().getSegments()); 1253 } 1254 path.add(segment); 1255 TableColumn found = table.getFieldPathNode(FieldNodeKey.of(parent.getDeclaredColumn(), path)); 1256 return found; 1257 } 1258 1259 1260 public TableColumn createTableColumn(Table table, TObjectName column, boolean fromCreateTable) { 1261 if (modelManager.getModel(new Pair<Table, TObjectName>(table, column)) instanceof TableColumn) { 1262 return (TableColumn) modelManager.getModel(new Pair<Table, TObjectName>(table, column)); 1263 } 1264 // STRUCT tree: a read of a field binds to its field path node, under the column it reads 1265 if (!fromCreateTable && !table.isPseudo() && gudusoft.gsqlparser.nodes.structured.NestedAccessProfile.forVendor(ModelBindingManager.getGlobalVendor()).buildsDeclaredTree()) { 1266 if (isUnnestTable(table) || table.isArrayRelation()) { 1267 // A column of an UNNEST has no field path nodes of its own. BigQuery: a field of the element is a 1268 // node of the array's table. Trino family: UNNEST expands a ROW element into a column per field, so 1269 // without types it is unknown which field of the element a column is; a read of a field of it reads 1270 // the column. 1271 TableColumn elementField = createUnnestElementRead(table, column); 1272 if (elementField == null && table.isArrayRelation()) { 1273 // a LATERAL VIEW column that is no array's element (posexplode's item, a UDTF's output): a read of 1274 // a field of it reads the column, as Mantis 4845 decided; it has no field nodes of its own 1275 elementField = lateralViewColumnOfFieldRead(table, column); 1276 } 1277 if (elementField != null) { 1278 modelManager.bindModel(new Pair<Table, TObjectName>(table, column), elementField); 1279 return elementField; 1280 } 1281 } else { 1282 gudusoft.gsqlparser.common.structured.StructuredColumnPath read = column.getFieldPathRead(); 1283 TSourceToken baseToken = column.getFieldPathBaseToken(); 1284 if (read == null) { 1285 // customer.*: every field of customer, which the node of customer stands for 1286 read = column.getStructStarPath(); 1287 baseToken = column.getStructStarBaseToken(); 1288 } 1289 if (read != null) { 1290 TableColumn node = createFieldPathRead(table, column, baseToken, read); 1291 if (node != null) { 1292 modelManager.bindModel(new Pair<Table, TObjectName>(table, column), node); 1293 } 1294 return node; 1295 } 1296 } 1297 } 1298 // a CTAS column created from a select item that reads a field; the orphan bucket holds reads 1299 String fieldLeaf = fromCreateTable && !table.isPseudo() ? targetFieldLeaf(column) : null; 1300 // Outside the STRUCT tree (Redshift SUPER, Oracle object columns) a resolved attribute read, 1301 // o.customer.address.city, is a read of its BASE column customer; its last segment named no column 1302 // and the read was lost (Mantis 4839, 4840) 1303 EDbVendor attributeVendor = ModelBindingManager.getGlobalVendor(); 1304 String attributeBase = (!fromCreateTable && (attributeVendor == EDbVendor.dbvredshift || attributeVendor == EDbVendor.dbvoracle) 1305 && column.getFieldPathRead() != null && column.getFieldPathBaseToken() != null) 1306 ? column.getFieldPathBaseToken().toString() : null; 1307 1308 if (table.isCreateTable() && !fromCreateTable) { 1309 String columnName = attributeBase != null ? attributeBase : DlineageUtil.rawColumnName(column); 1310 for (int i = 0; i < table.getColumns().size(); i++) { 1311 TableColumn columnModel = table.getColumns().get(i); 1312 // an attribute read names its base column, so the reference's own segment is not the key to match 1313 boolean matches = attributeBase != null ? DlineageUtil.columnMatches(columnModel, attributeBase) 1314 : matchesColumn(columnModel, columnModel.getName(), column, columnName); 1315 if (matches) { 1316 return columnModel; 1317 } 1318 } 1319 1320 for (int i = 0; i < table.getColumns().size(); i++) { 1321 TableColumn columnModel = table.getColumns().get(i); 1322 if (columnModel.isVariant()) { 1323 columnModel.bindStarLinkColumn(column); 1324 return columnModel; 1325 } 1326 if (columnModel.hasStarLinkColumn()) { 1327 columnModel.bindStarLinkColumn(column); 1328 return columnModel; 1329 } 1330 } 1331 1332 if(table.isDetermined()) { 1333 return null; 1334 } 1335 } else { 1336 String columnName = fieldLeaf != null ? fieldLeaf : attributeBase != null ? attributeBase : DlineageUtil.rawColumnName(column); 1337 // For BigQuery/Redshift struct fields, use full struct path for lookup 1338 // to prevent collision between base column and struct field (e.g., "customer" vs "customer.name") 1339 if (ModelBindingManager.getGlobalVendor() == EDbVendor.dbvbigquery 1340 || ModelBindingManager.getGlobalVendor() == EDbVendor.dbvredshift) { 1341 String structName = null; 1342 gudusoft.gsqlparser.resolver2.model.ColumnSource cs = column.getColumnSource(); 1343 if (cs != null && cs.isStructFieldAccess() && cs.hasFieldPath()) { 1344 structName = cs.getFieldPath().toFullReference(cs.getExposedName()); 1345 } 1346 if (structName == null) { 1347 gudusoft.gsqlparser.resolver2.model.StructFieldHint hint = column.getStructFieldHint(); 1348 if (hint != null && hint.getFieldPath() != null 1349 && !isQualifiedColumnReference(column)) { 1350 structName = hint.toFullReference(); 1351 } 1352 } 1353 if (structName == null && column.getPropertyToken() != null) { 1354 structName = column.getPartToken().getAstext() + "." + column.getPropertyToken().getAstext(); 1355 } 1356 if (structName != null) { 1357 columnName = structName; 1358 } 1359 } 1360 for (int i = 0; i < table.getColumns().size(); i++) { 1361 TableColumn columnModel = table.getColumns().get(i); 1362 boolean matches = attributeBase != null ? DlineageUtil.columnMatches(columnModel, attributeBase) 1363 : matchesColumn(columnModel, columnModel.getName(), column, columnName); 1364 if (matches) { 1365 return columnModel; 1366 } 1367 } 1368 } 1369 1370 if (DlineageUtil.isStarName(column) && column.getTableToken() == null) { 1371 if (!fromCreateTable && table.isCreateTable()) { 1372 return null; 1373 } 1374 for (int i = 0; i < table.getColumns().size(); i++) { 1375 TableColumn columnModel = table.getColumns().get(i); 1376 // The model names its wildcard column exactly "*"; a delimited "*" is a real column. 1377 if (columnModel.isStarColumn()) { 1378 return columnModel; 1379 } 1380 } 1381 } 1382 1383 if (table.isCreateTable() && !fromCreateTable && DlineageUtil.isWildcard(column)) { 1384 return null; 1385 } 1386 1387 if (table.isDetermined() && DlineageUtil.isWildcard(column)) { 1388 return null; 1389 } 1390 1391 TableColumn columnModel = new TableColumn(table, column); 1392 if (fieldLeaf != null) { 1393 columnModel.name = DlineageUtil.decodeColumnIdentifier(fieldLeaf); 1394 } else if (attributeBase != null) { 1395 columnModel.name = DlineageUtil.decodeColumnIdentifier(attributeBase); 1396 } 1397 modelManager.bindModel(new Pair<Table, TObjectName>(table, column), columnModel); 1398 1399 if (DlineageUtil.isWildcard(column)) { 1400 TSQLEnv sqlenv = ModelBindingManager.getGlobalSQLEnv(); 1401 TTable origin = table.getTableObject(); 1402 TSQLTable sqlTable = origin != null && origin.isResolved() && ModelBindingManager.isScopedTemporary(origin) 1403 ? origin.getResolvedTable() 1404 : sqlenv == null ? null : sqlenv.searchTable(getQualifiedTableName(table)); 1405 if (sqlTable != null && !sqlTable.getColumnList().isEmpty()) { 1406 table.setHasSQLEnv(true); 1407 if (sqlTable.getColumnList() != null) { 1408 boolean isDetermine = true; 1409 for (TSQLColumn sqlColumn : sqlTable.getColumnList()) { 1410 if(sqlColumn.getName() == null){ 1411 System.err.println("sqlevn table " + getQualifiedTableName(table) + " sqlColumn getName() returns null."); 1412 continue; 1413 } 1414 if(sqlColumn.isUnresolvedStar()) { 1415 isDetermine = false; 1416 continue; 1417 } 1418 TObjectName objectName = DeclaredColumnName.ofCatalog(sqlColumn.getName(), 1419 TSQLEnv.toLexical(ModelBindingManager.getGlobalVendor(), 1420 gudusoft.gsqlparser.sqlenv.ESQLDataObjectType.dotColumn, sqlColumn.getName(), 1421 gudusoft.gsqlparser.sqlenv.IdentifierInputForm.CATALOG_STORED)); 1422 columnModel.bindStarLinkColumn(objectName); 1423 } 1424 if (isDetermine) { 1425 columnModel.setShowStar(false); 1426 columnModel.setExpandStar(true); 1427 } 1428 } 1429 } 1430 } 1431 1432 return columnModel; 1433 } 1434 1435 public static String getQualifiedTableName(Table table) { 1436 List<String> segments = SQLUtil.parseNames(table.name); 1437 boolean supportCatalog = TSQLEnv.supportCatalog(ModelBindingManager.getGlobalVendor()); 1438 boolean supportSchema = TSQLEnv.supportSchema(ModelBindingManager.getGlobalVendor()); 1439 if (segments.size() < 2) { 1440 if (supportCatalog && supportSchema) { 1441 return getDefaultDatabase() + "." + getDefaultSchema() + "." + table.name; 1442 } else if (supportCatalog) { 1443 return TSQLEnv.DEFAULT_DB_NAME + "." + getDefaultDatabase() + "." + table.name; 1444 } else { 1445 return TSQLEnv.DEFAULT_DB_NAME + "." + getDefaultSchema() + "." + table.name; 1446 } 1447 } else if (segments.size() < 3) { 1448 if (supportCatalog && supportSchema) { 1449 return getDefaultDatabase() + "." + table.name; 1450 } else { 1451 return TSQLEnv.DEFAULT_DB_NAME + "." + table.name; 1452 } 1453 } else { 1454 return table.name; 1455 } 1456 } 1457 1458 protected static String getDefaultSchema() { 1459 return ModelBindingManager.getEffectiveDefaultSchema(); 1460 } 1461 1462 protected static String getDefaultDatabase() { 1463 return ModelBindingManager.getEffectiveDefaultDatabase(); 1464 } 1465 1466 public TableColumn createStageLocation(Table table, TObjectName location) { 1467 if (modelManager.getModel(new Pair<Table, TObjectName>(table, location)) instanceof TableColumn) { 1468 return (TableColumn) modelManager.getModel(new Pair<Table, TObjectName>(table, location)); 1469 } 1470 1471 StageLocation columnModel = new StageLocation(table, location); 1472 modelManager.bindModel(new Pair<Table, TObjectName>(table, location), columnModel); 1473 return columnModel; 1474 } 1475 1476 public TableColumn createFileUri(Table table, TObjectName fileUri) { 1477 if (modelManager.getModel(new Pair<Table, TObjectName>(table, fileUri)) instanceof TableColumn) { 1478 return (TableColumn) modelManager.getModel(new Pair<Table, TObjectName>(table, fileUri)); 1479 } 1480 1481 FileUri columnModel = new FileUri(table, fileUri); 1482 modelManager.bindModel(new Pair<Table, TObjectName>(table, fileUri), columnModel); 1483 return columnModel; 1484 } 1485 1486 /** 1487 * The field path node at {@code segments} below {@code fileColumn}, a column of the files of a LOCATION 1488 * ({@link #createFileUri}), made as needed. Like the files' column, it has no place in the SQL text. 1489 */ 1490 public TableColumn createFileUriFieldPath(TableColumn fileColumn, 1491 List<gudusoft.gsqlparser.common.structured.StructuredPathSegment> segments) { 1492 TableColumn node = createFieldPath(fileColumn, segments, fileColumn.getColumnObject()); 1493 for (TableColumn field = node; field != null && field != fileColumn; field = field.getParentColumn()) { 1494 field.startPosition = fileColumn.getStartPosition(); 1495 field.endPosition = fileColumn.getEndPosition(); 1496 } 1497 return node; 1498 } 1499 1500 public TableColumn createVariableProperty(Table variableTable, TDeclareVariable variable) { 1501 if (modelManager.getModel(new Pair<Table, TDeclareVariable>(variableTable, variable)) instanceof TableColumn) { 1502 return (TableColumn) modelManager.getModel(new Pair<Table, TDeclareVariable>(variableTable, variable)); 1503 } 1504 1505 TObjectName variableName = new TObjectName(); 1506 variableName.setString("property"); 1507 VariableProperty columnModel = new VariableProperty(variableTable, variableName, variable); 1508 modelManager.bindModel(new Pair<Table, TDeclareVariable>(variableTable, variable), columnModel); 1509 return columnModel; 1510 } 1511 1512 public TableColumn createJsonTableColumn(Table table, TObjectName column) { 1513 if (modelManager.getModel(new Pair<Table, TObjectName>(table, column)) instanceof TableColumn) { 1514 return (TableColumn) modelManager.getModel(new Pair<Table, TObjectName>(table, column)); 1515 } 1516 1517 JsonTableColumn columnModel = new JsonTableColumn(table, column); 1518 modelManager.bindModel(new Pair<Table, TObjectName>(table, column), columnModel); 1519 return columnModel; 1520 } 1521 1522 public DataFlowRelationship createDataFlowRelation() { 1523 DataFlowRelationship relation = new DataFlowRelationship(); 1524 modelManager.addRelation(relation); 1525 return relation; 1526 } 1527 1528 public CallRelationship createCallRelation() { 1529 CallRelationship relation = new CallRelationship(); 1530 modelManager.addRelation(relation); 1531 return relation; 1532 } 1533 1534 public CrudRelationship createCrudRelation() { 1535 CrudRelationship relation = new CrudRelationship(); 1536 modelManager.addRelation(relation); 1537 return relation; 1538 } 1539 1540 public ERRelationship createERRelation() { 1541 ERRelationship relation = new ERRelationship(); 1542 modelManager.addRelation(relation); 1543 return relation; 1544 } 1545 1546 public TableColumn createTableColumn(Table table, TResultColumn column) { 1547 if (column.getAliasClause() != null && column.getAliasClause().getAliasName() != null) { 1548 TableColumn columnModel = new TableColumn(table, column.getAliasClause().getAliasName()); 1549 modelManager.bindModel(column, columnModel); 1550 return columnModel; 1551 } else if (column.getFieldAttr() != null) { 1552 TableColumn columnModel = new TableColumn(table, column.getFieldAttr()); 1553 modelManager.bindModel(column, columnModel); 1554 return columnModel; 1555 } 1556 return null; 1557 } 1558 1559 public TableColumn createTableColumn(Table table, TConstant constant) { 1560 if (modelManager.getModel(new Pair<Table, TConstant>(table, constant)) instanceof TableColumn) { 1561 return (TableColumn) modelManager.getModel(new Pair<Table, TConstant>(table, constant)); 1562 } 1563 1564 TableColumn columnModel = new TableColumn(table, constant); 1565 modelManager.bindModel(new Pair<Table, TConstant>(table, constant), columnModel); 1566 return columnModel; 1567 } 1568 1569 public TableColumn createTableColumn(Table table, TFunctionCall constantFunction) { 1570 if (modelManager.getModel(new Pair<Table, TFunctionCall>(table, constantFunction)) instanceof TableColumn) { 1571 return (TableColumn) modelManager.getModel(new Pair<Table, TFunctionCall>(table, constantFunction)); 1572 } 1573 1574 TableColumn columnModel = new TableColumn(table, constantFunction); 1575 modelManager.bindModel(new Pair<Table, TFunctionCall>(table, constantFunction), columnModel); 1576 return columnModel; 1577 } 1578 1579 public TableColumn createTableColumn(Table table, String columnName) { 1580 for (TableColumn col : table.getColumns()) { 1581 if (col.getName().equals(columnName)) { 1582 return col; 1583 } 1584 } 1585 TableColumn columnModel = new TableColumn(table, columnName); 1586 return columnModel; 1587 } 1588 1589 public RecordSetRelationship createRecordSetRelation() { 1590 RecordSetRelationship relation = new RecordSetRelationship(); 1591 modelManager.addRelation(relation); 1592 return relation; 1593 } 1594 1595 public ImpactRelationship createImpactRelation() { 1596 ImpactRelationship relation = new ImpactRelationship(); 1597 modelManager.addRelation(relation); 1598 return relation; 1599 } 1600 1601 public IndirectImpactRelationship createIndirectImpactRelation() { 1602 IndirectImpactRelationship relation = new IndirectImpactRelationship(); 1603 modelManager.addRelation(relation); 1604 return relation; 1605 } 1606 1607 public JoinRelationship createJoinRelation() { 1608 JoinRelationship relation = new JoinRelationship(); 1609 modelManager.addRelation(relation); 1610 return relation; 1611 } 1612 1613 public Table createView(TCustomSqlStatement viewStmt, TObjectName viewName) { 1614 return createView(viewStmt, viewName, false); 1615 } 1616 1617 public Table createView(TCustomSqlStatement viewStmt, TObjectName viewName, boolean fromCreateView) { 1618 if (modelManager.getViewModel(viewStmt) instanceof Table) { 1619 Table table = (Table) modelManager.getViewModel(viewStmt); 1620 table.setView(true); 1621 table.setPosition(viewStmt); 1622 return table; 1623 } 1624 1625 if (modelManager.getTableByName(DlineageUtil.getTableIdentityKey(viewName.toString())) != null) { 1626 Table table = modelManager.getTableByName(DlineageUtil.getTableIdentityKey(viewName.toString())); 1627 table.setView(true); 1628 table.setPosition(viewStmt); 1629 return table; 1630 } 1631 1632 Table viewModel = new Table(viewStmt, viewName); 1633 viewModel.setCreateTable(fromCreateView); 1634 viewModel.setView(true); 1635 modelManager.bindViewModel(viewStmt, viewModel); 1636 modelManager.bindTableByName(DlineageUtil.getTableIdentityKey(viewName.toString()), viewModel); 1637 return viewModel; 1638 } 1639 1640 public Process createProcess(TCustomSqlStatement processStmt) { 1641 if (modelManager.getProcessModel(processStmt) instanceof Process) { 1642 Process process = (Process) modelManager.getProcessModel(processStmt); 1643 return process; 1644 } 1645 1646 Process processModel = new Process(processStmt); 1647 modelManager.bindProcessModel(processStmt, processModel); 1648 return processModel; 1649 } 1650 1651 public Process createProcess(TFunctionCall functionCall) { 1652 if (modelManager.getProcessModel(functionCall) instanceof Process) { 1653 Process process = (Process) modelManager.getProcessModel(functionCall); 1654 return process; 1655 } 1656 1657 Process processModel = new Process(functionCall); 1658 modelManager.bindProcessModel(functionCall, processModel); 1659 return processModel; 1660 } 1661 1662 public TableColumn createViewColumn(Table viewModel, TObjectName column, int index, boolean fromCreateView) { 1663 Pair<Table, TObjectName> bindingModel = new Pair<Table, TObjectName>(viewModel, column); 1664 if (modelManager.getViewModel(bindingModel) instanceof TableColumn) { 1665 return (TableColumn) modelManager.getViewModel(bindingModel); 1666 } 1667 1668 if (viewModel.isCreateTable() && !fromCreateView) { 1669 if (index < viewModel.getColumns().size()) { 1670 return (TableColumn) viewModel.getColumns().get(index); 1671 } else { 1672 return null; 1673 } 1674 } 1675 1676 TableColumn columnModel = new TableColumn(viewModel, column, index); 1677 modelManager.bindViewModel(bindingModel, columnModel); 1678 return columnModel; 1679 } 1680 1681 private TableColumn createCatalogTableColumn(Table tableModel, String storedName) { 1682 EDbVendor vendor = ModelBindingManager.getGlobalVendor(); 1683 TableColumn column = createInsertTableColumn(tableModel, DlineageUtil.catalogColumnLexical(vendor, storedName)); 1684 // Keep the catalog display contract; the AST/key retain lexical identity separately. 1685 column.name = storedName; 1686 column.setIdentifierKey(DlineageUtil.catalogColumnKey(vendor, storedName)); 1687 return column; 1688 } 1689 1690 private boolean matchesColumn(Column model, String modelName, TObjectName reference, String referenceName) { 1691 if (model.isStarColumn() != DlineageUtil.isStarName(reference)) { 1692 // A star names no column, and a column named * is not a star: they share only the display. 1693 return false; 1694 } 1695 if (model.getIdentifierKey() != null && DlineageUtil.columnKey(reference) != null) { 1696 return model.matchesIdentifier(reference); 1697 } 1698 // Non-identifier model names (e.g. STRUCT paths) are compared as column paths. 1699 return DlineageUtil.sameColumnPath(modelName, referenceName); 1700 } 1701 1702 public TableColumn createInsertTableColumn(Table tableModel, String columnName) { 1703 TObjectName columnNameObject = new TObjectName(); 1704 columnNameObject.setString(columnName); 1705 Pair<Table, String> bindingModel = new Pair<Table, String>(tableModel, columnName); 1706 1707 if (tableModel.isCreateTable()) { 1708 for (int i = 0; i < tableModel.getColumns().size(); i++) { 1709 TableColumn columnModel = tableModel.getColumns().get(i); 1710 if (matchesColumn(columnModel, columnModel.getName(), columnNameObject, columnName)) { 1711 modelManager.bindInsertModel(bindingModel, columnModel); 1712 return columnModel; 1713 } 1714 } 1715 } 1716 1717 if (modelManager.getInsertModel(bindingModel) instanceof TableColumn) { 1718 return (TableColumn) modelManager.getInsertModel(bindingModel); 1719 } 1720 TableColumn columnModel = new TableColumn(tableModel, columnNameObject); 1721 // This legacy overload also receives expression/display names. Only a caller 1722 // with explicit AST/catalog provenance may attach an identifier key. 1723 columnModel.setIdentifierKey(null); 1724 modelManager.bindInsertModel(bindingModel, columnModel); 1725 return columnModel; 1726 } 1727 1728 /** Preserve the producer's identity when materializing a projected column. */ 1729 public TableColumn createInsertTableColumn(Table tableModel, ResultColumn source) { 1730 if (source.getIdentifierKey() != null) { 1731 for (TableColumn existing : tableModel.getColumns()) { 1732 if (source.getIdentifierKey().equals(existing.getIdentifierKey())) return existing; 1733 } 1734 TObjectName name = new TObjectName(); 1735 name.setString(source.getName()); 1736 TableColumn column = new TableColumn(tableModel, name); 1737 column.inheritIdentifier(source); 1738 return column; 1739 } 1740 TableColumn column = createInsertTableColumn(tableModel, source.getName()); 1741 column.inheritIdentifier(source); 1742 return column; 1743 } 1744 1745 /** 1746 * The name of a column that receives a field read in the Trino family: the name the select item publishes, its 1747 * last field ({@code INSERT INTO t SELECT payload.address.city} writes {@code t.city}), not the column 1748 * {@code payload} the read comes from, which names the column only in the table it reads (Mantis 4822). Null 1749 * for any other name or vendor. 1750 */ 1751 private static String targetFieldLeaf(TObjectName column) { 1752 return gudusoft.gsqlparser.nodes.structured.NestedAccessProfile.forVendor(ModelBindingManager.getGlobalVendor()).readsFieldChains() && DlineageUtil.isFieldPath(column) 1753 ? DlineageUtil.fieldLeafText(column) : null; 1754 } 1755 1756 public TableColumn createInsertTableColumn(Table tableModel, TObjectName column) { 1757 Pair<Table, TObjectName> bindingModel = new Pair<Table, TObjectName>(tableModel, column); 1758 String fieldLeaf = targetFieldLeaf(column); 1759 1760 if (tableModel.isCreateTable()) { 1761 String columnName = fieldLeaf != null ? fieldLeaf : DlineageUtil.rawColumnName(column); 1762 for (int i = 0; i < tableModel.getColumns().size(); i++) { 1763 TableColumn columnModel = tableModel.getColumns().get(i); 1764 if (matchesColumn(columnModel, columnModel.getName(), column, columnName)) { 1765 modelManager.bindInsertModel(bindingModel, columnModel); 1766 return columnModel; 1767 } 1768 } 1769 } 1770 1771 if (modelManager.getInsertModel(bindingModel) instanceof TableColumn) { 1772 return (TableColumn) modelManager.getInsertModel(bindingModel); 1773 } 1774 TableColumn columnModel = new TableColumn(tableModel, column); 1775 if (fieldLeaf != null) { 1776 columnModel.name = DlineageUtil.decodeColumnIdentifier(fieldLeaf); 1777 } 1778 modelManager.bindInsertModel(bindingModel, columnModel); 1779 return columnModel; 1780 } 1781 1782 public TableColumn createInsertTableColumn(Table tableModel, TExpression column, int columnIndex) { 1783 Pair<Table, TExpression> bindingModel = new Pair<Table, TExpression>(tableModel, column); 1784 1785 if (tableModel.isCreateTable()) { 1786 if (columnIndex < tableModel.getColumns().size()) { 1787 TableColumn columnModel = tableModel.getColumns().get(columnIndex); 1788 modelManager.bindInsertModel(bindingModel, columnModel); 1789 return columnModel; 1790 } else { 1791 return null; 1792 } 1793 } 1794 1795 if (modelManager.getInsertModel(bindingModel) instanceof TableColumn) { 1796 return (TableColumn) modelManager.getInsertModel(bindingModel); 1797 } 1798 TableColumn columnModel = new TableColumn(tableModel, column, columnIndex); 1799 modelManager.bindInsertModel(bindingModel, columnModel); 1800 return columnModel; 1801 } 1802 1803 public TableColumn createInsertTableColumn(Table tableModel, TConstant column, int columnIndex) { 1804 Pair<Table, TConstant> bindingModel = new Pair<Table, TConstant>(tableModel, column); 1805 1806 if (tableModel.isCreateTable()) { 1807 if (columnIndex < tableModel.getColumns().size()) { 1808 TableColumn columnModel = tableModel.getColumns().get(columnIndex); 1809 modelManager.bindInsertModel(bindingModel, columnModel); 1810 return columnModel; 1811 } else { 1812 return null; 1813 } 1814 } 1815 1816 if (modelManager.getInsertModel(bindingModel) instanceof TableColumn) { 1817 return (TableColumn) modelManager.getInsertModel(bindingModel); 1818 } 1819 TableColumn columnModel = new TableColumn(tableModel, column, columnIndex); 1820 modelManager.bindInsertModel(bindingModel, columnModel); 1821 return columnModel; 1822 } 1823 1824 public SelectSetResultSet createSelectSetResultSet(TSelectSqlStatement stmt) { 1825 if (modelManager.getModel(stmt) instanceof SelectSetResultSet) { 1826 return (SelectSetResultSet) modelManager.getModel(stmt); 1827 } 1828 SelectSetResultSet resultSet = new SelectSetResultSet(stmt, stmt.getParentStmt() == null); 1829 modelManager.bindModel(stmt, resultSet); 1830 return resultSet; 1831 } 1832 1833 public ResultColumn createStarResultColumn(SelectResultSet resultSet, 1834 Pair<TResultColumn, TObjectName> starColumnPair) { 1835 if (modelManager.getModel(starColumnPair) instanceof ResultColumn) { 1836 return (ResultColumn) modelManager.getModel(starColumnPair); 1837 } 1838 ResultColumn column = new ResultColumn(resultSet, starColumnPair); 1839 modelManager.bindModel(starColumnPair, column); 1840 return column; 1841 } 1842 1843 public Variable createCursor(TCursorDeclStmt stmt) { 1844 return cursorFor(stmt, stmt.getCursorName(), false, () -> new Cursor(stmt)); 1845 } 1846 1847 public Variable createCursor(TMssqlDeclare stmt) { 1848 return cursorFor(stmt, stmt.getCursorName(), false, () -> new Cursor(stmt)); 1849 } 1850 1851 public Variable createCursor(TLoopStmt stmt) { 1852 return cursorFor(stmt, stmt.getRecordName(), false, () -> new Cursor(stmt)); 1853 } 1854 1855 public Variable createCursor(TForStmt stmt) { 1856 return cursorFor(stmt, stmt.getLoopName(), false, () -> new Cursor(stmt)); 1857 } 1858 1859 public Variable createCursor(TOpenforStmt stmt) { 1860 return cursorFor(stmt, stmt.getCursorVariableName(), true, () -> new Cursor(stmt)); 1861 } 1862 1863 /** 1864 * A cursor by its name as written. A declaration (a loop's record included) reuses only a cursor of its own scope, 1865 * which it may shadow an outer one with; OPEN FOR names a cursor variable visible from the statement. Otherwise a 1866 * new cursor, bound in the current scope. 1867 */ 1868 private Variable cursorFor(TCustomSqlStatement stmt, TObjectName cursorName, boolean reference, 1869 java.util.function.Supplier<Cursor> fresh) { 1870 String procedureName = DlineageUtil.getProcedureParentName(stmt); 1871 String written = withoutColon(cursorName.toString()); 1872 // B3 identity-first pools: cursors join the per-overload pool too 1873 // (codex-B3-r1 finding 7). Guarded so LEGACY never even allocates 1874 // the candidate Cursor — an eager throwaway would advance model IDs 1875 // (codex-B3-r2 finding 13); inactive in LEGACY by construction. 1876 if (modelManager.hasIdentityScope()) { 1877 if (reference && lookupInIdentityScope(written) == null) { 1878 // Not this overload's: OPEN FOR names a cursor variable of an enclosing routine or the package. 1879 Table visible = modelManager.lookupVisibleLocalSymbol(localKey(written, LocalSymbolKey.Kind.CURSOR), 1880 currentStatement()); 1881 if (visible instanceof Variable) { 1882 return (Variable) visible; 1883 } 1884 } 1885 Variable pooledCursor = identityScopedCursor(written, procedureName, fresh.get()); 1886 if (pooledCursor != null) { 1887 return pooledCursor; 1888 } 1889 } 1890 LocalSymbolKey key = localKey(written, LocalSymbolKey.Kind.CURSOR); 1891 Table existing = reference ? modelManager.lookupVisibleLocalSymbol(key, currentStatement()) 1892 : modelManager.getLocalSymbol(key); 1893 if (existing instanceof Variable) { 1894 return (Variable) existing; 1895 } 1896 Cursor cursor = fresh.get(); 1897 if (!SQLUtil.isEmpty(procedureName)) { 1898 cursor.setParent(procedureName); 1899 } 1900 modelManager.publishLocalSymbol(key, cursor); 1901 return cursor; 1902 } 1903 1904 /** 1905 * B1 (routine-summary-scc-design §3): additionally index a declared 1906 * formal's Variable under its routine's overload pool (its frozen scope 1907 * and declaration identity). Pure lookup-map addition — no model 1908 * binding, no new model objects — so LEGACY output is untouched by 1909 * construction; the identity-first lookups (slice B3) and 1910 * findFormalVariable are the consumers. 1911 */ 1912 public void bindVariableUnderIdentityScope(Procedure procedure, String variableName, 1913 Variable variable) { 1914 if (procedure == null || variable == null || SQLUtil.isEmpty(variableName)) { 1915 return; 1916 } 1917 LocalSymbolKey key = LocalSymbolKey.of(poolScopeOf(procedure), withoutColon(variableName), 1918 LocalSymbolKey.Kind.VARIABLE); 1919 if (key != null && key.getScope() != null && !(modelManager.getLocalSymbol(key) instanceof Variable)) { 1920 modelManager.publishLocalSymbol(key, variable); 1921 } 1922 } 1923 1924 /** 1925 * B3 (routine-summary-scc-design §3): object-level per-overload variable 1926 * pools. While a routine body is analyzed under the identity-first mode 1927 * ({@code Option.identityFirstVariablePools}), its overload pool (the 1928 * routine's frozen scope and declaration identity) sits on the manager's 1929 * pool stack and the creators key variables under it before the ordinary 1930 * scope — two same-named overloads get DISTINCT formal/local pools. LIFO 1931 * because routines nest (package members, nested blocks). Inactive in 1932 * LEGACY mode by construction, so every shipped path is untouched. 1933 */ 1934 /** Sticky per-analysis: set on first push; gates the callee-formal 1935 * identity-first lookup that runs OUTSIDE any body scope. The stack 1936 * itself lives on the {@link ModelBindingManager} so DIRECT reads 1937 * resolve identity-first too (codex-B3-r2 finding 7). */ 1938 private boolean identityFirstPools = false; 1939 1940 /** @deprecated a pool is keyed by its routine: use {@link #pushIdentityScope(String, Procedure)}. */ 1941 @Deprecated 1942 public void pushIdentityScope(String identityScopeKey) { 1943 pushIdentityScope(identityScopeKey, null); 1944 } 1945 1946 /** 1947 * Enters a routine body's overload pool: the routine model it belongs to (its legacy identity text is kept as 1948 * metadata only). Every call is matched by {@link #popIdentityScope()}. 1949 */ 1950 public void pushIdentityScope(String identityScopeKey, Procedure procedure) { 1951 modelManager.pushIdentityScope(identityScopeKey, poolScopeOf(procedure)); 1952 identityFirstPools = true; 1953 } 1954 1955 /** A routine's overload-pool scope: its frozen scope and declaration identity; null without an identity. */ 1956 private IdentityScope poolScopeOf(Procedure procedure) { 1957 if (procedure == null || procedure.getRoutineIdentity() == null) { 1958 return null; 1959 } 1960 Object scope = LocalScopes.scopeOf(modelManager, procedure); 1961 return scope == null ? null : IdentityScope.of(scope, procedure.getRoutineIdentity()); 1962 } 1963 1964 private static String withoutColon(String name) { 1965 return name != null && name.startsWith(":") ? name.substring(1) : name; 1966 } 1967 1968 public void popIdentityScope() { 1969 modelManager.popIdentityScope(); 1970 } 1971 1972 /** Identity-pool LOOKUP for the active pool ONLY. 1973 * Deliberately NO ordinary-scope fallback: a fallback would hand a 1974 * same-named OVERLOAD's variable back and silently re-merge the pools 1975 * this mode exists to split. Null when no pool is active or on a miss. */ 1976 private Variable lookupInIdentityScope(String written) { 1977 LocalSymbolKey pooled = LocalSymbolKey.of(modelManager.peekPoolScope(), written, LocalSymbolKey.Kind.VARIABLE); 1978 Table hit = pooled == null || pooled.getScope() == null ? null : modelManager.getLocalSymbol(pooled); 1979 return hit instanceof Variable ? (Variable) hit : null; 1980 } 1981 1982 /** Identity-pool BIND: always in the active pool, and ALSO in the 1983 * ordinary scope while that name is unoccupied there — ordinary reads 1984 * keep working for the non-colliding common case, while a same-named 1985 * overload finds the ordinary name taken and keeps its own pool 1986 * (codex-B3-r1 finding 7). */ 1987 private void bindInIdentityScope(String written, Variable variable) { 1988 modelManager.publishLocalSymbol( 1989 LocalSymbolKey.of(modelManager.peekPoolScope(), written, LocalSymbolKey.Kind.VARIABLE), variable); 1990 LocalSymbolKey ordinary = localKey(written, LocalSymbolKey.Kind.VARIABLE); 1991 if (!(modelManager.getLocalSymbol(ordinary) instanceof Variable)) { 1992 modelManager.publishLocalSymbol(ordinary, variable); 1993 } 1994 } 1995 1996 /** 1997 * Identity-pool variable resolution for the ACTIVE pool: lookup, then on 1998 * a full miss create a fresh pool member. Returns null when no pool is 1999 * active (caller continues the ordinary path) or when {@code create} is 2000 * false and nothing was found. 2001 */ 2002 private Variable identityScopedVariable(String written, TObjectName displayName, String displayText, 2003 String procedureName, boolean create, boolean reference) { 2004 if (!modelManager.hasIdentityScope()) { 2005 return null; 2006 } 2007 Variable found = lookupInIdentityScope(written); 2008 if (found == null && reference) { 2009 // Not this overload's: a reference sees the enclosing routines' and the package's declarations. 2010 Table visible = modelManager.lookupVisibleLocalSymbol(localKey(written, LocalSymbolKey.Kind.VARIABLE), 2011 currentStatement()); 2012 if (visible instanceof Variable) { 2013 return (Variable) visible; 2014 } 2015 } 2016 if (found != null || !create) { 2017 return found; 2018 } 2019 Variable variable = displayName != null ? new Variable(displayName) : new Variable(displayText); 2020 if (!SQLUtil.isEmpty(procedureName)) { 2021 variable.setParent(procedureName); 2022 } 2023 bindInIdentityScope(written, variable); 2024 return variable; 2025 } 2026 2027 /** Identity-pool branch shared by the five cursor creators: resolve in 2028 * the active pool or bind the freshly built cursor there. Null when no 2029 * pool is active (creator continues its ordinary path). */ 2030 private Variable identityScopedCursor(String written, String procedureName, Cursor freshCursor) { 2031 if (!modelManager.hasIdentityScope()) { 2032 return null; 2033 } 2034 Variable found = lookupInIdentityScope(written); 2035 if (found != null) { 2036 return found; 2037 } 2038 if (!SQLUtil.isEmpty(procedureName)) { 2039 freshCursor.setParent(procedureName); 2040 } 2041 bindInIdentityScope(written, freshCursor); 2042 return freshCursor; 2043 } 2044 2045 private TCustomSqlStatement currentStatement() { 2046 java.util.Stack<TCustomSqlStatement> stack = modelManager.getGlobalStmtStack(); 2047 return stack == null || stack.isEmpty() ? null : stack.peek(); 2048 } 2049 2050 /** A local symbol's key: its name as written (a leading ':' removed) in the current statement's scope. */ 2051 private LocalSymbolKey localKey(String name, LocalSymbolKey.Kind kind) { 2052 return name == null ? null 2053 : LocalSymbolKey.of(LocalScopes.scopeOf(modelManager, currentStatement()), withoutColon(name), kind); 2054 } 2055 2056 /** 2057 * The variable a declaration declares: the one already declared in the declaration's own scope, else a new one 2058 * there. A declaration never reuses an enclosing scope's variable, which it shadows. 2059 */ 2060 public Variable createVariable(TObjectName variableName) { 2061 return variableFor(variableName, false); 2062 } 2063 2064 /** 2065 * The variable a reference names: the declaration visible from the reference (its routine, the enclosing 2066 * routines, then its package), else a new variable in the reference's scope. 2067 */ 2068 public Variable createReferencedVariable(TObjectName variableName) { 2069 return variableFor(variableName, true); 2070 } 2071 2072 private Variable variableFor(TObjectName variableName, boolean reference) { 2073 String procedureName = DlineageUtil.getProcedureParentName(currentStatement()); 2074 // The name the AST writes: a record field reference rec.field names its record rec. 2075 String written = LocalSymbolNames.baseName(variableName); 2076 2077 // B3 identity-first pools: active only under the mode flag, so LEGACY 2078 // behavior below is untouched. 2079 Variable pooled = identityScopedVariable(written, variableName, null, procedureName, true, reference); 2080 if (pooled != null) { 2081 return pooled; 2082 } 2083 2084 LocalSymbolKey key = localKey(written, LocalSymbolKey.Kind.VARIABLE); 2085 Table existing = reference ? modelManager.lookupVisibleLocalSymbol(key, currentStatement()) 2086 : modelManager.getLocalSymbol(key); 2087 if (existing instanceof Variable) { 2088 return (Variable) existing; 2089 } 2090 Variable variable = new Variable(variableName); 2091 if (!SQLUtil.isEmpty(procedureName)) { 2092 variable.setParent(procedureName); 2093 } 2094 modelManager.publishLocalSymbol(key, variable); 2095 return variable; 2096 } 2097 2098 public Variable createVariable(String variableName) { 2099 return createVariable(variableName, true); 2100 } 2101 2102 /** 2103 * The variable a declaration declares (see {@link #createVariable(TObjectName)}); when {@code create} is false, 2104 * only one already declared in the current scope. 2105 */ 2106 public Variable createVariable(String variableName, boolean create) { 2107 return variableFor(variableName, create, false); 2108 } 2109 2110 /** 2111 * The variable a reference names (see {@link #createReferencedVariable(TObjectName)}); when {@code create} is 2112 * false, only a declaration visible from the reference. 2113 */ 2114 public Variable createReferencedVariable(String variableName, boolean create) { 2115 return variableFor(variableName, create, true); 2116 } 2117 2118 private Variable variableFor(String variableName, boolean create, boolean reference) { 2119 String procedureName = DlineageUtil.getProcedureParentName(currentStatement()); 2120 String written = withoutColon(variableName); 2121 2122 // B3 identity-first pools: active only under the mode flag, so LEGACY 2123 // behavior below is untouched. 2124 Variable pooled = identityScopedVariable(written, null, written, procedureName, create, reference); 2125 if (pooled != null) { 2126 return pooled; 2127 } 2128 if (modelManager.hasIdentityScope() && !create) { 2129 return null; 2130 } 2131 2132 LocalSymbolKey key = localKey(written, LocalSymbolKey.Kind.VARIABLE); 2133 Table existing = reference ? modelManager.lookupVisibleLocalSymbol(key, currentStatement()) 2134 : modelManager.getLocalSymbol(key); 2135 if (existing instanceof Variable) { 2136 return (Variable) existing; 2137 } 2138 if (!create) { 2139 return null; 2140 } 2141 Variable variable = new Variable(variableName); 2142 if (!SQLUtil.isEmpty(procedureName)) { 2143 variable.setParent(procedureName); 2144 } 2145 modelManager.publishLocalSymbol(key, variable); 2146 return variable; 2147 } 2148 2149 /** 2150 * The variable {@code variableName} of {@code procedure} as a caller binds an argument to it: the routine's formal 2151 * (or local), in the routine's frozen scope, whatever the session's defaults are when the call is analyzed; created 2152 * there when {@code create}. 2153 */ 2154 public Variable createVariable(Procedure procedure, String variableName, boolean create) { 2155 String written = withoutColon(variableName); 2156 // B3 identity-first pools (stage 0, mode-gated): the callee's formals 2157 // were keyed under ITS pool, so caller-side argument binding must 2158 // consult that pool first — this is what discriminates same-named 2159 // overloads. A complex-typed formal (records/collections carry a single 2160 // wildcard column) is refused: binding at that granularity would emit 2161 // the *-target relationship noise testIBF4RL guards against, and 2162 // field-level flow through such formals is produced by the body-side 2163 // machinery. 2164 if (identityFirstPools) { 2165 IdentityScope pool = poolScopeOf(procedure); 2166 Table identityHit = pool == null ? null 2167 : modelManager.getLocalSymbol(LocalSymbolKey.of(pool, written, LocalSymbolKey.Kind.VARIABLE)); 2168 if (identityHit instanceof Variable) { 2169 Variable found = (Variable) identityHit; 2170 if (!found.getColumns().isEmpty() 2171 && found.getColumns().get(0).isStarColumn()) { 2172 return null; 2173 } 2174 return found; 2175 } 2176 } 2177 LocalSymbolKey key = LocalSymbolKey.of(LocalScopes.scopeOf(modelManager, procedure), written, 2178 LocalSymbolKey.Kind.VARIABLE); 2179 Table found = modelManager.getLocalSymbol(key); 2180 if (found instanceof Variable) { 2181 return (Variable) found; 2182 } 2183 if (!create) { 2184 return null; 2185 } 2186 String displayScope = procedure.getName(); 2187 if (procedure.getParentPackage() != null) { 2188 displayScope = procedure.getParentPackage().getName() + "." + displayScope; 2189 } 2190 Variable variable = new Variable(variableName); 2191 if (!SQLUtil.isEmpty(displayScope)) { 2192 variable.setParent(displayScope); 2193 } 2194 modelManager.publishLocalSymbol(key, variable); 2195 return variable; 2196 } 2197 2198 /** 2199 * The formal-argument variable of {@code procedure} named 2200 * {@code variableName}, WITHOUT the pool stage's complex-typed refusal 2201 * (#695). The refusal exists because binding a whole record onto its 2202 * single wildcard column was noise; a caller that first materializes the 2203 * formal's fields from its declared type binds exactly, so it needs the 2204 * variable the refusal hides. Lookup only — never creates. 2205 */ 2206 public Variable findFormalVariable(Procedure procedure, String variableName) { 2207 String written = withoutColon(variableName); 2208 IdentityScope pool = poolScopeOf(procedure); 2209 Table identityHit = pool == null ? null 2210 : modelManager.getLocalSymbol(LocalSymbolKey.of(pool, written, LocalSymbolKey.Kind.VARIABLE)); 2211 if (identityHit instanceof Variable) { 2212 return (Variable) identityHit; 2213 } 2214 Table found = modelManager.getLocalSymbol(LocalSymbolKey.of(LocalScopes.scopeOf(modelManager, procedure), written, 2215 LocalSymbolKey.Kind.VARIABLE)); 2216 return found instanceof Variable ? (Variable) found : null; 2217 } 2218 2219 public Database createDatabase(TObjectName databaseName) { 2220 Object key = DlineageUtil.analysisNameKey(databaseName.toString(), ESQLDataObjectType.dotCatalog); 2221 if (modelManager.getTableByName(key) instanceof Database) { 2222 return (Database) modelManager.getTableByName(key); 2223 } 2224 Database database = new Database(databaseName); 2225 modelManager.bindTableByName(key, database); 2226 modelManager.bindModel(key, database); 2227 return database; 2228 } 2229 2230 public Schema createSchema(TObjectName schemaName) { 2231 Object key = DlineageUtil.schemaNameKey(schemaName.toString()); 2232 if (modelManager.getTableByName(key) instanceof Schema) { 2233 return (Schema) modelManager.getTableByName(key); 2234 } 2235 Schema schema = new Schema(schemaName); 2236 modelManager.bindTableByName(key, schema); 2237 modelManager.bindModel(key, schema); 2238 return schema; 2239 } 2240 2241 public PivotedTable createPivotdTable(TPivotClause pivotedTable) { 2242 if (modelManager.getModel(pivotedTable) instanceof PivotedTable) { 2243 return (PivotedTable) modelManager.getModel(pivotedTable); 2244 } 2245 PivotedTable resultSet = new PivotedTable(pivotedTable); 2246 modelManager.bindModel(pivotedTable, resultSet); 2247 if (pivotedTable.getPivotTable() != null) { 2248 modelManager.bindModel(pivotedTable.getPivotTable(), resultSet); 2249 } 2250 return resultSet; 2251 } 2252 2253 public Procedure createProcedure(TStoredProcedureSqlStatement stmt) { 2254 Procedure procedure = createProcedureInternal(stmt); 2255 if (gudusoft.gsqlparser.dlineage.routine.RoutineShadow.enabled()) { 2256 gudusoft.gsqlparser.dlineage.routine.RoutineShadow.declared(modelManager, "createProcedure", stmt, procedure); 2257 } 2258 return procedure; 2259 } 2260 2261 private Procedure createProcedureInternal(TStoredProcedureSqlStatement stmt) { 2262 if (stmt == null || DlineageUtil.getProcedureOrFunctionName(stmt) == null) { 2263 return null; 2264 } 2265 // The routine's three aliases (argument types, count, input count) on its name as written; a package 2266 // member's name is the package's name followed by its own. 2267 EDbVendor vendor = ModelBindingManager.getGlobalVendor(); 2268 RoutineAliasKey name = RoutineAliasKey.written(DlineageUtil.getProcedureOrFunctionName(stmt), null); 2269 if (name != null && ModelBindingManager.getGlobalOraclePackage() != null) { 2270 name = name.qualifiedBy(ModelBindingManager.getGlobalOraclePackage().getAliasKey()); 2271 } 2272 RoutineAliasKey withArgs = name == null ? null : name.withSignature(RoutineAliases.argumentTypes(stmt, vendor)); 2273 RoutineAliasKey withArgNum = name == null ? null : name.withSignature(RoutineAliases.argumentCount(stmt, vendor)); 2274 RoutineAliasKey withInputArgNum = name == null ? null 2275 : name.withSignature(RoutineAliases.inputArgumentCount(stmt, vendor)); 2276 if (isOraclePackageMember() && !(modelManager.getModel(stmt) instanceof Procedure)) { 2277 Procedure declared = findOraclePackageDeclaration(stmt); 2278 if (declared != null) { 2279 modelManager.bindModel(stmt, declared); 2280 return declared; 2281 } 2282 } 2283 if (this.modelManager.getModel(stmt) instanceof Procedure) { 2284 return (Procedure) this.modelManager.getModel(stmt); 2285 } 2286 if (!isOraclePackageMember()) { 2287 Procedure existing = this.modelManager.getProcedure(withArgs); 2288 if (existing == null) { 2289 existing = this.modelManager.getProcedure(withArgNum); 2290 } 2291 if (existing == null) { 2292 existing = this.modelManager.getProcedure(withInputArgNum); 2293 } 2294 if (existing != null) { 2295 if (existing.getProcedureObject() instanceof TObjectName) { 2296 // A call created this model by the routine's name before any definition of it was analyzed (a 2297 // later unit defines it, Mantis 4730). The definition takes it over: it describes the 2298 // definition and is indexed as the definition is when it comes first. 2299 existing.defineBy(stmt); 2300 this.modelManager.bindModel(stmt, existing); 2301 this.modelManager.bindProcedure(withArgs, existing); 2302 this.modelManager.bindProcedure(withArgNum, existing); 2303 this.modelManager.bindProcedure(withInputArgNum, existing); 2304 } 2305 return existing; 2306 } 2307 } 2308 Procedure procedure = new Procedure(stmt); 2309 this.modelManager.bindModel(stmt, procedure); 2310 this.modelManager.bindProcedure(withArgs, procedure); 2311 this.modelManager.bindProcedure(withArgNum, procedure); 2312 this.modelManager.bindProcedure(withInputArgNum, procedure); 2313 return procedure; 2314 } 2315 2316 private boolean isOraclePackageMember() { 2317 // The legacy name/arity aliases cannot distinguish quoted names or overloads. 2318 // Package members have a declaration AST identity; retain it for call binding. 2319 return ModelBindingManager.getGlobalVendor() == EDbVendor.dbvoracle 2320 && ModelBindingManager.getGlobalOraclePackage() != null; 2321 } 2322 2323 private Procedure findOraclePackageDeclaration(TStoredProcedureSqlStatement stmt) { 2324 RoutineIdentity wanted = oracleDeclarationSignature(stmt); 2325 for (TStoredProcedureSqlStatement prior : modelManager.getProcedures()) { 2326 Object bound = modelManager.getModel(prior); 2327 if (!(bound instanceof Procedure)) continue; 2328 Procedure candidate = (Procedure) bound; 2329 if (candidate.getParentPackage() != ModelBindingManager.getGlobalOraclePackage()) continue; 2330 // Package specification/body pairs share scope; nested declarations share 2331 // scope only when they have the very same enclosing routine AST. 2332 if (prior.getParentStmt() != stmt.getParentStmt() 2333 && !(prior.getParentStmt() instanceof TPlsqlCreatePackage 2334 && stmt.getParentStmt() instanceof TPlsqlCreatePackage)) continue; 2335 RoutineIdentity existing = oracleDeclarationSignature(prior); 2336 if (wanted.getKind() == existing.getKind() 2337 && wanted.getCanonName().equals(existing.getCanonName()) 2338 && wanted.getParamNames().equals(existing.getParamNames()) 2339 && wanted.getParamModes().equals(existing.getParamModes()) 2340 && wanted.getParamTypeNames().equals(existing.getParamTypeNames())) return candidate; 2341 } 2342 return null; 2343 } 2344 2345 private RoutineIdentity oracleDeclarationSignature( 2346 TStoredProcedureSqlStatement stmt) { 2347 return RoutineIdentity.of(EDbVendor.dbvoracle, 2348 stmt instanceof gudusoft.gsqlparser.stmt.oracle.TPlsqlCreateFunction 2349 ? RoutineKind.PACKAGE_MEMBER_FUNCTION : RoutineKind.PACKAGE_MEMBER_PROCEDURE, 2350 null, null, null, DlineageUtil.getProcedureOrFunctionName(stmt).getObjectString(), 2351 stmt.getParameterDeclarations(), null); 2352 } 2353 2354 public OraclePackage createOraclePackage(TPlsqlCreatePackage stmt) { 2355 if (this.modelManager.getModel(stmt) instanceof OraclePackage) { 2356 return (OraclePackage) this.modelManager.getModel(stmt); 2357 } else { 2358 OraclePackage oraclePackage = new OraclePackage(stmt); 2359 this.modelManager.bindModel(stmt, oraclePackage); 2360 RoutineAliasKey[] keys = packageAliasKeys(stmt); 2361 if (keys != null) { 2362 for (RoutineAliasKey key : keys) { 2363 this.modelManager.bindOraclePackage(key, oraclePackage); 2364 } 2365 } 2366 return oraclePackage; 2367 } 2368 } 2369 2370 /** 2371 * A package statement's name as written with each of its three alias suffixes (types, count, input count), as 2372 * {@code createOraclePackage} binds them; null when the statement has no name. 2373 */ 2374 public static RoutineAliasKey[] packageAliasKeys(TStoredProcedureSqlStatement stmt) { 2375 EDbVendor vendor = ModelBindingManager.getGlobalVendor(); 2376 RoutineAliasKey name = RoutineAliasKey.written(DlineageUtil.getProcedureOrFunctionName(stmt), null); 2377 if (name == null) { 2378 return null; 2379 } 2380 return new RoutineAliasKey[] {name.withSignature(RoutineAliases.argumentTypes(stmt, vendor)), 2381 name.withSignature(RoutineAliases.argumentCount(stmt, vendor)), 2382 name.withSignature(RoutineAliases.inputArgumentCount(stmt, vendor))}; 2383 } 2384 2385 public Procedure createProcedureFromFunctionCall(TFunctionCall function) { 2386 Procedure procedure = createProcedureFromFunctionCallInternal(function); 2387 if (gudusoft.gsqlparser.dlineage.routine.RoutineShadow.enabled() && function != null) { 2388 gudusoft.gsqlparser.dlineage.routine.RoutineShadow.declaredFromCall(modelManager, "fromFunctionCall", 2389 function.getFunctionName(), function, null, gudusoft.gsqlparser.catalog.runtime.CatalogObjectKind.FUNCTION, 2390 procedure); 2391 } 2392 return procedure; 2393 } 2394 2395 private Procedure createProcedureFromFunctionCallInternal(TFunctionCall function) { 2396 if (this.modelManager.getModel(function) instanceof Procedure) { 2397 return (Procedure) this.modelManager.getModel(function); 2398 } else { 2399 Procedure procedure = new Procedure(function); 2400 procedure.setType(ESqlStatementType.sstcreateprocedure); 2401 this.modelManager.bindModel(function, procedure); 2402 this.modelManager.bindProcedure(procedure.getAliasKey(), procedure); 2403 return procedure; 2404 } 2405 } 2406 2407 2408 public Procedure createProcedureByName(TObjectName procedureName, int argumentSize) { 2409 Procedure procedure = createProcedureByNameInternal(procedureName, argumentSize); 2410 if (gudusoft.gsqlparser.dlineage.routine.RoutineShadow.enabled()) { 2411 boolean counted = argumentSize > 0 && ModelBindingManager.getGlobalVendor() != null 2412 && gudusoft.gsqlparser.dlineage.routine.RoutineDeclaration.callsCounted(ModelBindingManager.getGlobalVendor()); 2413 gudusoft.gsqlparser.dlineage.routine.RoutineShadow.declaredFromCall(modelManager, "byName", procedureName, 2414 procedureName, counted ? Integer.valueOf(argumentSize) : null, 2415 gudusoft.gsqlparser.catalog.runtime.CatalogObjectKind.PROCEDURE, procedure); 2416 } 2417 return procedure; 2418 } 2419 2420 private Procedure createProcedureByNameInternal(TObjectName procedureName, int argumentSize) { 2421 // The name as written, with its argument count where the vendor overloads routines by count. 2422 RoutineAliasKey key = RoutineAliasKey.written(procedureName, argumentSize <= 0 ? null 2423 : RoutineAliases.countIfOverloads(argumentSize, ModelBindingManager.getGlobalVendor())); 2424 if (this.modelManager.getModel(procedureName) instanceof Procedure) { 2425 return (Procedure) this.modelManager.getModel(procedureName); 2426 } 2427 Procedure existing = this.modelManager.getProcedure(key); 2428 if (existing != null) { 2429 return existing; 2430 } 2431 Procedure procedure = new Procedure(procedureName); 2432 procedure.setType(ESqlStatementType.sstcreateprocedure); 2433 this.modelManager.bindModel(procedureName, procedure); 2434 this.modelManager.bindProcedure(key, procedure); 2435 return procedure; 2436 } 2437 2438 public Argument createProcedureArgument(Procedure procedure, TParameterDeclaration parameter, int index) { 2439 if (this.modelManager.getModel(parameter) instanceof Argument) { 2440 return (Argument) this.modelManager.getModel(parameter); 2441 } else { 2442 if (procedure.getArguments() != null) { 2443 for (Argument argument : procedure.getArguments()) { 2444 if (parameter.getParameterName() == null) { 2445 continue; 2446 } 2447 if (argument.getName().equals(parameter.getParameterName().toString())) { 2448 return argument; 2449 } 2450 } 2451 } 2452 Argument argumentModel = new Argument(procedure, parameter, index); 2453 this.modelManager.bindModel(parameter, argumentModel); 2454 this.modelManager.bindModel(procedure.getName() + ":" + argumentModel.getName(), argumentModel); 2455 return argumentModel; 2456 } 2457 } 2458 2459 public Argument createProcedureArgument(OraclePackage oraclePackage, TParameterDeclaration parameter, int index) { 2460 if (this.modelManager.getModel(parameter) instanceof Argument) { 2461 return (Argument) this.modelManager.getModel(parameter); 2462 } else { 2463 Argument argumentModel = new Argument(oraclePackage, parameter, index); 2464 this.modelManager.bindModel(parameter, argumentModel); 2465 return argumentModel; 2466 } 2467 } 2468 2469}