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}