001
002package gudusoft.gsqlparser.dlineage;
003
004import gudusoft.gsqlparser.dlineage.symbol.LocalScopes;
005import gudusoft.gsqlparser.dlineage.symbol.LocalSymbolKey;
006import gudusoft.gsqlparser.dlineage.routine.RoutineAliasKey;
007import gudusoft.gsqlparser.dlineage.routine.RoutineAliasRegistry;
008import gudusoft.gsqlparser.dlineage.routine.RoutineAliases;
009import gudusoft.gsqlparser.dlineage.comment.CommentBindingCollector;
010import gudusoft.gsqlparser.dlineage.comment.CommentBindingBundle;
011import gudusoft.gsqlparser.dlineage.comment.CommentBindingProjection;
012
013import gudusoft.gsqlparser.*;
014import gudusoft.gsqlparser.common.structured.StructuredAdapterContext;
015import gudusoft.gsqlparser.common.structured.StructuredDataflowDescriptor;
016import gudusoft.gsqlparser.common.structured.StructuredDataflowRegistry;
017import gudusoft.gsqlparser.common.structured.StructuredFieldBinding;
018import gudusoft.gsqlparser.common.structured.StructuredValueSource;
019import gudusoft.gsqlparser.dlineage.dataflow.listener.DataFlowHandleListener;
020import gudusoft.gsqlparser.dlineage.dataflow.metadata.MetadataReader;
021import gudusoft.gsqlparser.dlineage.dataflow.metadata.grabit.GrabitMetadataAnalyzer;
022import gudusoft.gsqlparser.dlineage.dataflow.metadata.sqldep.SQLDepMetadataAnalyzer;
023import gudusoft.gsqlparser.dlineage.dataflow.metadata.sqlflow.SqlflowMetadataAnalyzer;
024import gudusoft.gsqlparser.dlineage.dataflow.metadata.sqlflow.sharded.SqlflowShardedMetadataAnalyzer;
025import gudusoft.gsqlparser.dlineage.impl.powerquery.PowerQueryLineageResult;
026import gudusoft.gsqlparser.dlineage.impl.powerquery.TPowerQueryAnalyzer;
027import gudusoft.gsqlparser.dlineage.dataflow.model.*;
028import gudusoft.gsqlparser.dlineage.dataflow.model.Process;
029import gudusoft.gsqlparser.dlineage.dataflow.model.JoinRelationship.JoinClauseType;
030import gudusoft.gsqlparser.dlineage.dataflow.model.json.Coordinate;
031import gudusoft.gsqlparser.dlineage.dataflow.model.json.Dataflow;
032import gudusoft.gsqlparser.dlineage.dataflow.model.json.Error;
033import gudusoft.gsqlparser.dlineage.dataflow.model.xml.*;
034import gudusoft.gsqlparser.dlineage.dynamicsql.DynamicSqlTrustMode;
035import gudusoft.gsqlparser.dlineage.dataflow.sqlenv.SQLEnvParser;
036import gudusoft.gsqlparser.dlineage.metadata.MetadataUtil;
037import gudusoft.gsqlparser.dlineage.metadata.Sqlflow;
038import gudusoft.gsqlparser.dlineage.util.*;
039import gudusoft.gsqlparser.nodes.TTable;
040import gudusoft.gsqlparser.nodes.*;
041import gudusoft.gsqlparser.nodes.couchbase.TObjectConstruct;
042import gudusoft.gsqlparser.nodes.couchbase.TPair;
043import gudusoft.gsqlparser.nodes.functions.TJsonObjectFunction;
044import gudusoft.gsqlparser.nodes.hive.THiveTransformClause;
045import gudusoft.gsqlparser.nodes.hive.THiveTransformClause.ETransformType;
046import gudusoft.gsqlparser.resolver2.ScopeBuildResult;
047import gudusoft.gsqlparser.resolver2.TSQLResolver2;
048import gudusoft.gsqlparser.sqlenv.*;
049import gudusoft.gsqlparser.sqlenv.parser.TJSONSQLEnvParser;
050import gudusoft.gsqlparser.sqlenv.parser.sqlflow.SqlflowNameForm;
051import gudusoft.gsqlparser.stmt.*;
052import gudusoft.gsqlparser.stmt.db2.TDb2CallStmt;
053import gudusoft.gsqlparser.stmt.db2.TDb2ReturnStmt;
054import gudusoft.gsqlparser.stmt.db2.TDb2SqlVariableDeclaration;
055import gudusoft.gsqlparser.stmt.hive.THiveLoad;
056import gudusoft.gsqlparser.stmt.mssql.*;
057import gudusoft.gsqlparser.stmt.mysql.TLoadDataStmt;
058import gudusoft.gsqlparser.stmt.oracle.*;
059import gudusoft.gsqlparser.stmt.powerquery.TPowerQueryDocumentStmt;
060import gudusoft.gsqlparser.stmt.redshift.TRedshiftCopy;
061import gudusoft.gsqlparser.stmt.redshift.TRedshiftDeclare;
062import gudusoft.gsqlparser.stmt.snowflake.*;
063import gudusoft.gsqlparser.stmt.teradata.TTeradataCreateProcedure;
064import gudusoft.gsqlparser.util.*;
065import gudusoft.gsqlparser.util.json.JSON;
066
067import javax.script.ScriptEngine;
068import javax.script.ScriptEngineManager;
069import javax.script.ScriptException;
070import java.io.*;
071import java.util.zip.GZIPInputStream;
072import java.util.*;
073import java.util.concurrent.atomic.AtomicInteger;
074import java.util.regex.Matcher;
075import java.util.regex.Pattern;
076import java.util.stream.Collectors;
077
078import static gudusoft.gsqlparser.EJoinType.right;
079
080@SuppressWarnings("rawtypes")
081/**
082 * Single-threaded data flow analyzer that performs lineage analysis on SQL scripts.
083 *
084 * <h2>Avoiding OOM with Large Files</h2>
085 * <p>
086 * Analyzing very large SQL files or manifests (e.g. sqlflow-sharded exports with many
087 * shards) in a single JVM can easily exhaust heap memory and cause {@code OutOfMemoryError}.
088 * To avoid OOM, enable automatic large-file detection and delegation:
089 *
090 * <pre>{@code
091 * Option option = new Option();
092 * option.setAutoDetectLargeFile(true);                        // enable auto-detection
093 * option.setLargeSqlInfoCountThreshold(1000);                 // max SqlInfo entries before delegating
094 * option.setLargeSqlTotalSizeThreshold(25 * 1024 * 1024);     // max total SQL size (25 MB) before delegating
095 * option.setLargeQueryCountThreshold(1000);                   // max manifest queries before delegating
096 * option.setLargeShardCountThreshold(10);                     // max sharded source files before delegating
097 * option.setLargeFileSplitSizeMB(5);                          // split files larger than 5 MB
098 * option.setEstimatedMemoryPerTaskMB(2560);                   // estimated memory per parallel task (2.5 GB)
099 * option.setParallel(0);                                      // 0 = auto-calculate from CPU cores
100 *
101 * DataFlowAnalyzer analyzer = new DataFlowAnalyzer(sqlFiles, option);
102 * analyzer.generateDataFlow();
103 * }</pre>
104 *
105 * <p>
106 * When {@code autoDetectLargeFile} is enabled, {@link #generateDataFlow(boolean)} invokes
107 * {@link gudusoft.gsqlparser.dlineage.util.LargeFileDetector} to check the input scale against
108 * the configured thresholds. If any threshold is exceeded, the analyzer automatically delegates
109 * to {@link gudusoft.gsqlparser.dlineage.ParallelDataFlowAnalyzer}, which:
110 * <ul>
111 *   <li>Splits large files into smaller chunks via {@link gudusoft.gsqlparser.util.FileSplitter}</li>
112 *   <li>Processes each chunk in a separate {@code DataFlowAnalyzer} instance within a bounded
113 *       thread pool (size capped by {@code estimatedMemoryPerTaskMB})</li>
114 *   <li>For sharded manifests, distributes individual shard source files across parallel tasks</li>
115 *   <li>Merges results from all parallel tasks into a single dataflow output</li>
116 * </ul>
117 *
118 * <p>
119 * If delegation fails for any reason, the analyzer falls back to single-threaded processing
120 * with a warning log, so the analysis still completes (though it may OOM on very large inputs).
121 *
122 * @see gudusoft.gsqlparser.dlineage.util.LargeFileDetector
123 * @see gudusoft.gsqlparser.dlineage.ParallelDataFlowAnalyzer
124 * @see gudusoft.gsqlparser.dlineage.dataflow.model.Option#setAutoDetectLargeFile(boolean)
125 */
126public class DataFlowAnalyzer implements IDataFlowAnalyzer {
127
128        private static final Logger logger = LoggerFactory.getLogger(DataFlowAnalyzer.class);
129
130        private static final List<String> TERADATA_BUILTIN_FUNCTIONS = Arrays
131                        .asList(new String[] { "ACCOUNT", "CURRENT_DATE", "CURRENT_ROLE", "CURRENT_TIME", "CURRENT_TIMESTAMP",
132                                        "CURRENT_USER", "DATABASE", "DATE", "PROFILE", "ROLE", "SESSION", "TIME", "USER", "SYSDATE", });
133
134        private static final List<String> CONSTANT_BUILTIN_FUNCTIONS = Arrays.asList(new String[] { "ACCOUNT",
135                        "CURRENT_DATE", "CURRENT_ROLE", "CURRENT_TIME", "CURRENT_TIMESTAMP", "CURRENT_USER", "DATABASE", "DATE",
136                        "PROFILE", "ROLE", "SESSION", "TIME", "USER", "SYSDATE", "GETDATE" });
137
138        private Stack<TCustomSqlStatement> stmtStack = new Stack<TCustomSqlStatement>();
139        private List<ResultSet> appendResultSets = new ArrayList<ResultSet>();
140        private Set<TCustomSqlStatement> accessedStatements = new HashSet<TCustomSqlStatement>();
141        private Set<TSelectSqlStatement> accessedSubqueries = new HashSet<TSelectSqlStatement>();
142        /** A view's or synonym's DDL by the identity of its name as written (DlineageUtil.getTableIdentityKey). */
143        private Map<Object, TCustomSqlStatement> viewDDLMap = new HashMap<Object, TCustomSqlStatement>();
144        // The script's routine definitions, by the routine's name as written with its argument-count suffix, for calls
145        // that precede the definition they call.
146        private final RoutineAliasRegistry<TCustomSqlStatement> procedureDDLAliases = new RoutineAliasRegistry<TCustomSqlStatement>();
147        private final Map<OraclePackage, List<Pair<TBasicStmt, Procedure>>> pendingOracleCallArguments =
148                        new IdentityHashMap<OraclePackage, List<Pair<TBasicStmt, Procedure>>>();
149        // EXECs whose callee had no definition yet, in call order; their arguments are bound after the last unit
150        // (see bindDeferredExecuteArguments).
151        private final Map<TMssqlExecute, DeferredExecuteBinding> deferredExecuteBindings =
152                        new LinkedHashMap<TMssqlExecute, DeferredExecuteBinding>();
153        // Every statement analyzeCustomSqlStmt is analyzing, outermost first: a statement analyzed inside a dynamic SQL
154        // site has that site's statement below it.
155        private final List<TCustomSqlStatement> analysisPath = new ArrayList<TCustomSqlStatement>();
156        private Map<TTable, TObjectNameList> structObjectMap = new HashMap<TTable, TObjectNameList>();
157        private final Set<TParseTreeNode> processingFunctions = new HashSet<TParseTreeNode>();
158
159        // perf (10x, perf/column-lineage-10x): normalized-column-name -> first ResultColumn index over a
160        // resultset's column list. analyzeDataFlowRelation otherwise LINEAR-scans queryTable.getColumns()
161        // once per target column => O(W^2) for wide resultsets (now the dominant dlineage hotspot after the
162        // CTENamespace memo). Keyed by the getColumns() list identity, which is the stable ResultSet.columns
163        // field. Only built for "simple" column lists (no star, no struct) because the linear scan has ordered
164        // side effects on star/struct columns; other lists fall back to the scan. Rebuilt when size changes.
165        // The fast path is used only when it is byte-identical to the scan (verified over the full corpus).
166        private final java.util.IdentityHashMap<List<ResultColumn>, ColumnNameIndex> columnNameIndexCache =
167                        new java.util.IdentityHashMap<List<ResultColumn>, ColumnNameIndex>();
168
169        /** Cached identifier key -> first ResultColumn map for a simple resultset column list. */
170        private static final class ColumnNameIndex {
171                final int size;
172                /** null when the column list is NOT simple (has a star or struct column) — use the linear scan. */
173                final Map<CanonKey, ResultColumn> byIdentifier;
174                ColumnNameIndex(int size, Map<CanonKey, ResultColumn> byIdentifier) {
175                        this.size = size;
176                        this.byIdentifier = byIdentifier;
177                }
178        }
179
180        /**
181         * Return the identity index for {@code columns}, rebuilding when the list size changes.
182         * An unidentified expression, star or struct requires the ordered linear scan.
183         */
184        private ColumnNameIndex getColumnNameIndex(List<ResultColumn> columns) {
185                ColumnNameIndex idx = columnNameIndexCache.get(columns);
186                if (idx != null && idx.size == columns.size()) {
187                        return idx;
188                }
189                Map<CanonKey, ResultColumn> map = new HashMap<>();
190                boolean simple = true;
191                for (int i = 0; i < columns.size(); i++) {
192                        ResultColumn c = columns.get(i);
193                        if (c.getIdentifierKey() == null || c.isStruct()) {
194                                simple = false;
195                                break;
196                        }
197                        // putIfAbsent semantics: keep the FIRST column for a given identity, matching the
198                        // linear scan's first-match-wins + break.
199                        CanonKey key = c.getIdentifierKey();
200                        if (!map.containsKey(key)) {
201                                map.put(key, c);
202                        }
203                }
204                idx = new ColumnNameIndex(columns.size(), simple ? map : null);
205                columnNameIndexCache.put(columns, idx);
206                return idx;
207        }
208
209        // perf: normalized-column-name -> ResultColumn map for a resultset's column list, used by
210        // appendStarRelation to avoid O(S*C) linear scans per star column. Unlike ColumnNameIndex,
211        // this cache does NOT require "simple" column lists — it works for any list including those
212        // with star/struct columns, because appendStarRelation only needs O(1) lookup, not the
213        // ordered side effects that the linear scan in analyzeDataFlowRelation provides.
214        private final java.util.IdentityHashMap<List<ResultColumn>, StarLinkColumnIndex<ResultColumn>> resultSetColumnLookupCache =
215                        new java.util.IdentityHashMap<List<ResultColumn>, StarLinkColumnIndex<ResultColumn>>();
216
217        private StarLinkColumnIndex<ResultColumn> getResultSetColumnLookup(List<ResultColumn> columns) {
218                StarLinkColumnIndex<ResultColumn> index = resultSetColumnLookupCache.get(columns);
219                if (index == null) {
220                        index = new StarLinkColumnIndex<ResultColumn>(columns);
221                        resultSetColumnLookupCache.put(columns, index);
222                }
223                return index;
224        }
225
226        // perf: normalized-column-name -> TableColumn map for a table's column list, used by
227        // appendStarRelation to avoid O(C) linear scans via searchTableColumn.
228        private final java.util.IdentityHashMap<List<TableColumn>, StarLinkColumnIndex<TableColumn>> tableColumnLookupCache =
229                        new java.util.IdentityHashMap<List<TableColumn>, StarLinkColumnIndex<TableColumn>>();
230
231        private StarLinkColumnIndex<TableColumn> getTableColumnLookup(List<TableColumn> columns) {
232                StarLinkColumnIndex<TableColumn> index = tableColumnLookupCache.get(columns);
233                if (index == null) {
234                        index = new StarLinkColumnIndex<TableColumn>(columns);
235                        tableColumnLookupCache.put(columns, index);
236                }
237                return index;
238        }
239
240        private SqlInfo[] sqlInfos;
241        private File[] originalFiles;
242        private List<ErrorInfo> errorInfos = new ArrayList<ErrorInfo>();
243        private final List<DynamicSqlSite> dynamicSqlSites = new ArrayList<DynamicSqlSite>();
244        // The index in dynamicSqlSites of the last site each statement recorded (see attributeToEnclosingDynamicSite).
245        private final Map<TCustomSqlStatement, Integer> dynamicSiteIndexes =
246                        new IdentityHashMap<TCustomSqlStatement, Integer>();
247        /**
248         * The index in dynamicSqlSites of the site each statement of an evaluated variant's text belongs to: one EXEC
249         * records a site per variant, and a statement of one variant's text is that variant's (Mantis 4962).
250         */
251        private final Map<TCustomSqlStatement, Integer> dynamicInnerSiteIndexes =
252                        new IdentityHashMap<TCustomSqlStatement, Integer>();
253        private final Set<CteSelectRelationKey> cteSelectRelationKeys = new HashSet<CteSelectRelationKey>();
254        /**
255         * Per-procedure evaluation variants for the default dynamic-SQL path (identity keys).
256         * Index 0 is the no-bindings evaluation; further entries are one per literal call-site
257         * binding set discovered in the same file (capped).
258         */
259        private final Map<TCustomSqlStatement, DynamicEvaluation> dynamicEvalCache = new IdentityHashMap<TCustomSqlStatement, DynamicEvaluation>();
260
261        /**
262         * One procedure's default-path dynamic-SQL evaluation, built once per procedure node: the materialized
263         * variants (no bindings first, then one group per call-site binding set) and the notes every dynamic site of
264         * the procedure reports, an evaluation step that failed or a call site that was not bound (Mantis 4962).
265         */
266        private static final class DynamicEvaluation {
267                final List<Map<TCustomSqlStatement, gudusoft.gsqlparser.dlineage.dynamicsql.DynamicSqlLineageResolver.MaterializedSite>> variants =
268                                new ArrayList<Map<TCustomSqlStatement, gudusoft.gsqlparser.dlineage.dynamicsql.DynamicSqlLineageResolver.MaterializedSite>>();
269                final List<String> notes = new ArrayList<String>();
270                /**
271                 * The call-site bindings stand for every call of the procedure in the analysis (Mantis 4962): only then is
272                 * a site some binding reached published with its bound variants alone.
273                 */
274                boolean bindingsExhaustive = true;
275                /**
276                 * The procedure's presets are more than the evaluation takes (maxBindings): no variant of it stands for them,
277                 * and none evaluated without them is published in their place.
278                 */
279                boolean presetsRejected;
280
281                void note(String note) {
282                        if (note != null && !notes.contains(note)) {
283                                notes.add(note);
284                        }
285                }
286        }
287        /**
288         * Multi-file mssql analyses only (plan Phase 5): the raw text of every analysis
289         * unit, so parameter-dependent dynamic SQL in one file can pick up literal EXEC
290         * bindings from another. Null in single-file analyses — the cross-file discovery
291         * path is then never entered and per-file output is byte-identical to before.
292         */
293        private final Map<String, TStatementList> crossFileParseCache = new HashMap<String, TStatementList>();
294        /** Shared routine/call-site index over all units (plan §5.6); null in single-file analyses. */
295        private gudusoft.gsqlparser.dlineage.dynamicsql.RoutineCatalog routineCatalog;
296        /** Oracle files (by unit text) already run through the PL/SQL interpreter this analysis. */
297        private final Set<String> plsqlEvaluatedTexts = new HashSet<String>();
298        private IndexedLinkedHashMap<String, List<SqlInfo>> sqlInfoMap = new IndexedLinkedHashMap<String, List<SqlInfo>>();
299        private TSQLEnv sqlenv = null;
300        /**
301         * The identifier configuration this analysis runs under, snapshotted once at the
302         * {@code generateDataFlow} entry (see {@link #freezeIdentifierProfile()}). Null until then.
303         *
304         * <p>A snapshot, not a live read of {@link Option}: the option object is mutable and public,
305         * and an analysis whose rules could change halfway would have indexed part of its catalog
306         * under one relation and probed it under another.
307         */
308        private IdentifierProfile frozenIdentifierProfile = null;
309        private IdentifierService frozenIdentifierService = null;
310        /**
311         * Package-private, not private, for white-box tests (they read it through the test helper
312         * {@code gudusoft.gsqlparser.dlineage.AnalyzerModels}): the shipment obfuscates this name, and the obfuscation
313         * gate remaps a direct field reference but never a reflective {@code getDeclaredField("modelManager")}.
314         */
315        ModelBindingManager modelManager = new ModelBindingManager();
316        private ModelFactory modelFactory = new ModelFactory(modelManager);
317        private PipelinedFunctionAnalyzer pipelinedAnalyzer;
318        private List<Long> tableIds = new ArrayList<Long>();
319        private TTableList hiveFromTables;
320        private dataflow dataflow;
321    private CommentBindingCollector commentBindingCollector;
322    private String commentSourceKey;
323        private String dataflowString;
324        /**
325         * Package-private, not private, so a white-box test can seed a draft through the test helper
326         * {@code gudusoft.gsqlparser.dlineage.AnalyzerModels}: the shipment obfuscates this name, and the obfuscation
327         * gate remaps a direct field reference but never a reflective {@code getDeclaredField} lookup.
328         */
329        final AuthoritativeLineageEvidenceCollector authoritativeEvidenceCollector =
330                        new AuthoritativeLineageEvidenceCollector();
331
332        /**
333         * Whether this analyzer is still holding authoritative-evidence DRAFT state.
334         *
335         * <p>Drafts are per-analysis working state; the lifecycle invariant is that
336         * none are retained once a generation completes or the analyzer is disposed.
337         * Always false in normal use — a true here means evidence accumulated across
338         * analyses, which would leak one analysis's findings into the next.</p>
339         *
340         * <p>This deliberately exposes the INVARIANT, not the collector: the
341         * collector class and its fields stay package-private, so they remain free to
342         * change, and nothing internal is frozen into the public API. It is public so
343         * the assertion can be made against the shipped (ProGuard-obfuscated) jar,
344         * where package-private members are renamed and unreachable.</p>
345         *
346         * @return true if any evidence draft state is still held
347         * @since 4.2.6
348         */
349        public boolean hasRetainedEvidenceDrafts() {
350                return authoritativeEvidenceCollector.hasRetainedDrafts();
351        }
352        private AuthoritativeLineageEvidence authoritativeLineageEvidence =
353                        AuthoritativeLineageEvidence.empty();
354        private Option option = new Option();
355
356        {
357                modelManager.TABLE_COLUMN_ID = option.getStartId();
358                modelManager.RELATION_ID = option.getStartId();
359                ModelBindingManager.set(modelManager);
360                ModelBindingManager.setGlobalStmtStack(stmtStack);
361                ModelBindingManager.setGlobalOption(option);
362                ModelBindingManager.setGlobalSqlInfo(sqlInfoMap);
363        }
364
365    private void initPipelinedFunctionAnalyzer() {
366        // Only initialize pipelinedAnalyzer for Oracle
367        if (option.getVendor() == EDbVendor.dbvoracle)
368        {
369            pipelinedAnalyzer = new PipelinedFunctionAnalyzer(modelManager, modelFactory, option);
370        }
371        else{
372            pipelinedAnalyzer = null;
373        }
374    }
375
376    public DataFlowAnalyzer(Sqlflow sqlflow, Option option) {
377        this.option = option;
378        ModelBindingManager.setGlobalOption(this.option);
379
380        SqlInfo info = new SqlInfo();
381        info.setOriginIndex(0);
382
383        File tempFile = LargeFileDetector.writeSqlflowToTempFile(sqlflow);
384
385        if (option.isAutoDetectLargeFile() && tempFile.length() > option.getLargeSqlThresholdBytes()) {
386            info.setFilePath(tempFile.getAbsolutePath());
387            info.setFileName(tempFile.getName());
388            this.sqlInfos = new SqlInfo[] { info };
389            return;
390        }
391
392        String sql = SQLUtil.getFileContent(tempFile);
393        tempFile.delete();
394        info.setSql(sql);
395
396        this.sqlInfos = convertSQL(option.getVendor(), JSON.toJSONString(new SqlInfo[] { info })).toArray(new SqlInfo[0]);
397    }
398
399    public DataFlowAnalyzer(String sqlContent, Option option) {
400                SqlInfo[] sqlInfos = new SqlInfo[1];
401                SqlInfo info = new SqlInfo();
402                info.setSql(sqlContent);
403                info.setOriginIndex(0);
404                sqlInfos[0] = info;
405                this.option = option;
406                ModelBindingManager.setGlobalOption(this.option);
407                if (option.isLargeSql(sqlContent)) {
408                        LargeFileDetector.persistSqlToTempFile(info, sqlContent);
409            this.sqlInfos = sqlInfos;
410                }
411        else {
412            this.sqlInfos = convertSQL(option.getVendor(), JSON.toJSONString(sqlInfos)).toArray(new SqlInfo[0]);
413        }
414    }
415
416    public DataFlowAnalyzer(String sqlContent, EDbVendor dbVendor, boolean simpleOutput, String defaultServer,
417                        String defaultDatabase, String defaltSchema) {
418                SqlInfo[] sqlInfos = new SqlInfo[1];
419                SqlInfo info = new SqlInfo();
420                info.setSql(sqlContent);
421                info.setOriginIndex(0);
422                sqlInfos[0] = info;
423                option.setVendor(dbVendor);
424                option.setSimpleOutput(simpleOutput);
425                option.setDefaultServer(defaultServer);
426                option.setDefaultDatabase(defaultDatabase);
427                option.setDefaultSchema(defaltSchema);
428                this.sqlInfos = convertSQL(dbVendor, JSON.toJSONString(sqlInfos)).toArray(new SqlInfo[0]);
429        }
430
431        public DataFlowAnalyzer(String sqlContent, EDbVendor dbVendor, boolean simpleOutput) {
432                SqlInfo[] sqlInfos = new SqlInfo[1];
433                SqlInfo info = new SqlInfo();
434                info.setSql(sqlContent);
435                info.setOriginIndex(0);
436                sqlInfos[0] = info;
437                option.setVendor(dbVendor);
438                option.setSimpleOutput(simpleOutput);
439                this.sqlInfos = convertSQL(dbVendor, JSON.toJSONString(sqlInfos)).toArray(new SqlInfo[0]);
440    }
441
442        public DataFlowAnalyzer(String[] sqlContents, Option option) {
443                SqlInfo[] sqlInfos = new SqlInfo[sqlContents.length];
444                for (int i = 0; i < sqlContents.length; i++) {
445                        SqlInfo info = new SqlInfo();
446                        info.setSql(sqlContents[i]);
447                        info.setOriginIndex(0);
448                        sqlInfos[i] = info;
449                }
450                this.option = option;
451                ModelBindingManager.setGlobalOption(this.option);
452                this.sqlInfos = convertSQL(option.getVendor(), JSON.toJSONString(sqlInfos)).toArray(new SqlInfo[0]);
453        }
454
455        public DataFlowAnalyzer(String[] sqlContents, EDbVendor dbVendor, boolean simpleOutput, String defaultServer,
456                        String defaultDatabase, String defaltSchema) {
457                SqlInfo[] sqlInfos = new SqlInfo[sqlContents.length];
458                for (int i = 0; i < sqlContents.length; i++) {
459                        SqlInfo info = new SqlInfo();
460                        info.setSql(sqlContents[i]);
461                        info.setOriginIndex(0);
462                        sqlInfos[i] = info;
463                }
464                option.setVendor(dbVendor);
465                option.setSimpleOutput(simpleOutput);
466                option.setDefaultServer(defaultServer);
467                option.setDefaultDatabase(defaultDatabase);
468                option.setDefaultSchema(defaltSchema);
469                this.sqlInfos = convertSQL(dbVendor, JSON.toJSONString(sqlInfos)).toArray(new SqlInfo[0]);
470        }
471
472        public DataFlowAnalyzer(String[] sqlContents, EDbVendor dbVendor, boolean simpleOutput) {
473                SqlInfo[] sqlInfos = new SqlInfo[sqlContents.length];
474                for (int i = 0; i < sqlContents.length; i++) {
475                        SqlInfo info = new SqlInfo();
476                        info.setSql(sqlContents[i]);
477                        info.setOriginIndex(0);
478                        sqlInfos[i] = info;
479                }
480                option.setVendor(dbVendor);
481                option.setSimpleOutput(simpleOutput);
482                this.sqlInfos = convertSQL(dbVendor, JSON.toJSONString(sqlInfos)).toArray(new SqlInfo[0]);
483        }
484
485        public DataFlowAnalyzer(SqlInfo[] sqlInfos, Option option) {
486                this.sqlInfos = sqlInfos;
487                this.option = option;
488                ModelBindingManager.setGlobalOption(this.option);
489        }
490
491        public DataFlowAnalyzer(SqlInfo[] sqlInfos, EDbVendor dbVendor, boolean simpleOutput) {
492                this.sqlInfos = sqlInfos;
493                option.setVendor(dbVendor);
494                option.setSimpleOutput(simpleOutput);
495        }
496
497        public DataFlowAnalyzer(File[] sqlFiles, Option option) {
498                this.originalFiles = sqlFiles;
499                SqlInfo[] sqlInfos = new SqlInfo[sqlFiles.length];
500                for (int i = 0; i < sqlFiles.length; i++) {
501                        SqlInfo info = new SqlInfo();
502                        info.setFileName(sqlFiles[i].getName());
503                        info.setFilePath(sqlFiles[i].getAbsolutePath());
504                        info.setOriginIndex(0);
505                        sqlInfos[i] = info;
506                }
507                this.sqlInfos = sqlInfos;
508                this.option = option;
509                ModelBindingManager.setGlobalOption(this.option);
510        }
511
512        public DataFlowAnalyzer(File[] sqlFiles, EDbVendor dbVendor, boolean simpleOutput) {
513                this.originalFiles = sqlFiles;
514                SqlInfo[] sqlInfos = new SqlInfo[sqlFiles.length];
515                for (int i = 0; i < sqlFiles.length; i++) {
516                        SqlInfo info = new SqlInfo();
517                        info.setFileName(sqlFiles[i].getName());
518                        info.setFilePath(sqlFiles[i].getAbsolutePath());
519                        info.setOriginIndex(0);
520                        sqlInfos[i] = info;
521                }
522                this.sqlInfos = sqlInfos;
523                option.setVendor(dbVendor);
524                option.setSimpleOutput(simpleOutput);
525        }
526
527        public DataFlowAnalyzer(File sqlFile, Option option) {
528                File[] children = SQLUtil.listFiles(sqlFile);
529                this.originalFiles = children;
530                SqlInfo[] sqlInfos = new SqlInfo[children.length];
531                for (int i = 0; i < children.length; i++) {
532                        SqlInfo info = new SqlInfo();
533                        info.setFileName(children[i].getName());
534                        info.setFilePath(children[i].getAbsolutePath());
535                        info.setOriginIndex(0);
536                        sqlInfos[i] = info;
537                }
538                this.sqlInfos = sqlInfos;
539                this.option = option;
540                ModelBindingManager.setGlobalOption(this.option);
541        }
542
543        public DataFlowAnalyzer(File sqlFile, EDbVendor dbVendor, boolean simpleOutput) {
544                File[] children = SQLUtil.listFiles(sqlFile);
545                this.originalFiles = children;
546                List<SqlInfo> sqlInfos = new ArrayList<>();
547                for (int i = 0; i < children.length; i++) {
548                        SqlInfo info = new SqlInfo();
549                        info.setSql(SQLUtil.getFileContent(children[i]));
550            if(children[i].getName().toLowerCase(Locale.ROOT).endsWith(".csv")){
551                if(!MetadataReader.isMetadata(info.getSql())){
552                    continue;
553                }
554            }
555            else if(children[i].getName().toLowerCase(Locale.ROOT).endsWith(".json")){
556                if (!(MetadataReader.isGrabit(info.getSql()) || MetadataReader.isSqlflow(info.getSql()) || MetadataReader.isSqlflowSharded(info.getSql()))){
557                    continue;
558                }
559            }
560                        info.setFileName(children[i].getName());
561                        info.setFilePath(children[i].getAbsolutePath());
562                        info.setOriginIndex(0);
563                        sqlInfos.add(info);
564                }
565                this.sqlInfos = sqlInfos.toArray(new SqlInfo[0]);
566                option.setVendor(dbVendor);
567                option.setSimpleOutput(simpleOutput);
568        }
569
570        public boolean isIgnoreRecordSet() {
571                return option.isIgnoreRecordSet();
572        }
573
574        public void setIgnoreRecordSet(boolean ignoreRecordSet) {
575                option.setIgnoreRecordSet(ignoreRecordSet);
576        }
577
578        public boolean isSimpleShowTopSelectResultSet() {
579                return option.isSimpleShowTopSelectResultSet();
580        }
581
582        public void setSimpleShowTopSelectResultSet(boolean simpleShowTopSelectResultSet) {
583                option.setSimpleShowTopSelectResultSet(simpleShowTopSelectResultSet);
584        }
585
586        public boolean isSimpleShowFunction() {
587                return option.isSimpleShowFunction();
588        }
589
590        public void setSimpleShowFunction(boolean simpleShowFunction) {
591                option.setSimpleShowFunction(simpleShowFunction);
592        }
593
594        /**
595         * Whether this analysis leaves its relationship endpoints labelled as their producers spelled them, for the
596         * parallel analyzer to merge them first: a merge decides which relationships are one by their labels too, so the
597         * endpoints are labelled by their columns only once, after the last merge ({@link DataflowUtility#labelEndpoints}).
598         */
599        private boolean endpointLabelsDeferred;
600
601        /** Leaves the labelling of endpoints to the parallel analyzer's merge. */
602        void deferEndpointLabels() {
603                endpointLabelsDeferred = true;
604        }
605
606        public boolean isShowJoin() {
607                return option.isShowJoin();
608        }
609
610        public void setShowJoin(boolean showJoin) {
611                option.setShowJoin(showJoin);
612        }
613
614        public void setShowCallRelation(boolean showCallRelation) {
615                option.setShowCallRelation(showCallRelation);
616        }
617
618        public boolean isShowCallRelation() {
619                return option.isShowCallRelation();
620        }
621
622        public boolean isShowImplicitSchema() {
623                return option.isShowImplicitSchema();
624        }
625
626        public void setShowImplicitSchema(boolean showImplicitSchema) {
627                option.setShowImplicitSchema(showImplicitSchema);
628        }
629
630        public boolean isShowConstantTable() {
631                return option.isShowConstantTable();
632        }
633
634        public void setShowConstantTable(boolean showConstantTable) {
635                option.setShowConstantTable(showConstantTable);
636        }
637
638        public boolean isShowCountTableColumn() {
639                return option.isShowCountTableColumn();
640        }
641
642        public void setShowCountTableColumn(boolean showCountTableColumn) {
643                option.setShowCountTableColumn(showCountTableColumn);
644        }
645
646        public boolean isTransform() {
647                return option.isTransform();
648        }
649
650        public void setTransform(boolean transform) {
651                option.setTransform(transform);
652                if (option.isTransformCoordinate()) {
653                        option.setTransform(true);
654                }
655        }
656
657        public boolean isTransformCoordinate() {
658                return option.isTransformCoordinate();
659        }
660
661        public void setTransformCoordinate(boolean transformCoordinate) {
662                option.setTransformCoordinate(transformCoordinate);
663                if (transformCoordinate) {
664                        option.setTransform(true);
665                }
666        }
667
668        public boolean isLinkOrphanColumnToFirstTable() {
669                return option.isLinkOrphanColumnToFirstTable();
670        }
671
672        public void setLinkOrphanColumnToFirstTable(boolean linkOrphanColumnToFirstTable) {
673                option.setLinkOrphanColumnToFirstTable(linkOrphanColumnToFirstTable);
674        }
675
676        public boolean isIgnoreTemporaryTable() {
677                return option.isIgnoreTemporaryTable();
678        }
679
680        public void setIgnoreTemporaryTable(boolean ignoreTemporaryTable) {
681                option.setIgnoreTemporaryTable(ignoreTemporaryTable);
682        }
683        
684        public boolean isIgnoreCoordinate() {
685                return option.isIgnoreCoordinate();
686        }
687
688        public void setIgnoreCoordinate(boolean ignoreCoordinate) {
689                option.setIgnoreCoordinate(ignoreCoordinate);
690        }
691
692        public void setHandleListener(DataFlowHandleListener listener) {
693                option.setHandleListener(listener);
694        }
695
696        public void setSqlEnv(TSQLEnv sqlenv) {
697                this.sqlenv = sqlenv;
698        }
699
700        public void setOption(Option option) {
701                this.option = option;
702                ModelBindingManager.setGlobalOption(this.option);
703        }
704
705        public Option getOption() {
706                return option;
707        }
708
709        /**
710         * Create a parser carrying the syntax compatibility options owned by this
711         * analyzer. All primary and nested SQL parsing paths must use this method.
712         */
713        private TGSqlParser createSqlParser(EDbVendor vendor) {
714                TGSqlParser sqlparser = new TGSqlParser(vendor);
715                sqlparser.setEnableMssqlColonBindVariables(
716                                option != null && option.isMssqlColonBindVariablesEnabled());
717                if (sqllrProducerMode()) {
718                        // SQL-LR production: an EXACT_MATCH must never be a guess between candidates, whatever the
719                        // thread's or the JVM's resolver defaults are (plan 06, Section 3.1).
720                        gudusoft.gsqlparser.resolver2.TSQLResolverConfig sqllrConfig =
721                                        new gudusoft.gsqlparser.resolver2.TSQLResolverConfig();
722                        sqllrConfig.setGuessColumnStrategy(
723                                        gudusoft.gsqlparser.resolver2.TSQLResolverConfig.GUESS_COLUMN_STRATEGY_NOT_PICKUP);
724                        sqllrConfig.setAllowGuessWhenAllInferred(false);
725                        sqlparser.setResolver2Config(sqllrConfig);
726                        sqlparser.setResolverType(gudusoft.gsqlparser.EResolverType.RESOLVER2);
727                }
728                // Every parse this analysis performs — the primary script, nested/dynamic SQL, cross-file
729                // units — runs under the analysis's own configuration. A single parser left on the vendor
730                // default would resolve one statement by different rules than the rest of the run.
731                //
732                // Matched against the parser's EFFECTIVE vendor, not the requested one: TGSqlParser maps
733                // dbvazuresql onto dbvmssql in its constructor, so those are not always the same value.
734                IdentifierProfile profile = identifierProfileFor(sqlparser.getDbVendor());
735                if (profile != null) {
736                        sqlparser.setIdentifierProfile(profile);
737                        return sqlparser;
738                }
739                IdentifierProfile stated = option != null ? option.getIdentifierProfile() : null;
740                if (stated != null && stated.getVendor() == vendor && sqlparser.getDbVendor() != vendor) {
741                        // The caller configured a vendor this parser renames. Applying the configuration to the
742                        // catalog and the lineage model but not to the parse would leave the analysis keying
743                        // names two ways, which is the half-migration that produces a confident wrong answer
744                        // with nothing to notice it. Say so instead.
745                        throw new IllegalStateException("DataFlowAnalyzer: an identifier profile for "
746                                        + vendor + " cannot configure the parse, because TGSqlParser analyzes " + vendor
747                                        + " as " + sqlparser.getDbVendor() + ". Build the profile for "
748                                        + sqlparser.getDbVendor() + " instead.");
749                }
750                return sqlparser;
751        }
752
753        /**
754         * The parser of the analysis's own SQL: {@link #createSqlParser}, recovering T-SQL routines whose body holds a
755         * statement it cannot parse when the option asks for it ({@link Option#isRecoverRoutineBodyErrors()}). The
756         * recovery stops there: SQL parsed on the way -- dynamic SQL, routines found elsewhere -- is parsed as written,
757         * so its diagnostics and its dynamic SQL outcome stay what they were.
758         */
759        private TGSqlParser createPrimarySqlParser(EDbVendor vendor) {
760                TGSqlParser sqlparser = createSqlParser(vendor);
761                sqlparser.setRecoverRoutineBodyErrors(option != null && option.isRecoverRoutineBodyErrors());
762                return sqlparser;
763        }
764
765        /**
766         * This analysis's configuration when it applies to {@code vendor}, else null.
767         *
768         * <p>Nested SQL can carry another dialect (Power Query inner queries, dynamic SQL analyzed as
769         * a different vendor). A profile belongs to one vendor, so it is not transplanted onto another
770         * one — that parse uses its own vendor's default rather than a mismatched configuration.
771         */
772        private IdentifierProfile identifierProfileFor(EDbVendor vendor) {
773                IdentifierProfile profile = frozenIdentifierProfile != null
774                                ? frozenIdentifierProfile
775                                : (option != null ? option.getIdentifierProfile() : null);
776                return profile != null && profile.getVendor() == vendor ? profile : null;
777        }
778
779        /**
780         * Choose this analysis's identifier configuration once, before any catalog write, parse or
781         * model key.
782         *
783         * <p>Three inputs can state it — the {@link Option}, a {@link TSQLEnv} handed in by the caller
784         * through {@link #setSqlEnv(TSQLEnv)}, and the vendor default. They are reconciled here rather
785         * than by each consumer: an environment's catalog is already indexed under its own profile, so
786         * an option that disagrees with it is a configuration error, not something to resolve by
787         * letting one side win silently.
788         */
789        private void freezeIdentifierProfile() {
790                IdentifierProfile fromOption = option != null ? option.getIdentifierProfile() : null;
791                if (fromOption != null && option.getVendor() != null
792                                && fromOption.getVendor() != option.getVendor()) {
793                        throw new IllegalStateException("DataFlowAnalyzer: identifier profile is for vendor "
794                                        + fromOption.getVendor() + " but the option's vendor is " + option.getVendor());
795                }
796                IdentifierProfile fromEnv = sqlenv != null ? sqlenv.getIdentifierProfile() : null;
797                if (fromOption != null && fromEnv != null && !fromOption.hasSameEffectivePolicyAs(fromEnv)) {
798                        throw new IllegalStateException("DataFlowAnalyzer: identifier configuration conflict -"
799                                        + " Option.setIdentifierProfile() says " + fromOption
800                                        + " but the TSQLEnv passed to setSqlEnv() was built with " + fromEnv
801                                        + ". A populated environment cannot be re-keyed; build the environment with"
802                                        + " the intended profile instead.");
803                }
804                IdentifierProfile resolved = fromOption != null ? fromOption : fromEnv;
805                if (resolved == null) {
806                        EDbVendor vendor = option != null ? option.getVendor() : null;
807                        if (vendor == null) {
808                                return;
809                        }
810                        resolved = IdentifierProfile.forVendor(vendor, IdentifierProfile.VendorFlags.defaults());
811                }
812                this.frozenIdentifierProfile = resolved;
813                this.frozenIdentifierService = IdentifierService.forProfile(resolved);
814        }
815
816        /**
817         * The frozen identifier service of this analysis, or null before
818         * {@link #freezeIdentifierProfile()} ran.
819         *
820         * @return the analysis's single comparison engine
821         */
822        public IdentifierService getIdentifierService() {
823                return frozenIdentifierService;
824        }
825
826        /**
827         * The frozen identifier configuration of this analysis, or null before it was resolved.
828         *
829         * @return the effective identifier profile of this run
830         */
831        public IdentifierProfile getIdentifierProfile() {
832                return frozenIdentifierProfile;
833        }
834
835        public synchronized String chechSyntax() {
836                StringBuilder builder = new StringBuilder();
837                if (sqlInfos != null) {
838                        for (SqlInfo sqlInfo : sqlInfos) {
839                                String content = sqlInfo.getSql();
840                                if (content != null && content.indexOf("<dlineage") != -1) {
841                                        try {
842                                                XML2Model.loadXML(dataflow.class, content);
843                                                continue;
844                                        } catch (Exception e) {
845                                                builder.append("Parsing dataflow ").append("occurs errors.\n").append(e.getMessage())
846                                                                .append("\n");
847                                        }
848                                }
849                                if (content != null && content.trim().startsWith("{")) {
850                                        Map queryObject = (Map) JSON.parseObject(content);
851                                        if (isSourceUnavailable(queryObject)) {
852                                                continue; // DDL body not retrieved; its text is not the definition
853                                        }
854                                        content = (String) queryObject.get("sourceCode");
855                                }
856                                if (MetadataReader.isMetadata(content)) {
857                                        continue;
858                                }
859                                TGSqlParser sqlparser = createPrimarySqlParser(option.getVendor());
860                                sqlparser.sqltext = content;
861                                int result = sqlparser.parse();
862                                if (result != 0) {
863                                        builder.append("Parsing sql ").append("occurs errors.\n").append(sqlparser.getErrormessage())
864                                                        .append("\n");
865                                }
866                                // statements routine recovery skipped are still syntax errors of this SQL
867                                for (TSyntaxError error : sqlparser.getRecoveredSyntaxErrors()) {
868                                        builder.append("Parsing sql occurs errors (").append(skippedStatement(sqlparser, error))
869                                                        .append(" skipped, rest of the routine analyzed).\n")
870                                                        .append(getErrorMessage(error, ErrorInfo.SYNTAX_ERROR)).append("\n");
871                                }
872                        }
873                }
874                return builder.toString();
875        }
876
877        public synchronized String generateDataFlow(boolean withExtraInfo) {
878                // Gated here as well as in TGSqlParser.parse(), which this reaches
879                // transitively: refusing at the API boundary gives the caller a clear
880                // failure instead of one surfacing from deep inside the analysis.
881                gudusoft.gsqlparser.runtime.GspRuntime.requireActivated();
882                authoritativeLineageEvidence = AuthoritativeLineageEvidence.empty();
883                if (option.isAutoDetectLargeFile()) {
884                        LargeFileDetector.LargeFileDetectionResult result = LargeFileDetector.detect(sqlInfos, originalFiles, option);
885                        if (result.shouldDelegate && delegateToParallel(result)) {
886                                return finishGenerateDataFlow(withExtraInfo);
887                        }
888                }
889
890                if (ModelBindingManager.get() == null) {
891                        ModelBindingManager.set(modelManager);
892                }
893
894                // Choose the configuration ONCE, then publish it for the whole run and put back whatever
895                // was there before. Save/restore rather than a bare set: an outer analysis on this thread
896                // (nested dlineage, a pooled worker) must find its own rules again, on the exception path
897                // as well as the normal one.
898                freezeIdentifierProfile();
899                IdentifierService previousService = ModelBindingManager.getGlobalIdentifierServiceRaw();
900                if (frozenIdentifierService != null) {
901                        ModelBindingManager.setGlobalIdentifierService(frozenIdentifierService);
902                }
903                try {
904                        initPipelinedFunctionAnalyzer();
905
906                        dataflow = analyzeSqlScript();
907
908                        return finishGenerateDataFlow(withExtraInfo);
909                } finally {
910                        ModelBindingManager.setGlobalIdentifierService(previousService);
911                }
912        }
913
914        private boolean delegateToParallel(LargeFileDetector.LargeFileDetectionResult result) {
915                ParallelDataFlowAnalyzer parallelAnalyzer = null;
916                //autogenerate 委托路径产生的拆分目录、gsp-input 临时文件及拆分产物统一在此清理
917                File splitDir = new File(System.getProperty("java.io.tmpdir"), "gsp-split-" + UUID.randomUUID().toString());
918                if (!splitDir.exists() && !splitDir.mkdirs()) {
919                        logger.error("Failed to create split directory: " + splitDir.getAbsolutePath());
920                        return false;
921                }
922                try {
923                        logger.info("Delegating to ParallelDataFlowAnalyzer: " + result.reason);
924
925                        parallelAnalyzer = buildParallelAnalyzer(splitDir);
926                        parallelAnalyzer.setSqlEnv(sqlenv);
927
928                        parallelAnalyzer.generateDataFlow(false, true);
929
930                        this.dataflow = parallelAnalyzer.getDataFlow();
931                        this.dataflowString = null;
932                        this.errorInfos.clear();
933                        this.errorInfos.addAll(parallelAnalyzer.getErrorMessages());
934                        this.sqlInfoMap = (IndexedLinkedHashMap<String, List<SqlInfo>>) parallelAnalyzer.getSqlInfos();
935
936                        return true;
937                } catch (Exception e) {
938                        logger.error("Delegation to ParallelDataFlowAnalyzer failed, falling back to single-threaded", e);
939                        return false;
940                } finally {
941                        //autogenerate dispose 异常不能阻塞 splitDir 清理,避免临时产物残留
942                        if (parallelAnalyzer != null) {
943                                try {
944                                        parallelAnalyzer.dispose();
945                                } catch (Exception ex) {
946                                        logger.error("Failed to dispose parallel analyzer", ex);
947                                }
948                        }
949                        //autogenerate 清理委托路径产生的拆分目录、gsp-input 临时文件及拆分产物,成功或失败均必须执行
950                        SQLUtil.deltree(splitDir);
951                }
952        }
953
954        private ParallelDataFlowAnalyzer buildParallelAnalyzer(File splitDir) {
955                if (originalFiles != null) {
956                        return new ParallelDataFlowAnalyzer(originalFiles, option,
957                                        option.getLargeFileSplitSizeMB(), splitDir);
958                }
959                // SqlInfo[] constructor path: collect files from all entries. Entries
960                // without a filePath (e.g., String/String[] constructors, or large
961                // raw SQL strings) are written to temp files so they can be split.
962                // All paths must go through split, otherwise ParallelDataFlowAnalyzer
963                // would create DataFlowAnalyzer instances internally which would
964                // detect the same large input and delegate again → infinite recursion.
965                if (sqlInfos != null && sqlInfos.length > 0) {
966                        List<File> files = new ArrayList<>();
967                        for (SqlInfo info : sqlInfos) {
968                                if (info == null) continue;
969                                File f = null;
970                                if (info.getFilePath() != null) {
971                                        f = new File(info.getFilePath());
972                                }
973                                if (f != null && f.exists()) {
974                                        if (!files.contains(f)) {
975                                                files.add(f);
976                                        }
977                                } else if (info.getSql() != null) {
978                                        try {
979                                                f = File.createTempFile("gsp-input-", ".tmp", splitDir);
980                                                try (FileWriter fw = new FileWriter(f)) {
981                                                        fw.write(info.getSql());
982                                                }
983                                                files.add(f);
984                                        } catch (Exception e) {
985                                                logger.error("Failed to create temp file for split", e);
986                                        }
987                                }
988                        }
989                        if (!files.isEmpty()) {
990                                return new ParallelDataFlowAnalyzer(files.toArray(new File[0]), option,
991                                                option.getLargeFileSplitSizeMB(), splitDir);
992                        }
993                }
994                return new ParallelDataFlowAnalyzer(sqlInfos, option);
995        }
996
997        private String finishGenerateDataFlow(boolean withExtraInfo) {
998                if (dataflow != null && frozenIdentifierProfile != null) {
999                        // Same-run provenance, so a later in-memory merge can tell whether two results were
1000                        // produced under the same rules instead of guessing from display names. Transient:
1001                        // it does not enter the XML, and it is gone after a round-trip (see dataflow).
1002                        dataflow.setIdentifierPolicy(frozenIdentifierProfile);
1003                }
1004                if (dataflow != null && !withExtraInfo && dataflow.getResultsets() != null) {
1005                        for (table t : dataflow.getResultsets()) {
1006                                t.setIsTarget(null);
1007                t.setProcedureId(null);
1008                                if (t.getColumns() != null) {
1009                                        for (column t1 : t.getColumns()) {
1010                                                t1.setIsFunction(null);
1011                                        }
1012                                }
1013                        }
1014                }
1015
1016        if (dataflow != null && !withExtraInfo && dataflow.getVariables() != null) {
1017            for (table t : dataflow.getVariables()) {
1018                t.setIsTarget(null);
1019                t.setProcedureId(null);
1020            }
1021        }
1022
1023                if(dataflow!=null && dataflow.getRelationships()!=null && option.getFilterRelationTypes()!=null && !option.getFilterRelationTypes().isEmpty()) {
1024                        List<relationship> relationships = new ArrayList<relationship>();
1025                        for(relationship relationship: dataflow.getRelationships()) {
1026                                if(option.getFilterRelationTypes().contains(relationship.getType())) {
1027                                        relationships.add(relationship);
1028                                }
1029                        }
1030                        dataflow.setRelationships(relationships);
1031                }
1032
1033                CommentBindingProjection.retain(dataflow);
1034        if (option.getHandleListener() != null) {
1035                        option.getHandleListener().endAnalyze(dataflow);
1036                }
1037
1038                if (option.isOutput()) {
1039                        if (option.getHandleListener() != null) {
1040                                option.getHandleListener().startOutputDataFlowXML();
1041                        }
1042                        if (dataflow != null) {
1043                                if (option.isTextFormat()) {
1044                                        dataflowString = getTextOutput(dataflow);
1045                                } else {
1046                                        try {
1047                                                dataflowString = XML2Model.saveXML(dataflow);
1048                                        }catch (Exception e){
1049                                                logger.error("Output dataflow to xml failed.", e);
1050                                                dataflowString = null;
1051                                        }
1052                                }
1053                        }
1054                        if (option.getHandleListener() != null) {
1055                                option.getHandleListener().endOutputDataFlowXML(dataflowString == null ? 0 : dataflowString.length());
1056                        }
1057                }
1058
1059                return dataflowString;
1060        }
1061
1062        private dataflow removeDuplicateColumns(dataflow dataflow) {
1063                List<table> tables = new ArrayList<table>();
1064                if (dataflow.getTables() != null) {
1065                        tables.addAll(dataflow.getTables());
1066                }
1067                if (dataflow.getViews() != null) {
1068                        tables.addAll(dataflow.getViews());
1069                }
1070                if (dataflow.getStages() != null) {
1071                        tables.addAll(dataflow.getStages());
1072                }
1073                if (dataflow.getDatasources() != null) {
1074                        tables.addAll(dataflow.getDatasources());
1075                }
1076                if (dataflow.getStreams() != null) {
1077                        tables.addAll(dataflow.getStreams());
1078                }
1079                if (dataflow.getPaths() != null) {
1080                        tables.addAll(dataflow.getPaths());
1081                }
1082                if (dataflow.getVariables() != null) {
1083                        tables.addAll(dataflow.getVariables());
1084                }
1085                if (dataflow.getResultsets() != null) {
1086                        tables.addAll(dataflow.getResultsets());
1087                }
1088                for (table table : tables) {
1089                        if (table.getColumns() == null) {
1090                                continue;
1091                        }
1092                        Set<String> columnIds = new HashSet<String>();
1093                        Iterator<column> iter = table.getColumns().iterator();
1094                        while(iter.hasNext()) {
1095                                column column = iter.next();
1096                                String id = column.getId();
1097                                if (columnIds.contains(id)) {
1098                                        iter.remove();
1099                                } else {
1100                                        columnIds.add(id);
1101                                }
1102                        }
1103                }
1104                return dataflow;
1105        }
1106
1107        public synchronized String generateDataFlow() {
1108                return generateDataFlow(false);
1109        }
1110
1111        public synchronized String generateSqlInfos() {
1112                return JSON.toJSONString(sqlInfoMap);
1113        }
1114
1115        public Map<String, List<SqlInfo>> getSqlInfos() {
1116                return sqlInfoMap;
1117        }
1118
1119        public Map getHashSQLMap() {
1120                return modelManager.getHashSQLMap();
1121        }
1122        
1123        public Map getDynamicSQLMap() {
1124                return modelManager.getDynamicSQLMap();
1125        }
1126
1127        /**
1128         * @deprecated please use SqlInfoHelper.getSelectedDbObjectInfo
1129         */
1130        public DbObjectPosition getSelectedDbObjectInfo(Coordinate start, Coordinate end) {
1131                if (start == null || end == null) {
1132                        throw new IllegalArgumentException("Coordinate can't be null.");
1133                }
1134
1135                String hashCode = start.getHashCode();
1136
1137                if (hashCode == null) {
1138                        throw new IllegalArgumentException("Coordinate hashcode can't be null.");
1139                }
1140
1141                int dbObjectStartLine = (int) start.getX() - 1;
1142                int dbObjectStarColumn = (int) start.getY() - 1;
1143                int dbObjectEndLine = (int) end.getX() - 1;
1144                int dbObjectEndColumn = (int) end.getY() - 1;
1145                List<SqlInfo> sqlInfoList;
1146                if (hashCode.matches("\\d+")) {
1147                        sqlInfoList = sqlInfoMap.getValueAtIndex(Integer.valueOf(hashCode));
1148                } else {
1149                        sqlInfoList = sqlInfoMap.get(hashCode);
1150                }
1151                for (int j = 0; j < sqlInfoList.size(); j++) {
1152                        SqlInfo sqlInfo = sqlInfoList.get(j);
1153                        int startLine = sqlInfo.getLineStart();
1154                        int endLine = sqlInfo.getLineEnd();
1155                        if (dbObjectStartLine >= startLine && dbObjectStartLine <= endLine) {
1156                                DbObjectPosition position = new DbObjectPosition();
1157                                position.setFile(sqlInfo.getFileName());
1158                                position.setFilePath(sqlInfo.getFilePath());
1159                                position.setSql(sqlInfo.getSql());
1160                                position.setIndex(sqlInfo.getOriginIndex());
1161                                List<Pair<Integer, Integer>> positions = position.getPositions();
1162                                positions.add(new Pair<Integer, Integer>(
1163                                                dbObjectStartLine - startLine + sqlInfo.getOriginLineStart() + 1, dbObjectStarColumn + 1));
1164                                positions.add(new Pair<Integer, Integer>(dbObjectEndLine - startLine + sqlInfo.getOriginLineStart() + 1,
1165                                                dbObjectEndColumn + 1));
1166                                return position;
1167                        }
1168                }
1169                return null;
1170        }
1171
1172        /**
1173         * Whether a serialized coordinate ("[line,col,offset],[line,col,offset]...")
1174         * points at a real reference site. A position synthesized for a column that
1175         * has no source token renders as "[1,1,0],[1,&lt;n&gt;,0]" (start-of-module,
1176         * width = name length) or carries -1 markers; a genuine column reference can
1177         * never start at line 1, column 1, because the statement head keyword
1178         * occupies that position.
1179         */
1180        private static boolean hasUsableCoordinate(String coordinate) {
1181                return DlineageUtil.hasUsableCoordinate(coordinate);
1182        }
1183
1184        public static dataflow mergeTables(dataflow dataflow, Long startId) {
1185                return mergeTables(dataflow, startId, new Option());
1186        }
1187
1188        public static dataflow mergeTables(dataflow dataflow, Long startId, Option option) {
1189                List<table> tableCopy = new ArrayList<table>();
1190                List<table> viewCopy = new ArrayList<table>();
1191                List<table> databaseCopy = new ArrayList<table>();
1192                List<table> schemaCopy = new ArrayList<table>();
1193                List<table> stageCopy = new ArrayList<table>();
1194                List<table> dataSourceCopy = new ArrayList<table>();
1195                List<table> streamCopy = new ArrayList<table>();
1196                List<table> fileCopy = new ArrayList<table>();
1197                List<table> variableCopy = new ArrayList<table>();
1198                List<table> cursorCopy = new ArrayList<table>();
1199                List<table> resultSetCopy = new ArrayList<table>();
1200                if (dataflow.getTables() != null) {
1201                        tableCopy.addAll(dataflow.getTables());
1202                }
1203                dataflow.setTables(tableCopy);
1204                if (dataflow.getViews() != null) {
1205                        viewCopy.addAll(dataflow.getViews());
1206                }
1207                dataflow.setViews(viewCopy);
1208                if (dataflow.getDatabases() != null) {
1209                        databaseCopy.addAll(dataflow.getDatabases());
1210                }
1211                dataflow.setDatabases(databaseCopy);
1212                if (dataflow.getSchemas() != null) {
1213                        schemaCopy.addAll(dataflow.getSchemas());
1214                }
1215                dataflow.setSchemas(schemaCopy);
1216                if (dataflow.getStages() != null) {
1217                        stageCopy.addAll(dataflow.getStages());
1218                }
1219                dataflow.setStages(stageCopy);
1220                if (dataflow.getDatasources() != null) {
1221                        dataSourceCopy.addAll(dataflow.getDatasources());
1222                }
1223                dataflow.setDatasources(dataSourceCopy);
1224                if (dataflow.getStreams() != null) {
1225                        streamCopy.addAll(dataflow.getStreams());
1226                }
1227                dataflow.setStreams(streamCopy);
1228                if (dataflow.getPaths() != null) {
1229                        fileCopy.addAll(dataflow.getPaths());
1230                }
1231                dataflow.setPaths(fileCopy);
1232                if (dataflow.getVariables() != null) {
1233                        variableCopy.addAll(dataflow.getVariables());
1234                }
1235                dataflow.setVariables(variableCopy);
1236                if (dataflow.getResultsets() != null) {
1237                        resultSetCopy.addAll(dataflow.getResultsets());
1238                }
1239                dataflow.setResultsets(resultSetCopy);
1240
1241                Map<Object, List<table>> tableMap = new HashMap<Object, List<table>>();
1242                Map<Object, String> tableTypeMap = new HashMap<Object, String>();
1243                Map<String, TMssqlCreateType> mssqlTypeMap = new HashMap<String, TMssqlCreateType>();
1244                Map<String, String> tableIdMap = new HashMap<String, String>();
1245
1246                Map<Object, List<column>> columnMap = new HashMap<Object, List<column>>();
1247                Map<Object, Set<Object>> tableColumnMap = new HashMap<Object, Set<Object>>();
1248                Map<String, String> columnIdMap = new HashMap<String, String>();
1249                Map<String, column> columnMergeIdMap = new HashMap<String, column>();
1250                // columns of one table renamed apart because their names collide across identities, by column id
1251                Map<String, String> renamedColumns = new HashMap<String, String>();
1252
1253                List<table> tables = new ArrayList<table>();
1254                tables.addAll(dataflow.getTables());
1255                tables.addAll(dataflow.getViews());
1256                tables.addAll(dataflow.getDatabases());
1257                tables.addAll(dataflow.getSchemas());
1258                tables.addAll(dataflow.getStages());
1259                tables.addAll(dataflow.getDatasources());
1260                tables.addAll(dataflow.getStreams());
1261                tables.addAll(dataflow.getPaths());
1262                tables.addAll(dataflow.getResultsets());
1263                tables.addAll(dataflow.getVariables());
1264                
1265                Set<String> columnIds = new HashSet<String>();
1266
1267                // Physical tables and views that share a legacy folded name but are different tables under
1268                // the analysis's identifier rules must not merge into one output table.
1269                Set<table> physicalTables = Collections.newSetFromMap(new IdentityHashMap<table, Boolean>());
1270                physicalTables.addAll(dataflow.getTables());
1271                physicalTables.addAll(dataflow.getViews());
1272                Map<table, String> legacyTableKeys = new IdentityHashMap<table, String>();
1273                for (table table : tables) {
1274                        legacyTableKeys.put(table, LegacyDlineageWireNamesV1.tableBucket(DlineageUtil.getQualifiedTableName(table)));
1275                }
1276                // Decided per legacy bucket once every table of the merge is known, so the result does not
1277                // depend on the order the tables arrive in.
1278                PhysicalTableMergeKeys mergeKeys = new PhysicalTableMergeKeys(DlineageUtil.configuresIdentifierRules());
1279                for (table table : tables) {
1280                        if (table.getIdentifierKey() == null && physicalTables.contains(table)) {
1281                                String qualifiedTableName = DlineageUtil.getQualifiedTableName(table);
1282                                mergeKeys.addTable(table, legacyTableKeys.get(table), qualifiedTableName);
1283                        }
1284                }
1285
1286                for (table table : tables) {
1287                        String qualifiedTableName = DlineageUtil.getQualifiedTableName(table);
1288                        Object tableFullName = table.getIdentifierKey() != null ? table.getIdentifierKey()
1289                                        : legacyTableKeys.get(table);
1290                        if (table.getIdentifierKey() == null && physicalTables.contains(table)) {
1291                                tableFullName = mergeKeys.keyOf((String) tableFullName,
1292                                                PhysicalTableMergeKeys.carriedIdentity(table, (String) tableFullName, qualifiedTableName),
1293                                                qualifiedTableName);
1294                        }
1295                        if ("variable".endsWith(table.getType()) && !SQLUtil.isEmpty(table.getParent())) {
1296                                tableFullName = table.getParent() + "." + tableFullName;
1297                        }
1298                        if (table.isScopedTemporary()) {
1299                                // One routine body's temporary table is not the same table as another of its name.
1300                                tableFullName = Arrays.asList("scoped temporary table", table.getId());
1301                        }
1302                        if (table.isArrayRelation()) {
1303                                // A LATERAL VIEW's relation is not the table of the script that has its name (Mantis 4890).
1304                                tableFullName = Arrays.asList("array relation", table.getId());
1305                        }
1306                        if (table.isFile() && (table.getDataSourceName() != null || table.getEndpointKind() != null)) {
1307                                // A file a BULK INSERT names relative to an external data source, or a runtime template, is not
1308                                // the file of its name elsewhere (Mantis 4962).
1309                                tableFullName = Arrays.asList("path", tableFullName, table.getUri(), table.getDataSourceName(),
1310                                                table.getEndpointKind());
1311                        }
1312
1313                        if (!tableMap.containsKey(tableFullName)) {
1314                                tableMap.put(tableFullName, new ArrayList<table>());
1315                        }
1316
1317                        tableMap.get(tableFullName).add(table);
1318
1319                        if (!tableTypeMap.containsKey(tableFullName)) {
1320                                tableTypeMap.put(tableFullName, table.getType());
1321                        } else if ("view".equals(table.getSubType())) {
1322                                tableTypeMap.put(tableFullName, table.getType());
1323                        } else if ("database".equals(table.getSubType())) {
1324                                tableTypeMap.put(tableFullName, table.getType());
1325                        } else if ("schema".equals(table.getSubType())) {
1326                                tableTypeMap.put(tableFullName, table.getType());
1327                        } else if ("stage".equals(table.getSubType())) {
1328                                tableTypeMap.put(tableFullName, table.getType());
1329                        } else if ("sequence".equals(table.getSubType())) {
1330                                tableTypeMap.put(tableFullName, table.getType());
1331                        } else if ("datasource".equals(table.getSubType())) {
1332                                tableTypeMap.put(tableFullName, table.getType());
1333                        } else if ("stream".equals(table.getSubType())) {
1334                                tableTypeMap.put(tableFullName, table.getType());
1335                        } else if ("file".equals(table.getSubType())) {
1336                                tableTypeMap.put(tableFullName, table.getType());
1337                        } else if ("table".equals(tableTypeMap.get(tableFullName))) {
1338                                tableTypeMap.put(tableFullName, table.getType());
1339                        } else if ("variable".equals(tableTypeMap.get(tableFullName))) {
1340                                tableTypeMap.put(tableFullName, table.getType());
1341                        }
1342
1343                        if (table.getColumns() != null) {
1344                                if (!tableColumnMap.containsKey(tableFullName)) {
1345                                        tableColumnMap.put(tableFullName, new LinkedHashSet<Object>());
1346                                }
1347                                for (column column : table.getColumns()) {
1348                                        String columnSuffix = (column.getQualifiedTable() != null
1349                                                                        ? (LegacyDlineageWireNamesV1.tableBucket(column.getQualifiedTable()) + ".")
1350                                                                        : "")
1351                                                        + ("false".equals(table.getIsTarget()) ? LegacyDlineageWireNamesV1.columnSegmentBucket(column.getName())
1352                                                                        : LegacyDlineageWireNamesV1.columnBucket(column.getName()));
1353                                        Object columnFullName = table.getIdentifierKey() != null
1354                                                        ? java.util.Arrays.asList(tableFullName, columnSuffix) : tableFullName + "." + columnSuffix;
1355                                        if (column.getIdentifierKey() != null) {
1356                                                columnFullName = java.util.Arrays.asList(tableFullName, column.getQualifiedTable(),
1357                                                                column.getIdentifierKey());
1358                                        } else if (column.getFieldPathKey() != null) {
1359                                                // a STRUCT field path node, by its declared column and path (plan section 4.7)
1360                                                columnFullName = java.util.Arrays.asList(tableFullName, column.getQualifiedTable(),
1361                                                                column.getFieldPathKey());
1362                                        }
1363
1364                                        if (!columnMap.containsKey(columnFullName)) {
1365                                                columnMap.put(columnFullName, new ArrayList<column>());
1366                                                tableColumnMap.get(tableFullName).add(columnFullName);
1367                                        }
1368
1369                                        columnMap.get(columnFullName).add(column);
1370                                        columnIds.add(column.getId());
1371                                }
1372                        }
1373                }
1374
1375                Set<String> relationParentIds = new HashSet<String>();
1376                if (dataflow.getRelationships() != null) {
1377                        for (relationship rel : dataflow.getRelationships()) {
1378                                if (rel.getSources() != null) {
1379                                        for (sourceColumn src : rel.getSources()) {
1380                                                if (src.getParent_id() != null) {
1381                                                        relationParentIds.add(src.getParent_id());
1382                                                }
1383                                        }
1384                                }
1385                                if (rel.getTarget() != null) {
1386                                        if (rel.getTarget().getParent_id() != null) {
1387                                                relationParentIds.add(rel.getTarget().getParent_id());
1388                                        }
1389                                }
1390                        }
1391                }
1392
1393                Iterator<Object> tableNameIter = tableMap.keySet().iterator();
1394                while (tableNameIter.hasNext()) {
1395                        Object tableName = tableNameIter.next();
1396                        List<table> tableList = tableMap.get(tableName);
1397                        table table;
1398                        if (tableList.size() > 1) {
1399                                DataflowUtility.GroupNames chosen = DataflowUtility.groupNames(tableList);
1400                                table standardTable = chosen.displayed;
1401                                // Function允许重名,不做合并处理
1402                                if (standardTable.isFunction()) {
1403                                        continue;
1404                                }
1405                                
1406                                // Variable允许重名,不做合并处理
1407                                if (standardTable.isVariable()) {
1408                                        continue;
1409                                }
1410                                
1411                                // 临时表不做合并处理
1412                                if(SQLUtil.isTempTable(standardTable)) {
1413                                        continue;
1414                                }
1415
1416                                String type = tableTypeMap.get(tableName);
1417                                table = new table();
1418                                table.setId(String.valueOf(++startId));
1419                                table names = chosen.names;
1420                                table.setServer(names.getServer());
1421                                table.setDatabase(names.getDatabase());
1422                                table.setSchema(names.getSchema());
1423                                table.setName(names.getName());
1424                                table.setIdentifierKey(standardTable.getIdentifierKey());
1425                                DataflowUtility.carryGroupIdentity(table, tableList, chosen);
1426                                table.setDisplayName(names.getDisplayName());
1427                                table.setParent(standardTable.getParent());
1428                                table.setMore(standardTable.getMore());
1429                                if (standardTable.getCandidateTables() != null && !standardTable.getCandidateTables().isEmpty()) {
1430                                        table.setCandidateTables(new ArrayList<String>(standardTable.getCandidateTables()));
1431                                }
1432                                table.setColumns(new ArrayList<column>());
1433                                String subType = null;
1434                                for(table item: tableList){
1435                                        if (item.getSubType() != null) {
1436                                                subType = item.getSubType();
1437                                                break;
1438                                        }
1439                                }
1440                                if (subType != null) {
1441                                        table.setSubType(subType);
1442                                } else {
1443                                        table.setSubType(standardTable.getSubType());
1444                                }
1445                                Set<String> processIds = new LinkedHashSet<String>();
1446                                for (int k = 0; k < tableList.size(); k++) {
1447                                        if (tableList.get(k).getProcessIds() != null) {
1448                                                processIds.addAll(tableList.get(k).getProcessIds());
1449                                        }
1450                                }
1451                                if (!processIds.isEmpty()) {
1452                                        table.setProcessIds(new ArrayList<String>(processIds));
1453                                }
1454                                Set<String> alias = new LinkedHashSet<String>();
1455                                for (int k = 0; k < tableList.size(); k++) {
1456                                        if (tableList.get(k).getAlias() != null) {
1457                                                alias.addAll(Arrays.asList(tableList.get(k).getAlias().split("\\s*,\\s*")));
1458                                        }
1459                                }
1460                                if (!alias.isEmpty()) {
1461                                        String aliasString = Arrays.toString(alias.toArray(new String[0]));
1462                                        table.setAlias(aliasString.substring(1, aliasString.length() - 1));
1463                                }
1464                                table.setType(type);
1465                                if (tableName instanceof List && "path".equals(((List<?>) tableName).get(0))) {
1466                                        // one file under one identity (above): what tells it apart stays on it
1467                                        table.setUri(standardTable.getUri());
1468                                        table.setDataSourceName(standardTable.getDataSourceName());
1469                                        table.setEndpointKind(standardTable.getEndpointKind());
1470                                        table.setTemplatedParts(standardTable.getTemplatedParts());
1471                                }
1472                                for (table item : tableList) {
1473                                        if (!SQLUtil.isEmpty(table.getCoordinate()) && !SQLUtil.isEmpty(item.getCoordinate())) {
1474                                                if (table.getCoordinate().indexOf(item.getCoordinate()) == -1) {
1475                                                        table.appendCoordinate(item.getCoordinate());
1476                                                }
1477                                        } else if (!SQLUtil.isEmpty(item.getCoordinate())) {
1478                                                table.setCoordinate(item.getCoordinate());
1479                                        }
1480
1481                                        if (item.getStarStmt() != null) {
1482                                                table.setStarStmt(item.getStarStmt());
1483                                        }
1484
1485                                        tableIdMap.put(item.getId(), table.getId());
1486
1487                                        if (item.isView()) {
1488                                                dataflow.getViews().remove(item);
1489                                        } else if (item.isDatabaseType()) {
1490                                                dataflow.getDatabases().remove(item);
1491                                        } else if (item.isSchemaType()) {
1492                                                dataflow.getSchemas().remove(item);
1493                                        } else if (item.isStage()) {
1494                                                dataflow.getStages().remove(item);
1495                                        } else if (item.isDataSource()) {
1496                                                dataflow.getDatasources().remove(item);
1497                                        } else if (item.isStream()) {
1498                                                dataflow.getStreams().remove(item);
1499                                        } else if (item.isFile()) {
1500                                                dataflow.getPaths().remove(item);
1501                                        } else if (item.isVariable()) {
1502                                                dataflow.getVariables().remove(item);
1503                                        } else if (item.isTable()) {
1504                                                dataflow.getTables().remove(item);
1505                                        } else if (item.isResultSet()) {
1506                                                dataflow.getResultsets().remove(item);
1507                                        }
1508                                }
1509
1510                                if (table.isView()) {
1511                                        dataflow.getViews().add(table);
1512                                } else if (table.isDatabaseType()) {
1513                                        dataflow.getDatabases().add(table);
1514                                } else if (table.isSchemaType()) {
1515                                        dataflow.getSchemas().add(table);
1516                                } else if (table.isStage()) {
1517                                        dataflow.getStages().add(table);
1518                                } else if (table.isDataSource()) {
1519                                        dataflow.getDatasources().add(table);
1520                                } else if (table.isStream()) {
1521                                        dataflow.getStreams().add(table);
1522                                } else if (table.isFile()) {
1523                                        dataflow.getPaths().add(table);
1524                                } else if (table.isVariable()) {
1525                                        dataflow.getVariables().add(table);
1526                                } else if (table.isResultSet()) {
1527                                        dataflow.getResultsets().add(table);
1528                                } else {
1529                                        dataflow.getTables().add(table);
1530                                }
1531                        } else {
1532                                table = tableList.get(0);
1533                                if(Boolean.TRUE.toString().equals(table.getIsDetermined())){
1534                                        continue;
1535                                }
1536                                
1537                                if (option.isIgnoreUnusedSynonym() && SubType.synonym.name().equals(table.getSubType())) {
1538                                        boolean hasSourceRelation = relationParentIds.contains(table.getId());
1539                                        if (!hasSourceRelation) {
1540                                                dataflow.getTables().remove(table);
1541                                                tableColumnMap.get(tableName).clear();
1542                                                continue;
1543                                        }
1544                                }
1545                        }
1546
1547                        Set<Object> columns = tableColumnMap.get(tableName);
1548                        Iterator<Object> columnIter = columns.iterator();
1549                        List<column> mergeColumns = new ArrayList<column>();
1550                        while (columnIter.hasNext()) {
1551                                Object columnName = columnIter.next();
1552                                List<column> columnList = columnMap.get(columnName);
1553                                List<column> functions = new ArrayList<column>();
1554                                for (column t : columnList) {
1555                                        if (Boolean.TRUE.toString().equals(t.getIsFunction())) {
1556                                                functions.add(t);
1557                                        }
1558                                }
1559                                if (functions != null && !functions.isEmpty()) {
1560                                        for (column function : functions) {
1561                                                mergeColumns.add(function);
1562                                                columnIdMap.put(function.getId(), function.getId());
1563                                                columnMergeIdMap.put(function.getId(), function);
1564                                        }
1565
1566                                        columnList.removeAll(functions);
1567                                }
1568                                if (!columnList.isEmpty()) {
1569                                        column firstColumn = columnList.iterator().next();
1570                                        if (columnList.size() > 1) {
1571                                                column mergeColumn = new column();
1572                                                mergeColumn.setId(String.valueOf(++startId));
1573                                                mergeColumn.setName(firstColumn.getName());
1574                                                mergeColumn.setStar(firstColumn.getStar());
1575                                                mergeColumn.setIdentifierKey(firstColumn.getIdentifierKey());
1576                                                mergeColumn.setFieldPathKey(firstColumn.getFieldPathKey());
1577                                                mergeColumn.setSqlSpelling(DataflowUtility.sqlSpellingOf(columnList));
1578                                                mergeColumn.setDisplayName(firstColumn.getDisplayName());
1579                                                mergeColumn.setSource(firstColumn.getSource());
1580                                                mergeColumn.setQualifiedTable(firstColumn.getQualifiedTable());
1581                                                mergeColumn.setDataType(firstColumn.getDataType());
1582                                                mergeColumn.setForeignKey(firstColumn.isForeignKey());
1583                                                mergeColumn.setPrimaryKey(firstColumn.isPrimaryKey());
1584                                                mergeColumn.setUnqiueKey(firstColumn.isUnqiueKey());
1585                                                mergeColumn.setIndexKey(firstColumn.isIndexKey());
1586                                                mergeColumns.add(mergeColumn);
1587                                                for (column item : columnList) {
1588                                                        mergeColumn.appendCoordinate(item.getCoordinate());
1589                                                        columnIdMap.put(item.getId(), mergeColumn.getId());
1590                                                }
1591                                                columnMergeIdMap.put(mergeColumn.getId(), mergeColumn);
1592                                                columnIds.add(mergeColumn.getId());
1593                                        } else {
1594                                                mergeColumns.add(firstColumn);
1595                                                columnIdMap.put(firstColumn.getId(), firstColumn.getId());
1596                                                columnMergeIdMap.put(firstColumn.getId(), firstColumn);
1597                                        }
1598                                }
1599                        }
1600                        table.setColumns(mergeColumns);
1601                        if (Column.starLinksGroupedByIdentity()) {
1602                                renamedColumns.putAll(DataflowUtility.nameCollidingIdentities(mergeColumns,
1603                                                option.getVendor() != null ? option.getVendor() : ModelBindingManager.getGlobalVendor()));
1604                        }
1605                }
1606
1607                Map<String, String> commentRelationIds = dataflow.getCommentBindings() == null ? null : new HashMap<String, String>();
1608        if (dataflow.getRelationships() != null) {
1609                        Map<Long, relationship> mergeRelations = new LinkedHashMap<Long, relationship>();
1610                        // A synonym relationship states a catalog fact between two columns, and two producers can state the
1611                        // same one (the metadata reader from the synonym list, the catalog synonym materializer for a synonym
1612                        // the SQL reads). Once the merged ids decide the columns, a repeat of the same columns is one fact;
1613                        // the relationship's display names and coordinates, which the two producers spell differently, do not
1614                        // make it another.
1615                        Map<List<String>, relationship> synonymRelations = new HashMap<List<String>, relationship>();
1616                        for (int i = 0; i < dataflow.getRelationships().size(); i++) {
1617                                relationship relation = dataflow.getRelationships().get(i);
1618                                
1619                                if("crud".equals(relation.getType()) && option.getAnalyzeMode() == AnalyzeMode.crud) {
1620                                        long key = relation.toDedupHash();
1621                                        if (!mergeRelations.containsKey(key)) {
1622                                                mergeRelations.put(key, relation);
1623                                        }
1624                                        continue;
1625                                }
1626                                
1627                                targetColumn target = relation.getTarget();
1628                                if ("call".equals(relation.getType())) {
1629                                        target = relation.getCaller();
1630                                }
1631                                if (target != null && tableIdMap.containsKey(target.getParent_id())) {
1632                                        target.setParent_id(tableIdMap.get(target.getParent_id()));
1633                                }
1634
1635                                if (columnIdMap.containsKey(target.getId())) {
1636                                        target.setId(columnIdMap.get(target.getId()));
1637                                        target.setCoordinate(columnMergeIdMap.get(target.getId()).getCoordinate());
1638                                }
1639                                else if(option.isIgnoreUnusedSynonym() && EffectType.synonym.name().equals(relation.getEffectType())){
1640                                        continue;
1641                                }
1642                                
1643                                if (!"call".equals(relation.getType()) && !columnIds.contains(target.getId())) {
1644                                        continue;
1645                                }
1646
1647                                List<sourceColumn> sources = relation.getSources();
1648                                if ("call".equals(relation.getType())) {
1649                                        sources = relation.getCallees();
1650                                }
1651                                // Sources of merged columns keep their own reference-site
1652                                // coordinate (the ON/WHERE operand span) instead of the merged
1653                                // column's stamped definition coordinate. Source and relation
1654                                // dedup, however, must still collapse EXACTLY as they did under
1655                                // the stamped coordinates — sourceColumn.equals and
1656                                // relationship.toDedupHash both include the coordinate — so the
1657                                // stamped coordinate is applied for the dedup steps and the
1658                                // reference-site coordinate is restored on the survivors below.
1659                                Map<sourceColumn, String> referenceCoordinates = new IdentityHashMap<sourceColumn, String>();
1660                                Set<sourceColumn> sourceSet = new LinkedHashSet<sourceColumn>();
1661                                if (sources != null) {
1662                                        for (sourceColumn source : sources) {
1663                                                // Reference-published source of an UNMERGED column: stamp
1664                                                // the definition span it carried before reference-site
1665                                                // publishing, so dedup keeps its legacy behavior.
1666                                                String dedupCoordinate = source.getDedupCoordinate();
1667                                                if (dedupCoordinate != null && !dedupCoordinate.equals(source.getCoordinate())) {
1668                                                        referenceCoordinates.put(source, source.getCoordinate());
1669                                                        source.setCoordinate(dedupCoordinate);
1670                                                }
1671                                                if (!"call".equals(relation.getType()) && !columnIds.contains(source.getId())) {
1672                                                        continue;
1673                                                }
1674                                                if (tableIdMap.containsKey(source.getParent_id())) {
1675                                                        source.setParent_id(tableIdMap.get(source.getParent_id()));
1676                                                }
1677                                                if (tableIdMap.containsKey(source.getSource_id())) {
1678                                                        source.setSource_id(tableIdMap.get(source.getSource_id()));
1679                                                }
1680                                                if (columnIdMap.containsKey(source.getId())) {
1681                                                        String ownCoordinate = referenceCoordinates.containsKey(source)
1682                                                                        ? referenceCoordinates.get(source)
1683                                                                        : source.getCoordinate();
1684                                                        source.setId(columnIdMap.get(source.getId()));
1685                                                        source.setCoordinate(columnMergeIdMap.get(source.getId()).getCoordinate());
1686                                                        if (hasUsableCoordinate(ownCoordinate)) {
1687                                                                // The operand's own coordinate survives the merge;
1688                                                                // only a synthetic/absent one is replaced by the
1689                                                                // merged column's definition coordinate for good.
1690                                                                referenceCoordinates.put(source, ownCoordinate);
1691                                                        } else {
1692                                                                referenceCoordinates.remove(source);
1693                                                        }
1694                                                }
1695                                        }
1696
1697                                        sourceSet.addAll(sources);
1698                                        if ("call".equals(relation.getType())) {
1699                                                relation.setCallees(new ArrayList<sourceColumn>(sourceSet));
1700                                        } else {
1701                                                relation.setSources(new ArrayList<sourceColumn>(sourceSet));
1702                                        }
1703                                }
1704
1705                                if (EffectType.synonym.name().equals(relation.getEffectType()) && target != null
1706                                                && relation.getSources() != null) {
1707                                        List<String> synonymKey = new ArrayList<String>();
1708                                        synonymKey.add(target.getId());
1709                                        List<String> sourceIds = new ArrayList<String>();
1710                                        for (sourceColumn source : relation.getSources()) {
1711                                                sourceIds.add(source.getId());
1712                                        }
1713                                        Collections.sort(sourceIds, Comparator.nullsFirst(Comparator.<String>naturalOrder()));
1714                                        synonymKey.addAll(sourceIds);
1715                                        relationship stated = synonymRelations.get(synonymKey);
1716                                        if (stated != null) {
1717                                                if (commentRelationIds != null) commentRelationIds.put(relation.getId(), stated.getId());
1718                                                continue;
1719                                        }
1720                                        synonymRelations.put(synonymKey, relation);
1721                                }
1722
1723                                long key = relation.toDedupHash();
1724                                if (!mergeRelations.containsKey(key)) {
1725                                        mergeRelations.put(key, relation);
1726                                        // Restore the reference-site coordinates on the surviving
1727                                        // relation's sources, now that dedup ran under the stamped
1728                                        // legacy coordinates.
1729                                        for (Map.Entry<sourceColumn, String> entry : referenceCoordinates.entrySet()) {
1730                                                entry.getKey().setCoordinate(entry.getValue());
1731                                        }
1732                                }
1733                if (commentRelationIds != null) commentRelationIds.put(relation.getId(), mergeRelations.get(key).getId());
1734                        }
1735
1736                        dataflow.setRelationships(new ArrayList<relationship>(mergeRelations.values()));
1737                }
1738
1739        if (commentRelationIds != null) CommentBindingProjection.remap(dataflow, tableIdMap, columnIdMap, commentRelationIds);
1740                DataflowUtility.renameEndpoints(dataflow.getRelationships(), renamedColumns);
1741
1742                tableMap.clear();
1743                tableTypeMap.clear();
1744                tableIdMap.clear();
1745                columnMap.clear();
1746                tableColumnMap.clear();
1747                columnIdMap.clear();
1748                columnMergeIdMap.clear();
1749                tables.clear();
1750                return dataflow;
1751        }
1752
1753        public synchronized dataflow getDataFlow() {
1754                if (dataflow != null) {
1755                        return dataflow;
1756                } else if (dataflowString != null) {
1757                        return XML2Model.loadXML(dataflow.class, dataflowString);
1758                }
1759                return null;
1760        }
1761
1762        /**
1763         * Returns immutable AST/catalog evidence captured during this analyzer run.
1764         * The evidence is correlated to {@link #getDataFlow()} by document-local IDs
1765         * and is intentionally absent from the legacy serialized contract.
1766         */
1767        @Override
1768        public synchronized AuthoritativeLineageEvidence getAuthoritativeLineageEvidence() {
1769                return authoritativeLineageEvidence;
1770        }
1771
1772        /**
1773         * Binds a SQL-LR observation recorder to this analysis, before {@link #generateDataFlow()}. Internal API of the
1774         * SQL-LR producer ({@code gudusoft.gsqlparser.lineage.v2.producer}). An observe-only recorder never changes the
1775         * legacy output; a producer-mode recorder ({@code new SqllrObservations(true)}) turns the run into SQL-LR
1776         * production and is made only by the producer.
1777         *
1778         * @since 4.2.17
1779         */
1780        public synchronized void setSqllrObservations(SqllrObservations observations) {
1781                modelManager.setSqllrObservations(observations);
1782        }
1783
1784        /** The SQL-LR observation recorder bound to this analysis, or null. Internal API of the SQL-LR producer. */
1785        public synchronized SqllrObservations getSqllrObservations() {
1786                return modelManager.getSqllrObservations();
1787        }
1788
1789        /** Whether this analysis is the SQL-LR producer's own (an observe-only recorder changes nothing). */
1790        private boolean sqllrProducerMode() {
1791                SqllrObservations observations = modelManager.getSqllrObservations();
1792                return observations != null && observations.isProducerMode();
1793        }
1794
1795        List<ErrorInfo> metadataErrors = new ArrayList<>();
1796        
1797        private synchronized dataflow analyzeSqlScript() {
1798                init();
1799
1800                try {
1801                        dataflow dataflow = new dataflow();
1802            List<CommentBindingBundle> importedCommentBundles = null;
1803                        
1804                        if (sqlInfos != null) {
1805                                if (option.getHandleListener() != null) {
1806                                        if (sqlInfos.length == 1) {
1807                                                int sqlLength = sqlInfos[0].getSql() != null ? sqlInfos[0].getSql().length() : 0;
1808                                                option.getHandleListener().startAnalyze(null, sqlLength, false);
1809                                        } else {
1810                                                option.getHandleListener().startAnalyze(null, sqlInfos.length, true);
1811                                        }
1812                                }
1813
1814                                if (sqlenv == null) {
1815                                        if (option.getHandleListener() != null) {
1816                                                option.getHandleListener().startParseSQLEnv();
1817                                        }
1818                                        TSQLEnv[] sqlenvs = new SQLEnvParser(option.getDefaultServer(), option.getDefaultDatabase(),
1819                                                        option.getDefaultSchema())
1820                                                        .withIdentifierProfile(frozenIdentifierProfile)
1821                                                        .parseSQLEnv(option.getVendor(), sqlInfos);
1822                                        if (sqlenvs != null && sqlenvs.length > 0) {
1823                                                sqlenv = sqlenvs[0];
1824                                        }
1825                                        if (option.getHandleListener() != null) {
1826                                                option.getHandleListener().endParseSQLEnv();
1827                                        }
1828                                }
1829                                TGSqlParser sqlparser = createPrimarySqlParser(option.getVendor());
1830                                Map<String, Pair3<StringBuilder, AtomicInteger, String>> databaseMap = new LinkedHashMap<String, Pair3<StringBuilder, AtomicInteger, String>>();
1831                                groupNameForms.clear();
1832                                for (int i = 0; i < sqlInfos.length; i++) {
1833                                        SqlInfo sqlInfo = sqlInfos[i];
1834                                        if (sqlInfo == null) {
1835                                                sqlInfoMap.put(String.valueOf(i), new ArrayList<SqlInfo>());
1836                                                continue;
1837                                        }
1838                                        String sql = sqlInfo.getSql();
1839                                        if (SQLUtil.isEmpty(sql) && sqlInfo.getFileName() != null
1840                                                        && new File(sqlInfo.getFileName()).exists()) {
1841                                                sql = SQLUtil.getFileContent(sqlInfo.getFileName());
1842                                        }
1843                                        if (SQLUtil.isEmpty(sql) && sqlInfo.getFilePath() != null
1844                                                        && new File(sqlInfo.getFilePath()).exists()) {
1845                                                sql = SQLUtil.getFileContent(sqlInfo.getFilePath());
1846                                        }
1847                                        String sqlTrim = null;
1848                                        if (sql != null) {
1849                                                sqlTrim = sql.substring(0, Math.min(sql.length(), 512)).trim();
1850                                        }
1851                                        if(sql!=null && sqlTrim.startsWith("<") && sqlTrim.indexOf("<dlineage")!=-1){
1852                                                dataflow temp = XML2Model.loadXML(dataflow.class, sql);
1853                        if (commentBindingCollector != null && temp.getCommentBindings() == null) commentBindingCollector.sourceUnavailable();
1854                                                if(sqlInfos.length == 1){
1855                                                        dataflow = temp;
1856                                                }
1857                                                else {
1858                            if (temp.getCommentBindings() != null) {
1859                                if (importedCommentBundles == null) importedCommentBundles = new ArrayList<CommentBindingBundle>();
1860                                // This legacy append does not offset input IDs. Keep source facts without guessing targets.
1861                                importedCommentBundles.add(CommentBindingProjection.sourceOnly(temp.getCommentBindings()));
1862                            }
1863                                                        if (temp.getTables() != null) {
1864                                                                dataflow.getTables().addAll(temp.getTables());
1865                                                        }
1866                                                        if (temp.getViews() != null) {
1867                                                                dataflow.getViews().addAll(temp.getViews());
1868                                                        }
1869                                                        if (temp.getResultsets() != null) {
1870                                                                dataflow.getResultsets().addAll(temp.getResultsets());
1871                                                        }
1872                                                        if (temp.getRelationships() != null) {
1873                                                                dataflow.getRelationships().addAll(temp.getRelationships());
1874                                                        }
1875                                                        if (temp.getErrors() != null) {
1876                                                                dataflow.getErrors().addAll(temp.getErrors());
1877                                                        }
1878                                                }
1879                                        }
1880                                        else if (sql != null && sqlTrim.startsWith("{")) {
1881                                                EDbVendor vendor = SQLUtil.isEmpty(sqlInfo.getDbVendor()) ? option.getVendor()
1882                                                                : EDbVendor.valueOf(sqlInfo.getDbVendor());
1883                                                // Pass the manifest's directory so a sharded manifest supplied
1884                                                // as inline content can still resolve its relative catalog
1885                                                // paths (otherwise baseDir is null and the catalog is not read).
1886                                                String envBaseDir = sqlInfo.getFilePath() != null
1887                                                                ? new File(sqlInfo.getFilePath()).getParent() : null;
1888                                                TSQLEnv[] sqlenvs = new TJSONSQLEnvParser(option.getDefaultServer(),
1889                                                                option.getDefaultDatabase(), option.getDefaultSchema())
1890                                                                .withIdentifierProfile(frozenIdentifierProfile)
1891                                                                .parseSQLEnv(vendor, sql, envBaseDir);
1892                                                if (sqlenvs != null && sqlenvs.length > 0) {
1893                                                        if (sqlenv == null) {
1894                                                                sqlenv = sqlenvs[0];
1895                                                        } else {
1896                                                                sqlenv = SQLEnvParser.mergeSQLEnv(Arrays.asList(sqlenv, sqlenvs[0]));
1897                                                        }
1898                                                }
1899                                                if (sqlenv != null) {
1900                                                        if (MetadataReader.isGrabit(sql) || MetadataReader.isSqlflow(sql) || MetadataReader.isSqlflowSharded(sql)) {
1901                                                                String hash = SHA256.getMd5(sql);
1902                                                                String fileHash = SHA256.getMd5(hash);
1903                                                                if (!sqlInfoMap.containsKey(fileHash)) {
1904                                                                        sqlInfoMap.put(fileHash, new ArrayList<SqlInfo>());
1905                                                                        sqlInfoMap.get(fileHash).add(sqlInfo);
1906                                                                }
1907                                                                ModelBindingManager.setGlobalHash(fileHash);
1908                                                                dataflow temp = null;
1909
1910                                                                // Check the authoritative sharded `format` before the
1911                                                                // grabit brand: a grabit-branded sharded manifest must go
1912                                                                // to the sharded reader, not GrabitMetadataAnalyzer.
1913                                                                if (MetadataReader.isSqlflowSharded(sql)) {
1914                                                                        String baseDir = null;
1915                                                                        if (sqlInfo.getFilePath() != null) {
1916                                                                                baseDir = new File(sqlInfo.getFilePath()).getParent();
1917                                                                        }
1918                                                                        temp = new SqlflowShardedMetadataAnalyzer(baseDir).analyzeMetadata(option.getVendor(), sql);
1919                                                                } else if (MetadataReader.isGrabit(sql)) {
1920                                                                        temp = new GrabitMetadataAnalyzer().analyzeMetadata(option.getVendor(), sql);
1921                                                                } else {
1922                                                                        temp = new SqlflowMetadataAnalyzer(sqlenv).analyzeMetadata(option.getVendor(), sql);
1923                                                                }
1924//                                                              if (temp.getPackages() != null) {
1925//                                                                      dataflow.getPackages().addAll(temp.getPackages());
1926//                                                              }
1927//                                                              if (temp.getProcedures() != null) {
1928//                                                                      dataflow.getProcedures().addAll(temp.getProcedures());
1929//                                                              }
1930                                                                if (temp.getTables() != null) {
1931                                                                        dataflow.getTables().addAll(temp.getTables());
1932                                                                }
1933                                                                if (temp.getViews() != null) {
1934                                                                        dataflow.getViews().addAll(temp.getViews());
1935                                                                }
1936                                                                if (temp.getResultsets() != null) {
1937                                                                        dataflow.getResultsets().addAll(temp.getResultsets());
1938                                                                }
1939                                                                if (temp.getRelationships() != null) {
1940                                                                        dataflow.getRelationships().addAll(temp.getRelationships());
1941                                                                }
1942                                                                if (temp.getErrors() != null) {
1943                                                                        dataflow.getErrors().addAll(temp.getErrors());
1944                                                                }
1945                                                                if (sql.indexOf("createdBy") != -1) {
1946                                                                        // Route on the authoritative `format` field, not the
1947                                                                        // `createdBy` product brand. A sqlflow-sharded manifest
1948                                                                        // carries the exporter brand (SQLdep/sqlflow/grabit) in
1949                                                                        // createdBy; a plain substring check on the brand hijacked
1950                                                                        // SQLdep-branded sharded exports into the single-file
1951                                                                        // `queries` path, which finds no `queries` array and read
1952                                                                        // nothing. Exclude sharded manifests from the brand branch
1953                                                                        // so they fall through to the sharded reader below.
1954                                                                        if ((sql.toLowerCase(Locale.ROOT).indexOf("sqldep") != -1
1955                                                                                        || sql.toLowerCase(Locale.ROOT).indexOf("grabit") != -1)
1956                                                                                        && !MetadataReader.isSqlflowSharded(sql)) {
1957                                                                                Map jsonObject = (Map) JSON.parseObject(sql);
1958                                                                                List<Map> queries = (List<Map>) jsonObject.get("queries");
1959                                                                                if (queries != null) {
1960                                                                                        for (int j = 0; j < queries.size(); j++) {
1961                                                                                                Map queryObject = queries.get(j);
1962                                                                                                // SQLDep and Grabit inputs refuse CATALOG_STORED.
1963                                                                                                appendSqlInfo(databaseMap, j, sqlInfo, queryObject,
1964                                                                                                                IdentifierInputForm.SQL_LEXICAL);
1965                                                                                        }
1966                                                                                }
1967                                                                        } else if (sql.toLowerCase(Locale.ROOT).indexOf("sqlflow") != -1) {
1968                                                                                Map sqlflow = (Map) JSON.parseObject(sql);
1969                                                                                if ("sqlflow-sharded".equals(sqlflow.get("format"))
1970                                                                                                && !MetadataReader.isSupportedSqlflowSharded(sql)) {
1971                                                                                        ErrorInfo errorInfo = new ErrorInfo();
1972                                                                                        errorInfo.setErrorType(ErrorInfo.METADATA_ERROR);
1973                                                                                        errorInfo.setErrorMessage("Unsupported sqlflow-sharded formatVersion "
1974                                                                                                        + MetadataReader.shardedFormatVersion(sql)
1975                                                                                                        + "; this build supports up to "
1976                                                                                                        + MetadataReader.SUPPORTED_SHARDED_FORMAT_VERSION
1977                                                                                                        + ". Source SQL not loaded.");
1978                                                                                        errorInfo.setFileName(sqlInfo.getFileName());
1979                                                                                        errorInfo.setFilePath(sqlInfo.getFilePath());
1980                                                                                        errorInfo.setStartPosition(new Pair3<Long, Long, String>(-1L, -1L,
1981                                                                                                        ModelBindingManager.getGlobalHash()));
1982                                                                                        errorInfo.setEndPosition(new Pair3<Long, Long, String>(-1L, -1L,
1983                                                                                                        ModelBindingManager.getGlobalHash()));
1984                                                                                        errorInfo.setOriginStartPosition(new Pair<Long, Long>(-1L, -1L));
1985                                                                                        errorInfo.setOriginEndPosition(new Pair<Long, Long>(-1L, -1L));
1986                                                                                        metadataErrors.add(errorInfo);
1987                                                                                } else if ("sqlflow-sharded".equals(sqlflow.get("format"))) {
1988                                                                                        String baseDir = null;
1989                                                                                        if (sqlInfo.getFilePath() != null) {
1990                                                                                                baseDir = new File(sqlInfo.getFilePath()).getParent();
1991                                                                                        }
1992                                                                                        String sourceCompression = (String) sqlflow.get("sourceCompression");
1993                                                                                        List<Map> servers = (List<Map>) sqlflow.get("servers");
1994                                                                                        if (servers != null) {
1995                                                                                                for (Map serverObject : servers) {
1996                                                                                                        List<Map> databases = (List<Map>) serverObject.get("databases");
1997                                                                                                        // Source shards live under databases[] (catalog topology) or
1998                                                                                                        // schemas[] (schema topology, the natural Oracle shape). Both
1999                                                                                                        // carry a per-shard `source`; iterate whichever is present.
2000                                                                                                        // Reading only databases[] silently dropped every source record
2001                                                                                                        // of a schema-topology manifest -> empty lineage.
2002                                                                                                        List<Map> schemaShards = (List<Map>) serverObject.get("schemas");
2003                                                                                                        if (schemaShards != null && !schemaShards.isEmpty()) {
2004                                                                                                                databases = schemaShards;
2005                                                                                                        }
2006                                                                                                        if (databases != null) {
2007                                                                                                                for (Map database : databases) {
2008                                                                                                                        Map source = (Map) database.get("source");
2009                                                                                                                        if (source != null) {
2010                                                                                                                                String sourcePath = (String) source.get("path");
2011                                                                                                                                if (sourcePath != null && baseDir != null) {
2012                                                                    File sourceFile = new File(baseDir, sourcePath);
2013                                                                                                                                        String fullPath = sourceFile.getAbsolutePath();
2014                                                                                                                                        if ("block".equals(sourceCompression)) {
2015                                                                                                                                                readGzipBlockSource(fullPath, databaseMap, sqlInfo,
2016                                                                                                                                                                SqlflowNameForm.declared(sqlflow));
2017                                                                                                                                        } else {
2018                                                                                                                                                String sourceContent = SQLUtil.getFileContent(fullPath);
2019                                                                                                                                                if (sourceContent != null) {
2020                                                                                                                                                        String[] lines = sourceContent.split("\\r?\\n");
2021                                                                                                                                                        for (int j = 0; j < lines.length; j++) {
2022                                                                                                                                                                String line = lines[j].trim();
2023                                                                                                                                                                if (line.isEmpty()) {
2024                                                                                                                                                                        continue;
2025                                                                                                                                                                }
2026                                                                                                                                                                try {
2027                                                                                                                                                                        Map sourceObject = (Map) JSON.parseObject(line);
2028                                                                                                                                                                        String sourceCode = (String) sourceObject.get("sourceCode");
2029                                                                                                                                                                        if (sourceCode != null && !sourceCode.isEmpty()) {
2030                                                                                                                                                                                SqlInfo sourceSqlInfo = new SqlInfo();
2031                                                                                                                                                                                sourceSqlInfo.setFileName(sourceFile.getName());
2032                                                                                                                                                                                sourceSqlInfo.setFilePath(sourceFile.getAbsolutePath());
2033                                                                                                                                                                                sourceSqlInfo.setSql(sourceCode);
2034                                                                                                                                                                                sourceSqlInfo.setOriginIndex(j);
2035                                                                                                                                                                                appendSqlInfo(databaseMap, j, sourceSqlInfo, sourceObject,
2036                                                                                                                                                                                                SqlflowNameForm.declared(sqlflow));
2037                                                                                                                                                                        }
2038                                                                                                                                                                } catch (Exception e) {
2039                                                                                                                                                                        logger.warn("Parse source jsonl line failed.", e);
2040                                                                                                                                                                }
2041                                                                                                                                                        }
2042                                                                                                                                                }
2043                                                                                                                                        }
2044                                                                                                                                }
2045                                                                                                                        }
2046                                                                                                                }
2047                                                                                                        }
2048                                                                                                }
2049                                                                                        }
2050                                                                                } else {
2051                                                                                        List<Map> servers = (List<Map>) sqlflow.get("servers");
2052                                                                                        if (servers != null) {
2053                                                                                                for (Map serverObject : servers) {
2054                                                                                                        List<Map> queries = (List<Map>) serverObject.get("queries");
2055                                                                                                        if (queries != null) {
2056                                                                                                                IdentifierInputForm nameForm = SqlflowNameForm.declared(sqlflow);
2057                                                                                                                for (int j = 0; j < queries.size(); j++) {
2058                                                                                                                        Map queryObject = queries.get(j);
2059                                                                                                                        appendSqlInfo(databaseMap, j, sqlInfo, queryObject, nameForm);
2060                                                                                                                }
2061                                                                                                        }
2062                                                                                                }
2063                                                                                        }
2064                                                                                }
2065                                                                                List<Map> errorMessages =  (List<Map>) sqlflow.get("errorMessages");
2066                                                                                if(errorMessages!=null && !errorMessages.isEmpty()) {
2067                                                                                        for(Map error: errorMessages){
2068                                                                                                ErrorInfo errorInfo = new ErrorInfo();
2069                                                                                                errorInfo.setErrorType(ErrorInfo.METADATA_ERROR);
2070                                                                                                errorInfo.setErrorMessage((String)error.get("errorMessage"));
2071                                                                                                errorInfo.setFileName(sqlInfo.getFileName());
2072                                                                                                errorInfo.setFilePath(sqlInfo.getFilePath());
2073                                                                                                errorInfo.setStartPosition(new Pair3<Long, Long, String>(-1L, -1L,
2074                                                                                                                ModelBindingManager.getGlobalHash()));
2075                                                                                                errorInfo.setEndPosition(new Pair3<Long, Long, String>(-1L, -1L,
2076                                                                                                                ModelBindingManager.getGlobalHash()));
2077                                                                                                errorInfo.setOriginStartPosition(new Pair<Long, Long>(-1L, -1L));
2078                                                                                                errorInfo.setOriginEndPosition(new Pair<Long, Long>(-1L, -1L));
2079                                                                                                metadataErrors.add(errorInfo);
2080                                                                                        }
2081                                                                                }
2082                                                                        }
2083                                                                }
2084                                                        } else {
2085                                                                Map queryObject = (Map) JSON.parseObject(sql);
2086                                                                appendSqlInfo(databaseMap, i, sqlInfo, queryObject);
2087                                                        }
2088                                                } else {
2089                                                        Map queryObject = (Map) JSON.parseObject(sql);
2090                                                        appendSqlInfo(databaseMap, i, sqlInfo, queryObject);
2091                                                }
2092                                        } else {
2093                                                ModelBindingManager.removeGlobalDatabase();
2094                                                ModelBindingManager.removeGlobalSchema();
2095                                                ModelBindingManager.removeGlobalHash();
2096
2097                                                String content = sql;
2098
2099                                                if (content == null) {
2100                                                        continue;
2101                                                }
2102
2103                                                String delimiterChar = String.valueOf(sqlparser.getDelimiterChar());
2104
2105                                                // SQL-LR production parses every input as its own unit, from its text unchanged: positions are
2106                                                // published against that text, and no other input is appended to it.
2107                                                if (sqlInfos.length > 1 && !sqllrProducerMode()) {
2108                                                        String endTrim = SQLUtil.endTrim(content);
2109                                                        if (endTrim.endsWith(delimiterChar) || endTrim.endsWith(";")) {
2110                                                                content += "\n";
2111                                                        } else if (option.getVendor() == EDbVendor.dbvredshift
2112                                                                        || option.getVendor() == EDbVendor.dbvgaussdb
2113                                                                        || option.getVendor() == EDbVendor.dbvedb
2114                                                                        || option.getVendor() == EDbVendor.dbvpostgresql
2115                                                                        || option.getVendor() == EDbVendor.dbvmysql
2116                                                                        || option.getVendor() == EDbVendor.dbvteradata) {
2117                                                                content += ("\n\n-- " + TBaseType.sqlflow_stmt_delimiter_str + "\n\n");
2118                                                        } else {
2119                                                                content = endTrim + ";" + "\n";
2120                                                        }
2121                                                }
2122
2123                                                sqlInfo.setSql(content);
2124
2125                                                if (MetadataReader.isMetadata(content)) {
2126                                                        String hash = SHA256.getMd5(content);
2127                                                        ModelBindingManager.setGlobalHash(hash);
2128                                                        dataflow temp = new SQLDepMetadataAnalyzer().analyzeMetadata(option.getVendor(), content);
2129                                                        if (temp.getProcedures() != null) {
2130                                                                dataflow.getProcedures().addAll(temp.getProcedures());
2131                                                        }
2132                                                        if (temp.getTables() != null) {
2133                                                                dataflow.getTables().addAll(temp.getTables());
2134                                                        }
2135                                                        if (temp.getViews() != null) {
2136                                                                dataflow.getViews().addAll(temp.getViews());
2137                                                        }
2138                                                        if (temp.getResultsets() != null) {
2139                                                                dataflow.getResultsets().addAll(temp.getResultsets());
2140                                                        }
2141                                                        if (temp.getRelationships() != null) {
2142                                                                dataflow.getRelationships().addAll(temp.getRelationships());
2143                                                        }
2144                                                        if (temp.getErrors() != null) {
2145                                                                dataflow.getErrors().addAll(temp.getErrors());
2146                                                        }
2147                                                        String fileHash = SHA256.getMd5(hash);
2148                                                        if (!sqlInfoMap.containsKey(fileHash)) {
2149                                                                sqlInfoMap.put(fileHash, new ArrayList<SqlInfo>());
2150                                                                sqlInfoMap.get(fileHash).add(sqlInfo);
2151                                                        }
2152                                                } else {
2153                                                        // SQL-LR production keys each input as its own unit: two files with equal text are
2154                                                        // two source units (specification Section 7.8), never one unit holding the text twice.
2155                                                        String sqlHash = sqllrProducerMode()
2156                                                                        ? SHA256.getMd5(content + "\u0000sqllr-unit-" + i) : SHA256.getMd5(content);
2157                                                        if (modelManager.getSqllrObservations() != null) {
2158                                                                modelManager.getSqllrObservations().registerUnit(sqlHash, i);
2159                                                        }
2160                                                        String fileHash = SHA256.getMd5(sqlHash);
2161                                                        if (!sqlInfoMap.containsKey(fileHash)) {
2162                                                                sqlInfoMap.put(fileHash, new ArrayList<SqlInfo>());
2163                                                        }
2164
2165                                                        String database = TSQLEnv.DEFAULT_DB_NAME;
2166                                                        String schema = TSQLEnv.DEFAULT_SCHEMA_NAME;
2167                                                        if (sqlenv != null) {
2168                                                                // Prefer the per-analysis Option default database over the attached
2169                                                                // env's default catalog, so a shared multi-catalog TSQLEnv can be
2170                                                                // reused across analyses with different default databases without
2171                                                                // the env's default catalog deciding name resolution.
2172                                                                if (!SQLUtil.isEmpty(option.getDefaultDatabase())
2173                                                                                && !TSQLEnv.DEFAULT_DB_NAME.equals(option.getDefaultDatabase())) {
2174                                                                        database = option.getDefaultDatabase();
2175                                                                } else {
2176                                                                        database = sqlenv.getDefaultCatalogName();
2177                                                                }
2178                                                                if (database == null) {
2179                                                                        database = TSQLEnv.DEFAULT_DB_NAME;
2180                                                                }
2181                                                                schema = sqlenv.getDefaultSchemaName();
2182                                                                if (schema == null) {
2183                                                                        schema = TSQLEnv.DEFAULT_SCHEMA_NAME;
2184                                                                }
2185                                                        }
2186
2187                                                        boolean supportCatalog = TSQLEnv.supportCatalog(option.getVendor());
2188                                                        boolean supportSchema = TSQLEnv.supportSchema(option.getVendor());
2189                                                        String group;
2190                                                        IdentifierInputForm groupForm = null;
2191                                                        if (sqlInfo.getGroup() != null && !sqlInfo.getGroup().isEmpty()) {
2192                                                                group = sqlInfo.getGroup();
2193                                                                groupForm = sqlInfo.getGroupNameForm();
2194                                                        } else {
2195                                                                StringBuilder builder = new StringBuilder();
2196                                                                if (supportCatalog) {
2197                                                                        builder.append(database);
2198                                                                }
2199                                                                if (supportSchema) {
2200                                                                        if (builder.length() > 0) {
2201                                                                                builder.append(".");
2202                                                                        }
2203                                                                        builder.append(schema);
2204                                                                }
2205                                                                group = builder.toString();
2206                                                        }
2207                                                        SqlInfo sqlInfoItem = new SqlInfo();
2208                                                        sqlInfoItem.setFileName(sqlInfo.getFileName());
2209                                                        sqlInfoItem.setFilePath(sqlInfo.getFilePath());
2210                                                        sqlInfoItem.setSql(sqlInfo.getSql());
2211                                                        sqlInfoItem.setOriginIndex(0);
2212                                                        sqlInfoItem.setOriginLineStart(0);
2213                                                        int lineEnd = sqlInfo.getSql().split("\n").length - 1;
2214                                                        sqlInfoItem.setOriginLineEnd(lineEnd);
2215                                                        sqlInfoItem.setIndex(0);
2216                                                        sqlInfoItem.setLineStart(0);
2217                                                        sqlInfoItem.setLineEnd(lineEnd);
2218                                                        sqlInfoItem.setHash(SHA256.getMd5(sqlHash));
2219                                                        sqlInfoItem.setGroup(group);
2220                                                        sqlInfoMap.get(fileHash).add(sqlInfoItem);
2221                                                        if (!databaseMap.containsKey(sqlHash)) {
2222                                                                databaseMap.put(sqlHash, new Pair3<StringBuilder, AtomicInteger, String>(
2223                                                                                new StringBuilder(), new AtomicInteger(), group));
2224                                                                if (groupForm == IdentifierInputForm.CATALOG_STORED) {
2225                                                                        groupNameForms.put(sqlHash, groupForm);
2226                                                                }
2227                                                        }
2228                                                        if (commentBindingCollector != null) commentBindingCollector.register(sqlHash, sqlInfoItem.getSql(),
2229                                databaseMap.get(sqlHash).first.length(), i, sqlInfoItem.getFileName(), "derived".equals(sqlInfo.getSourceOriginKind()));
2230                            databaseMap.get(sqlHash).first.append(sqlInfoItem.getSql());
2231                                                        databaseMap.get(sqlHash).second.incrementAndGet();
2232                                                }
2233                                        }
2234                                }
2235
2236                                boolean supportCatalog = TSQLEnv.supportCatalog(option.getVendor());
2237                                boolean supportSchema = TSQLEnv.supportSchema(option.getVendor());
2238
2239                                // Cross-file call-site binding discovery (plan Phase 5) needs every
2240                                // unit's text before the first unit is analyzed: units are parsed and
2241                                // analyzed strictly one at a time, so a caller in a later file would
2242                                // otherwise be invisible while an earlier file's procedure is analyzed.
2243                                if ((option.getVendor() == EDbVendor.dbvmssql || option.getVendor() == EDbVendor.dbvoracle)
2244                                                && databaseMap.size() > 1) {
2245                                        List<String> unitTexts = new ArrayList<String>();
2246                                        for (Pair3<StringBuilder, AtomicInteger, String> unit : databaseMap.values()) {
2247                                                unitTexts.add(unit.first.toString());
2248                                        }
2249                                        routineCatalog = new gudusoft.gsqlparser.dlineage.dynamicsql.RoutineCatalog(
2250                                                        option.getVendor(), unitTexts, crossFileParseCache);
2251                                }
2252
2253                                // Two different names that share one legacy normal name must be told apart from the
2254                                // first unit on, so every unit is scanned before any is analyzed: a lone unit from the
2255                                // tokens its parse reads anyway, several by lexing their text first. Names are told
2256                                // apart under the analysis's own rules, a configured profile's included.
2257                                collisionScanPending = false;
2258                                if (databaseMap.size() == 1) {
2259                                        collisionScanPending = true;
2260                                } else {
2261                                        List<String> unitTexts = new ArrayList<String>();
2262                                        for (Pair3<StringBuilder, AtomicInteger, String> unit : databaseMap.values()) {
2263                                                unitTexts.add(unit.first.toString());
2264                                        }
2265                                        publishContestedNormalNames(NormalNameCollisions.scan(collisionService(), unitTexts));
2266                                }
2267
2268                                Iterator<String> schemaIter = databaseMap.keySet().iterator();
2269                                while (schemaIter.hasNext()) {
2270                                        if (option.getHandleListener() != null && option.getHandleListener().isCanceled()) {
2271                                                break;
2272                                        }
2273                                        String key = schemaIter.next();
2274                                        String group = databaseMap.get(key).third;
2275                                        String[] split = SQLUtil.parseNames(group).toArray(new String[0]);
2276
2277                                        ModelBindingManager.removeGlobalDatabase();
2278                                        ModelBindingManager.removeGlobalSchema();
2279                                        ModelBindingManager.removeGlobalSQLEnv();
2280                                        ModelBindingManager.removeGlobalHash();
2281
2282                                        if (sqlenv == null) {
2283                                                // The placeholder carries the analysis's own profile: built on the vendor
2284                                                // default, it contradicts a configured profile and the parser refuses the pair.
2285                                                sqlenv = new TSQLEnv(option.getVendor(),
2286                                                                IdentifierProfile.forVendorOr(frozenIdentifierProfile, option.getVendor())) {
2287
2288                                                        @Override
2289                                                        public void initSQLEnv() {
2290                                                        }
2291                                                };
2292                                        }
2293                                        
2294                                        String defaultDatabase = null;
2295                                        String defaultSchema = null;
2296                                        if(sqlenv!=null){
2297                                                defaultDatabase = sqlenv.getDefaultCatalogName();
2298                                                defaultSchema = sqlenv.getDefaultSchemaName();
2299                                        }
2300                                        IdentifierInputForm groupForm = groupNameForms.get(key);
2301                                        if (supportCatalog && supportSchema) {
2302                                                if (split.length >= 2) {
2303                                                        if (!TSQLEnv.DEFAULT_DB_NAME.equals(split[split.length - 2])) {
2304                                                                ModelBindingManager.setGlobalDatabase(split[split.length - 2], groupForm);
2305                                                                sqlenv.setDefaultCatalogName(ModelBindingManager.getGlobalDatabase());
2306                                                        }
2307                                                }
2308                                                if (split.length >= 1) {
2309                                                        if (!TSQLEnv.DEFAULT_SCHEMA_NAME.equals(split[split.length - 1])) {
2310                                                                ModelBindingManager.setGlobalSchema(split[split.length - 1], groupForm);
2311                                                                sqlenv.setDefaultSchemaName(ModelBindingManager.getGlobalSchema());
2312                                                        }
2313                                                }
2314                                        } else if (supportCatalog) {
2315                                                if (!TSQLEnv.DEFAULT_DB_NAME.equals(split[split.length - 1])) {
2316                                                        ModelBindingManager.setGlobalDatabase(split[split.length - 1], groupForm);
2317                                                        sqlenv.setDefaultCatalogName(ModelBindingManager.getGlobalDatabase());
2318                                                }
2319                                        } else if (supportSchema) {
2320                                                if (!TSQLEnv.DEFAULT_SCHEMA_NAME.equals(split[split.length - 1])) {
2321                                                        ModelBindingManager.setGlobalSchema(split[split.length - 1], groupForm);
2322                                                        sqlenv.setDefaultSchemaName(ModelBindingManager.getGlobalSchema());
2323                                                }
2324                                        }
2325                                        if (option.getHandleListener() != null) {
2326                                                option.getHandleListener().startParse(null, databaseMap.get(key).first.toString());
2327                                        }
2328
2329                                        ModelBindingManager.setGlobalSQLEnv(sqlenv);
2330                                        sqlparser.sqltext = databaseMap.get(key).first.toString();
2331                                        ModelBindingManager.setGlobalHash(SHA256.getMd5(key));
2332                                        commentSourceKey = key;
2333                                        if (modelManager.getSqllrObservations() != null) {
2334                                                modelManager.getSqllrObservations().beginUnit(key);
2335                                        }
2336                    analyzeAndOutputResult(sqlparser);
2337                                        if(sqlenv!=null){
2338                                                sqlenv.setDefaultCatalogName(defaultDatabase);
2339                                                sqlenv.setDefaultSchemaName(defaultSchema);
2340                                        }
2341                                        // An EXEC of a routine that this unit defines binds its arguments now, before a later unit
2342                                        // changes what the call's arguments resolve to.
2343                                        bindDeferredExecuteArguments();
2344                                }
2345                                // A call whose routine no unit defines stays unbound.
2346                                deferredExecuteBindings.clear();
2347
2348                                materializeSqlEnvSynonyms();
2349
2350                                if (modelManager.getSqllrObservations() != null) {
2351                                        // the SQL-LR producer reads the graph as analysis left it; appendRelation below changes it
2352                                        modelManager.getSqllrObservations().freeze(modelManager);
2353                                }
2354
2355                                appendProcesses(dataflow);
2356                                appendOraclePackages(dataflow);
2357                                appendProcedures(dataflow);
2358                                appendTables(dataflow);
2359                                appendViews(dataflow);
2360                                appendResultSets(dataflow);
2361                                appendRelations(dataflow);
2362                                appendErrors(dataflow);
2363                        }
2364
2365            if (importedCommentBundles != null) dataflow.setCommentBindings(CommentBindingProjection.merge(importedCommentBundles));
2366                        if (commentBindingCollector != null && dataflow != null) {
2367                CommentBindingBundle collected = commentBindingCollector.build(modelManager);
2368                if (dataflow.getCommentBindings() == null) dataflow.setCommentBindings(collected);
2369                else if (!collected.getSourceUnits().isEmpty() || !"complete".equals(collected.getState())) dataflow.setCommentBindings(CommentBindingProjection.merge(
2370                        Arrays.asList(dataflow.getCommentBindings(), collected)));
2371                CommentBindingProjection.retain(dataflow, false);
2372            }
2373            dataflow = handleDataflowExecProcedure(dataflow);
2374
2375                        if (dataflow != null && option.getAnalyzeMode() != AnalyzeMode.crud) {
2376                                if (!isShowJoin()) {
2377                                        dataflow = mergeTables(dataflow, modelManager.TABLE_COLUMN_ID, option);
2378                                } else {
2379                                        dataflow = removeDuplicateColumns(dataflow);
2380                                }
2381                        }
2382
2383                        if (dataflow != null && option.isNormalizeOutput()) {
2384                                dataflow = getNormalizeDataflow(dataflow);
2385                        }
2386
2387                        if (dataflow != null && option.isIgnoreCoordinate()) {
2388                                dataflow = filterDataflowCoordinate(dataflow);
2389                        }
2390
2391                        // once the names and coordinates are final: blanked coordinates can make two relationships one
2392                        if (dataflow != null && option.getAnalyzeMode() != AnalyzeMode.crud && !endpointLabelsDeferred) {
2393                                DataflowUtility.labelEndpoints(dataflow);
2394                        }
2395                        
2396                        if (option.isSimpleOutput() || option.isIgnoreRecordSet()) {
2397                                List<String> showTypes = new ArrayList<>();
2398                                if(option.getSimpleShowRelationTypes()!=null) {
2399                                        showTypes.addAll(option.getSimpleShowRelationTypes());
2400                                }
2401                                if(showTypes.isEmpty()) {
2402                                        showTypes.add("fdd");
2403                                }
2404                                if(option.isShowCallRelation()) {
2405                                        showTypes.add("call");
2406                                }
2407                                if(option.isShowERDiagram()) {
2408                                        showTypes.add("er");
2409                                }
2410                                dataflow simpleDataflow = getSimpleDataflow(dataflow, option.isSimpleOutput(), showTypes);
2411                                if (simpleDataflow.getResultsets() != null) {
2412                                        for (table t : simpleDataflow.getResultsets()) {
2413                                                t.setIsTarget(null);
2414                                        }
2415                                }
2416                                return finalizeAuthoritativeLineageEvidence(simpleDataflow);
2417                        } else {
2418                                return finalizeAuthoritativeLineageEvidence(dataflow);
2419                        }
2420                        
2421                } catch (Exception e) {
2422                        logger.error("analyze sql failed.", e);
2423                        ErrorInfo errorInfo = new ErrorInfo();
2424                        errorInfo.setErrorType(ErrorInfo.ANALYZE_ERROR);
2425                        if (e.getMessage() == null) {
2426                                if (e.getStackTrace() != null && e.getStackTrace().length > 0) {
2427                                        errorInfo.setErrorMessage(e.getClass().getSimpleName() + ": " + e.getStackTrace()[0].toString());
2428                                } else {
2429                                        errorInfo.setErrorMessage(e.getClass().getSimpleName());
2430                                }
2431                        } else {
2432                                errorInfo.setErrorMessage(e.getClass().getSimpleName() + ": " + e.getMessage());
2433                        }
2434                        errorInfo.fillInfo(this);
2435                        errorInfos.add(errorInfo);
2436                }
2437
2438                modelManager.reset();
2439                return null;
2440        }
2441
2442        private dataflow finalizeAuthoritativeLineageEvidence(dataflow completed) {
2443                if (!shouldCollectAuthoritativeLineageEvidence()) {
2444                        authoritativeEvidenceCollector.reset();
2445                        authoritativeLineageEvidence = AuthoritativeLineageEvidence.empty();
2446                        return completed;
2447                }
2448                try {
2449                        authoritativeLineageEvidence = authoritativeEvidenceCollector.build(modelManager, completed);
2450                } catch (ThreadDeath fatal) {
2451                        throw fatal;
2452                } catch (VirtualMachineError fatal) {
2453                        throw fatal;
2454                } catch (Throwable evidenceFailure) {
2455                        // Additive evidence must never make the established legacy analyzer fail.
2456                        logger.error("Capture authoritative lineage evidence failed; legacy output is preserved.",
2457                                        evidenceFailure);
2458                        authoritativeLineageEvidence = AuthoritativeLineageEvidence.empty();
2459                } finally {
2460                        // Drafts retain AST/model objects. The published bundle contains only
2461                        // immutable scalar values, so release all per-run references immediately.
2462                        authoritativeEvidenceCollector.reset();
2463                }
2464                return completed;
2465        }
2466
2467        private boolean shouldCollectAuthoritativeLineageEvidence() {
2468                return option != null && option.isCollectAuthoritativeLineageEvidence();
2469        }
2470
2471        private dataflow handleDataflowExecProcedure(dataflow dataflow) {
2472                // The xml objects' models, reached by id: the join compares the routines' names as written.
2473                Map<String, Procedure> procedureModels = new HashMap<String, Procedure>();
2474                for (Procedure model : modelManager.getProcedureModels()) {
2475                        procedureModels.put(String.valueOf(model.getId()), model);
2476                }
2477                Set<RoutineAliasKey> procedureKeys = new HashSet<RoutineAliasKey>();
2478                for (procedure procedure : dataflow.getProcedures()) {
2479                        Procedure model = procedureModels.get(procedure.getId());
2480                        if (model != null && model.getAliasKey() != null) {
2481                                procedureKeys.add(model.getAliasKey());
2482                        }
2483                }
2484                Map<String, ResultSet> resultSetModels = new HashMap<String, ResultSet>();
2485                for (ResultSet model : modelManager.getResultSets()) {
2486                        resultSetModels.put(String.valueOf(model.getId()), model);
2487                }
2488                List<table> removeResultSets = new ArrayList<table>();
2489                
2490                Map<String, table> allMap = new HashMap<String, table>();
2491                Map<String, column> columnsById = DlineageUtil.outputColumnsById(dataflow);
2492                
2493                if (dataflow.getResultsets() != null) {
2494                        for (table t : dataflow.getResultsets()) {
2495                                allMap.put(t.getId().toLowerCase(Locale.ROOT), t);
2496                        }
2497                }
2498
2499                if (dataflow.getTables() != null) {
2500                        for (table t : dataflow.getTables()) {
2501                                allMap.put(t.getId().toLowerCase(Locale.ROOT), t);
2502                        }
2503                }
2504
2505                if (dataflow.getViews() != null) {
2506                        for (table t : dataflow.getViews()) {
2507                                allMap.put(t.getId().toLowerCase(Locale.ROOT), t);
2508                        }
2509                }
2510
2511                if (dataflow.getVariables() != null) {
2512                        for (table t : dataflow.getVariables()) {
2513                                allMap.put(t.getId().toLowerCase(Locale.ROOT), t);
2514                        }
2515                }
2516
2517                
2518                for (table resultSet : dataflow.getResultsets()) {
2519                        boolean functionResult = ResultSetType.function.name().equals(resultSet.getType());
2520                        RoutineAliasKey resultSetKey = functionResult ? resultSetAliasKey(resultSetModels.get(resultSet.getId())) : null;
2521                        boolean join = resultSetKey != null && modelManager.getFunctionResults(resultSetKey) != null
2522                                        && procedureKeys.contains(resultSetKey);
2523                        if (!join) {
2524                                continue;
2525                        }
2526
2527                        Set<Object> functionTables = modelManager.getFunctionResults(resultSetKey);
2528                        List<ResultSet> functionResults = functionTables.stream().filter(t -> t instanceof ResultSet)
2529                                        .map(t -> (ResultSet) t).collect(Collectors.toList());
2530                        if (!(resultSet.getColumns().size() == 1
2531                                        && DlineageUtil.sameQualifiedName(ESQLDataObjectType.dotTable, resultSet.getColumns().get(0).getName(), resultSet.getName()))
2532                                        || functionResults.isEmpty()) {
2533                                continue;
2534                        }
2535
2536                        column column = resultSet.getColumns().get(0);
2537                        String sourceColumnId = column.getId();
2538                        boolean reserveResultSet = false;
2539                        List<relationship> appendRelations = new ArrayList<relationship>();
2540                        Set<relationship> removeRelations = new HashSet<relationship>();
2541                        
2542                        for (relationship relationship : dataflow.getRelationships()) {
2543                                targetColumn targetColumn = relationship.getTarget();
2544                                List<sourceColumn> sourceColumns = relationship.getSources();
2545                                List<sourceColumn> newSourceColumns = new ArrayList<sourceColumn>();
2546                                for (sourceColumn sourceColumn : sourceColumns) {
2547                                        if (!sourceColumn.getId().equals(sourceColumnId)) {
2548                                                continue;
2549                                        }
2550                                        for (ResultSet sourceResultSet : functionResults) {
2551                                                for (ResultColumn resultColumn : sourceResultSet.getColumns()) {
2552                                                        if (!DlineageUtil.compareColumnIdentifier(resultColumn.getName(),
2553                                                                        targetColumn.getColumn())) {
2554                                                                if (DlineageUtil.isStarEndpoint(columnsById, targetColumn.getId(), targetColumn.getColumn())) {
2555                                                                        table parent = allMap.get(targetColumn.getParent_id());
2556                                                                        if (parent != null && parent.getColumns().size() > 1) {
2557                                                                                for (column item : parent.getColumns()) {
2558                                                                                        if (DlineageUtil.compareColumnIdentifier(resultColumn.getName(),
2559                                                                                                        item.getName())) {
2560                                                                                                relationship newRelation = appendStarRelation(dataflow, relationship, item, resultColumn);
2561                                                                                                appendRelations.add(newRelation);
2562                                                                                                removeRelations.add(relationship);
2563                                                                                                newSourceColumns.add(newRelation.getSources().get(0));
2564                                                                                                if (resultColumn.getResultSet() != null && resultColumn.getResultSet().isTarget()) {
2565                                                                                                        dataflow.getResultsets().stream().filter(
2566                                                                                                                        t -> t.getId().equals(String.valueOf(resultColumn.getResultSet().getId())))
2567                                                                                                                        .forEach(t -> t.setIsTarget(Boolean.FALSE.toString()));
2568                                                                                                }
2569                                                                                        }
2570                                                                                }
2571                                                                        }
2572                                                                }
2573                                                                continue;
2574                                                        }
2575                                                        
2576                                                        sourceColumn sourceColumn1 = new sourceColumn();
2577                                                        sourceColumn1.setId(String.valueOf(resultColumn.getId()));
2578                                                        sourceColumn1.setColumn(resultColumn.getName());
2579                                                        sourceColumn1.setParent_id(String.valueOf(resultColumn.getResultSet().getId()));
2580                                                        sourceColumn1.setParent_name(getResultSetName(resultColumn.getResultSet()));
2581                                                        if (resultColumn.getStartPosition() != null
2582                                                                        && resultColumn.getEndPosition() != null) {
2583                                                                sourceColumn1.setCoordinate(convertCoordinate(resultColumn.getStartPosition())
2584                                                                                + "," + convertCoordinate(resultColumn.getEndPosition()));
2585                                                        }
2586                                                        newSourceColumns.add(sourceColumn1);
2587                                                        
2588                                                        if (resultColumn.getResultSet() != null && resultColumn.getResultSet().isTarget()) {
2589                                                                dataflow.getResultsets().stream().filter(
2590                                                                                t -> t.getId().equals(String.valueOf(resultColumn.getResultSet().getId())))
2591                                                                                .forEach(t -> t.setIsTarget(Boolean.FALSE.toString()));
2592                                                        }
2593                                                }
2594                                        }
2595
2596                                        if (!newSourceColumns.isEmpty()) {
2597                                                if (removeRelations.isEmpty()) {
2598                                                        relationship.setSources(newSourceColumns);
2599                                                }
2600                                        }
2601                                        else {
2602                                                reserveResultSet = true;
2603                                        }
2604                                }
2605                        }
2606                        
2607                        dataflow.getRelationships().addAll(appendRelations);
2608                        dataflow.getRelationships().removeAll(removeRelations);
2609                        
2610                        if(!reserveResultSet) {
2611                                removeResultSets.add(resultSet);
2612                        }
2613                }
2614                dataflow.getResultsets().removeAll(removeResultSets);
2615                return dataflow;
2616        }
2617
2618        private relationship appendStarRelation(dataflow dataflow,
2619                        relationship relationship, column item, ResultColumn resultColumn) {
2620                relationship relationElement = new relationship();
2621                relationElement.setType(relationship.getType());
2622                relationElement.setEffectType(relationship.getEffectType());
2623                relationElement.setSqlHash(relationship.getSqlHash());
2624                relationElement.setSqlComment(relationship.getSqlComment());
2625                relationElement.setProcedureId(relationship.getProcedureId());
2626                relationElement.setId(String.valueOf(++ModelBindingManager.get().RELATION_ID));
2627                relationElement.setProcessId(relationship.getProcessId());
2628                relationElement.setProcessType(relationship.getProcessType());
2629                
2630                targetColumn targetColumn1 = new targetColumn();
2631                targetColumn1.setId(String.valueOf(item.getId()));
2632                targetColumn1.setColumn(item.getName());
2633                targetColumn1.setParent_id(relationship.getTarget().getParent_id());
2634                targetColumn1.setParent_name(relationship.getTarget().getParent_name());
2635                targetColumn1.setParent_alias(relationship.getTarget().getParent_alias());
2636                if (relationship.getTarget().getCoordinate() != null) {
2637                        targetColumn1.setCoordinate(item.getCoordinate());
2638                }
2639                relationElement.setTarget(targetColumn1);       
2640                
2641                sourceColumn sourceColumn1 = new sourceColumn();
2642                sourceColumn1.setId(String.valueOf(resultColumn.getId()));
2643                sourceColumn1.setColumn(resultColumn.getName());
2644                sourceColumn1.setParent_id(String.valueOf(resultColumn.getResultSet().getId()));
2645                sourceColumn1.setParent_name(getResultSetName(resultColumn.getResultSet()));
2646                if (resultColumn.getStartPosition() != null
2647                                && resultColumn.getEndPosition() != null) {
2648                        sourceColumn1.setCoordinate(convertCoordinate(resultColumn.getStartPosition())
2649                                        + "," + convertCoordinate(resultColumn.getEndPosition()));
2650                }
2651                relationElement.addSource(sourceColumn1);
2652                return relationElement;
2653        }
2654
2655        private dataflow getNormalizeDataflow(dataflow instance) {
2656                List<table> tables = new ArrayList<>();
2657                if (instance.getResultsets() != null) {
2658                        for (table t : instance.getResultsets()) {
2659                                tables.add(t);
2660                        }
2661                }
2662
2663                if (instance.getTables() != null) {
2664                        for (table t : instance.getTables()) {
2665                                tables.add(t);
2666                        }
2667                }
2668
2669                if (instance.getViews() != null) {
2670                        for (table t : instance.getViews()) {
2671                                tables.add(t);
2672                        }
2673                }
2674
2675                if (instance.getPaths() != null) {
2676                        for (table t : instance.getPaths()) {
2677                                tables.add(t);
2678                        }
2679                }
2680
2681                if (instance.getStages() != null) {
2682                        for (table t : instance.getStages()) {
2683                                tables.add(t);
2684                        }
2685                }
2686
2687                if (instance.getSequences() != null) {
2688                        for (table t : instance.getSequences()) {
2689                                tables.add(t);
2690                        }
2691                }
2692
2693                if (instance.getDatasources() != null) {
2694                        for (table t : instance.getDatasources()) {
2695                                tables.add(t);
2696                        }
2697                }
2698
2699                if (instance.getDatabases() != null) {
2700                        for (table t : instance.getDatabases()) {
2701                                tables.add(t);
2702                        }
2703                }
2704
2705                if (instance.getSchemas() != null) {
2706                        for (table t : instance.getSchemas()) {
2707                                tables.add(t);
2708                        }
2709                }
2710
2711                if (instance.getStreams() != null) {
2712                        for (table t : instance.getStreams()) {
2713                                tables.add(t);
2714                        }
2715                }
2716
2717                if (instance.getVariables() != null) {
2718                        for (table t : instance.getVariables()) {
2719                                tables.add(t);
2720                        }
2721                }
2722
2723                Map<String, String> idNameMap = new HashMap();
2724
2725                for(table table: tables){
2726                        if(table.getDatabase()!=null) {
2727                                table.setDatabase(LegacyDlineageWireNamesV1.name(LegacyDlineageWireNamesV1.Profile.NORMALIZED_OUTPUT, table.getDatabase(), ESQLDataObjectType.dotCatalog));
2728                        }
2729                        if(table.getSchema()!=null) {
2730                                table.setSchema(LegacyDlineageWireNamesV1.name(LegacyDlineageWireNamesV1.Profile.NORMALIZED_OUTPUT, table.getSchema(), ESQLDataObjectType.dotSchema));
2731                        }
2732                        if(table.getName()!=null) {
2733                                table.setName(LegacyDlineageWireNamesV1.name(LegacyDlineageWireNamesV1.Profile.NORMALIZED_OUTPUT, table.getName(), ESQLDataObjectType.dotTable));
2734                                idNameMap.put(table.getId(), table.getName());
2735                        }
2736                        if(table.getColumns()!=null){
2737                                for(column column: table.getColumns()){
2738                                        column.setName(LegacyDlineageWireNamesV1.name(LegacyDlineageWireNamesV1.Profile.NORMALIZED_OUTPUT, column.getName(), ESQLDataObjectType.dotColumn));
2739                                        idNameMap.put(column.getId(), column.getName());
2740                                }
2741                        }
2742                }
2743                if(instance.getPackages()!=null){
2744                        for(oraclePackage oraclePackage: instance.getPackages()){
2745                                if(oraclePackage.getDatabase()!=null) {
2746                                        oraclePackage.setDatabase(LegacyDlineageWireNamesV1.name(LegacyDlineageWireNamesV1.Profile.NORMALIZED_OUTPUT, oraclePackage.getDatabase(), ESQLDataObjectType.dotCatalog));
2747                                }
2748                                if(oraclePackage.getSchema()!=null) {
2749                                        oraclePackage.setSchema(LegacyDlineageWireNamesV1.name(LegacyDlineageWireNamesV1.Profile.NORMALIZED_OUTPUT, oraclePackage.getSchema(), ESQLDataObjectType.dotSchema));
2750                                }
2751                                if(oraclePackage.getName()!=null) {
2752                                        oraclePackage.setName(LegacyDlineageWireNamesV1.name(LegacyDlineageWireNamesV1.Profile.NORMALIZED_OUTPUT, oraclePackage.getName(), ESQLDataObjectType.dotTable));
2753                                        idNameMap.put(oraclePackage.getId(), oraclePackage.getName());
2754                                }
2755                                if(oraclePackage.getArguments()!=null){
2756                                        for(argument argument: oraclePackage.getArguments()){
2757                                                argument.setName(LegacyDlineageWireNamesV1.name(LegacyDlineageWireNamesV1.Profile.NORMALIZED_OUTPUT, argument.getName(), ESQLDataObjectType.dotColumn));
2758                                                idNameMap.put(argument.getId(), argument.getName());
2759                                        }
2760                                }
2761                                if(oraclePackage.getProcedures()!=null){
2762                                        for(procedure procedure: oraclePackage.getProcedures()){
2763                                                if(procedure.getDatabase()!=null) {
2764                                                        procedure.setDatabase(LegacyDlineageWireNamesV1.name(LegacyDlineageWireNamesV1.Profile.NORMALIZED_OUTPUT, procedure.getDatabase(), ESQLDataObjectType.dotCatalog));
2765                                                }
2766                                                if(procedure.getSchema()!=null) {
2767                                                        procedure.setSchema(LegacyDlineageWireNamesV1.name(LegacyDlineageWireNamesV1.Profile.NORMALIZED_OUTPUT, procedure.getSchema(), ESQLDataObjectType.dotSchema));
2768                                                }
2769                                                if(procedure.getName()!=null) {
2770                                                        procedure.setName(LegacyDlineageWireNamesV1.name(LegacyDlineageWireNamesV1.Profile.NORMALIZED_OUTPUT, procedure.getName(), ESQLDataObjectType.dotTable));
2771                                                        idNameMap.put(procedure.getId(), procedure.getName());
2772                                                }
2773                                                for(argument argument: procedure.getArguments()){
2774                                                        argument.setName(LegacyDlineageWireNamesV1.name(LegacyDlineageWireNamesV1.Profile.NORMALIZED_OUTPUT, argument.getName(), ESQLDataObjectType.dotColumn));
2775                                                        idNameMap.put(argument.getId(), argument.getName());
2776                                                }
2777                                        }
2778                                }
2779                        }
2780                }
2781                if(instance.getProcedures()!=null){
2782                        for(procedure procedure: instance.getProcedures()){
2783                                if(procedure.getDatabase()!=null) {
2784                                        procedure.setDatabase(LegacyDlineageWireNamesV1.name(LegacyDlineageWireNamesV1.Profile.NORMALIZED_OUTPUT, procedure.getDatabase(), ESQLDataObjectType.dotCatalog));
2785                                }
2786                                if(procedure.getSchema()!=null) {
2787                                        procedure.setSchema(LegacyDlineageWireNamesV1.name(LegacyDlineageWireNamesV1.Profile.NORMALIZED_OUTPUT, procedure.getSchema(), ESQLDataObjectType.dotSchema));
2788                                }
2789                                if(procedure.getName()!=null) {
2790                                        procedure.setName(LegacyDlineageWireNamesV1.name(LegacyDlineageWireNamesV1.Profile.NORMALIZED_OUTPUT, procedure.getName(), ESQLDataObjectType.dotTable));
2791                                        idNameMap.put(procedure.getId(), procedure.getName());
2792                                }
2793                                for(argument argument: procedure.getArguments()){
2794                                        argument.setName(LegacyDlineageWireNamesV1.name(LegacyDlineageWireNamesV1.Profile.NORMALIZED_OUTPUT, argument.getName(), ESQLDataObjectType.dotColumn));
2795                                        idNameMap.put(argument.getId(), argument.getName());
2796                                }
2797                        }
2798                }
2799                if (instance.getProcesses() != null) {
2800                        for (process process : instance.getProcesses()) {
2801                                if(process.getDatabase()!=null) {
2802                                        process.setDatabase(LegacyDlineageWireNamesV1.name(LegacyDlineageWireNamesV1.Profile.NORMALIZED_OUTPUT, process.getDatabase(), ESQLDataObjectType.dotCatalog));
2803                                }
2804                                if(process.getSchema()!=null) {
2805                                        process.setSchema(LegacyDlineageWireNamesV1.name(LegacyDlineageWireNamesV1.Profile.NORMALIZED_OUTPUT, process.getSchema(), ESQLDataObjectType.dotSchema));
2806                                }
2807                                if (process.getProcedureId() != null) {
2808                                        process.setProcedureName(LegacyDlineageWireNamesV1.name(LegacyDlineageWireNamesV1.Profile.NORMALIZED_OUTPUT, process.getProcedureName(), ESQLDataObjectType.dotTable));
2809                                        idNameMap.put(process.getId(), process.getName());
2810                                }
2811                        }
2812                }
2813
2814                if(instance.getRelationships()!=null){
2815                        for(relationship relation: instance.getRelationships()){
2816                                targetColumn targetColumn = relation.getTarget();
2817                                if(targetColumn != null) {
2818                                        targetColumn.setColumn(idNameMap.get(targetColumn.getId()));
2819                                        targetColumn.setTarget_name(idNameMap.get(targetColumn.getTarget_id()));
2820                                        targetColumn.setParent_name(idNameMap.get(targetColumn.getParent_id()));
2821                                }
2822                                List<sourceColumn> sourceColumns = relation.getSources();
2823                                if(sourceColumns!=null){
2824                                        for(sourceColumn sourceColumn: sourceColumns){
2825                                                sourceColumn.setColumn(idNameMap.get(sourceColumn.getId()));
2826                                                sourceColumn.setSource_name(idNameMap.get(sourceColumn.getSource_id()));
2827                                                sourceColumn.setParent_name(idNameMap.get(sourceColumn.getParent_id()));
2828                                        }
2829                                }
2830                                targetColumn = relation.getCaller();
2831                                if(targetColumn != null) {
2832                                        targetColumn.setName(idNameMap.get(targetColumn.getId()));
2833                                }
2834                                sourceColumns = relation.getCallees();
2835                                if(sourceColumns!=null){
2836                                        for(sourceColumn sourceColumn: sourceColumns){
2837                                                sourceColumn.setName(idNameMap.get(sourceColumn.getId()));
2838                                        }
2839                                }
2840                        }
2841                }
2842
2843
2844                return instance;
2845        }
2846
2847        private dataflow filterDataflowCoordinate(dataflow instance) {
2848                List<table> tables = new ArrayList<>();
2849                if (instance.getResultsets() != null) {
2850                        for (table t : instance.getResultsets()) {
2851                                tables.add(t);
2852                        }
2853                }
2854
2855                if (instance.getTables() != null) {
2856                        for (table t : instance.getTables()) {
2857                                tables.add(t);
2858                        }
2859                }
2860
2861                if (instance.getViews() != null) {
2862                        for (table t : instance.getViews()) {
2863                                tables.add(t);
2864                        }
2865                }
2866
2867                if (instance.getPaths() != null) {
2868                        for (table t : instance.getPaths()) {
2869                                tables.add(t);
2870                        }
2871                }
2872
2873                if (instance.getStages() != null) {
2874                        for (table t : instance.getStages()) {
2875                                tables.add(t);
2876                        }
2877                }
2878
2879                if (instance.getSequences() != null) {
2880                        for (table t : instance.getSequences()) {
2881                                tables.add(t);
2882                        }
2883                }
2884
2885                if (instance.getDatasources() != null) {
2886                        for (table t : instance.getDatasources()) {
2887                                tables.add(t);
2888                        }
2889                }
2890
2891                if (instance.getDatabases() != null) {
2892                        for (table t : instance.getDatabases()) {
2893                                tables.add(t);
2894                        }
2895                }
2896
2897                if (instance.getSchemas() != null) {
2898                        for (table t : instance.getSchemas()) {
2899                                tables.add(t);
2900                        }
2901                }
2902
2903                if (instance.getStreams() != null) {
2904                        for (table t : instance.getStreams()) {
2905                                tables.add(t);
2906                        }
2907                }
2908
2909                if (instance.getVariables() != null) {
2910                        for (table t : instance.getVariables()) {
2911                                tables.add(t);
2912                        }
2913                }
2914
2915                for(table table: tables){
2916                        table.clearCoordinate();
2917                        if(table.getColumns()!=null){
2918                                for(column column: table.getColumns()){
2919                                        column.clearCoordinate();
2920                                }
2921                        }
2922                }
2923                if(instance.getPackages()!=null){
2924                        for(oraclePackage oraclePackage: instance.getPackages()){
2925                                oraclePackage.setCoordinate(null);
2926                                if(oraclePackage.getProcedures()!=null){
2927                                        for(procedure procedure: oraclePackage.getProcedures()){
2928                                                procedure.setCoordinate(null);
2929                                                for(argument argument: procedure.getArguments()){
2930                                                        argument.setCoordinate(null);
2931                                                }
2932                                        }
2933                                }
2934                        }
2935                }
2936                if(instance.getProcedures()!=null){
2937                        for(procedure procedure: instance.getProcedures()){
2938                                procedure.setCoordinate(null);
2939                                for(argument argument: procedure.getArguments()){
2940                                        argument.setCoordinate(null);
2941                                }
2942                        }
2943                }
2944                if (instance.getProcesses() != null) {
2945                        for (process process : instance.getProcesses()) {
2946                                process.setCoordinate(null);
2947                        }
2948                }
2949
2950                if(instance.getRelationships()!=null){
2951                        for(relationship relation: instance.getRelationships()){
2952                                targetColumn targetColumn = relation.getTarget();
2953                                if(targetColumn != null) {
2954                                        targetColumn.setCoordinate(null);
2955                                }
2956                                List<sourceColumn> sourceColumns = relation.getSources();
2957                                if(sourceColumns!=null){
2958                                        for(sourceColumn sourceColumn: sourceColumns){
2959                                                sourceColumn.setCoordinate(null);
2960                                        }
2961                                }
2962                                targetColumn = relation.getCaller();
2963                                if(targetColumn != null) {
2964                                        targetColumn.setCoordinate(null);
2965                                }
2966                                sourceColumns = relation.getCallees();
2967                                if(sourceColumns!=null){
2968                                        for(sourceColumn sourceColumn: sourceColumns){
2969                                                sourceColumn.setCoordinate(null);
2970                                        }
2971                                }
2972                        }
2973                }
2974
2975                return instance;
2976        }
2977
2978        /**
2979         * Read a sqlflow-sharded manifest's source records into {@link SqlInfo}s (for the
2980         * convertSQL / text-constructor path). Relative source paths resolve against
2981         * {@code baseDir}; without a base directory the shard files cannot be located, so
2982         * nothing is added (a manifest passed as inline text with no file location simply
2983         * cannot be followed to its shards). Unavailable records are skipped. Block-compressed
2984         * sources are handled on the generateDataFlow path, not here.
2985         */
2986        private void appendShardedSourceSqlInfos(String manifestSql, String baseDir, String fileName,
2987                        List<SqlInfo> sqlInfos) {
2988                if (baseDir == null) {
2989                        return;
2990                }
2991                Map sqlflow = (Map) JSON.parseObject(manifestSql);
2992                boolean block = "block".equals(sqlflow.get("sourceCompression"));
2993                List<Map> servers = (List<Map>) sqlflow.get("servers");
2994                if (servers == null) {
2995                        return;
2996                }
2997                for (Map server : servers) {
2998                        String dbVendor = (String) server.get("dbVendor");
2999                        String serverName = (String) server.get("name");
3000                        // A sharded export is organized either as catalog shards
3001                        // (servers[].databases[]) or as schema shards
3002                        // (servers[].schemas[], the natural shape for schema-topology
3003                        // vendors like Oracle). Both carry a per-shard `source` pointer;
3004                        // prefer schema shards when present, treating an empty list as
3005                        // absent so a manifest with "databases":[] alongside schemas[]
3006                        // still loads. Handling only `databases` silently dropped every
3007                        // source record of a schema-topology manifest -> empty lineage.
3008                        List<Map> shards = (List<Map>) server.get("schemas");
3009                        if (shards == null || shards.isEmpty()) {
3010                                shards = (List<Map>) server.get("databases");
3011                        }
3012                        if (shards == null || shards.isEmpty()) {
3013                                continue;
3014                        }
3015                        for (Map shard : shards) {
3016                                appendShardedSourceRecords((Map) shard.get("source"), baseDir, block,
3017                                                dbVendor, serverName, sqlInfos, SqlflowNameForm.declared(sqlflow));
3018                        }
3019                }
3020        }
3021
3022        /**
3023         * Read one shard's source jsonl (catalog- or schema-topology) into SqlInfos.
3024         * Shared by the {@code databases[]} and {@code schemas[]} shard shapes so a
3025         * schema-topology manifest loads its source SQL identically to a
3026         * catalog-topology one.
3027         */
3028        private void appendShardedSourceRecords(Map source, String baseDir, boolean block,
3029                        String dbVendor, String serverName, List<SqlInfo> sqlInfos, IdentifierInputForm nameForm) {
3030                if (source == null) {
3031                        return;
3032                }
3033                String sourcePath = (String) source.get("path");
3034                if (sourcePath == null) {
3035                        return;
3036                }
3037                File sourceFile = new File(baseDir, sourcePath);
3038                String content = block ? readGzipContent(sourceFile.getAbsolutePath())
3039                                : SQLUtil.getFileContent(sourceFile.getAbsolutePath());
3040                if (content == null) {
3041                        return;
3042                }
3043                String[] lines = content.split("\\r?\\n");
3044                EDbVendor vendor = EDbVendor.valueOf(dbVendor);
3045                boolean supportCatalog = TSQLEnv.supportCatalog(vendor);
3046                boolean supportSchema = TSQLEnv.supportSchema(vendor);
3047
3048                for (int j = 0; j < lines.length; j++) {
3049                        String line = lines[j].trim();
3050                        if (line.isEmpty()) {
3051                                continue;
3052                        }
3053                        try {
3054                                Map rec = (Map) JSON.parseObject(line);
3055                                String sc = (String) rec.get("sourceCode");
3056                                if (sc != null && !sc.isEmpty() && !isSourceUnavailable(rec)) {
3057                                        SqlInfo info = new SqlInfo();
3058                                        info.setSql(sc);
3059                                        info.setFileName(sourceFile.getName());
3060                                        info.setFilePath(sourceFile.getAbsolutePath());
3061                                        info.setOriginIndex(j);
3062                                        info.setDbVendor(dbVendor);
3063                                        info.setServer(serverName);
3064
3065                                        String database = (String) rec.get("database");
3066                                        String schema = (String) rec.get("schema");
3067                                        StringBuilder groupBuilder = new StringBuilder();
3068                                        if (supportCatalog) {
3069                                                if (database.indexOf(".") != -1) {
3070                                                        database = SQLUtil.quoteDottedName(vendor, ESQLDataObjectType.dotCatalog, database);
3071                                                }
3072                                                groupBuilder.append(database);
3073                                        }
3074                                        if (supportSchema) {
3075                                                if (schema.indexOf(".") != -1) {
3076                                                        schema = SQLUtil.quoteDottedName(vendor, ESQLDataObjectType.dotSchema, schema);
3077                                                }
3078                                                if (groupBuilder.length() > 0) {
3079                                                        groupBuilder.append(".");
3080                                                }
3081                                                groupBuilder.append(schema);
3082                                        }
3083                                        info.setGroup(groupBuilder.toString());
3084                                        info.setGroupNameForm(nameForm == IdentifierInputForm.CATALOG_STORED ? nameForm : null);
3085
3086                                        sqlInfos.add(info);
3087                                }
3088                        } catch (Exception e) {
3089                                logger.warn("Parse sharded source jsonl line failed.", e);
3090                        }
3091                }
3092        }
3093
3094        /** Decompress a gzip/block-compressed source shard to its text, or null on failure. */
3095        private static String readGzipContent(String path) {
3096                File f = new File(path);
3097                if (!f.exists()) {
3098                        return null;
3099                }
3100                StringBuilder sb = new StringBuilder();
3101                try (java.io.BufferedReader reader = new java.io.BufferedReader(new java.io.InputStreamReader(
3102                                new java.util.zip.GZIPInputStream(new java.io.FileInputStream(f)),
3103                                java.nio.charset.StandardCharsets.UTF_8))) {
3104                        String line;
3105                        while ((line = reader.readLine()) != null) {
3106                                sb.append(line).append('\n');
3107                        }
3108                } catch (IOException e) {
3109                        logger.warn("Failed to read gzip source shard: " + path, e);
3110                        return null;
3111                }
3112                return sb.toString();
3113        }
3114
3115        /**
3116         * True when a sqlflow source record declares its DDL body unavailable
3117         * ({@code sourceUnavailable:true}). Robust to the JSON value arriving as a
3118         * boolean or a string. A record with no such field is available.
3119         */
3120        static boolean isSourceUnavailable(Map record) {
3121                if (record == null) {
3122                        return false;
3123                }
3124                Object v = record.get("sourceUnavailable");
3125                if (v instanceof Boolean) {
3126                        return (Boolean) v;
3127                }
3128                return v != null && "true".equalsIgnoreCase(v.toString().trim()); // non-identifier-compare: JSON boolean literal, not a DB name
3129        }
3130
3131        /**
3132         * The form of each group's names, by the group's sql hash: {@code CATALOG_STORED} for the stored
3133         * database and schema of a metadata query whose file declares it; absent for SQL spellings. The
3134         * group text stays the bare name; the form goes to ModelBindingManager with it.
3135         */
3136        private final Map<String, IdentifierInputForm> groupNameForms = new HashMap<String, IdentifierInputForm>();
3137
3138        private void appendSqlInfo(Map<String, Pair3<StringBuilder, AtomicInteger, String>> databaseMap, int index,
3139                        SqlInfo sqlInfo, Map queryObject) {
3140                // A lone query object may carry its input's declaration.
3141                appendSqlInfo(databaseMap, index, sqlInfo, queryObject,
3142                                queryObject == null ? IdentifierInputForm.SQL_LEXICAL : SqlflowNameForm.declared(queryObject));
3143        }
3144
3145        /**
3146         * @param nameForm how the input declares its names; the query's {@code database} and
3147         *        {@code schema} form its group in that form
3148         */
3149        private void appendSqlInfo(Map<String, Pair3<StringBuilder, AtomicInteger, String>> databaseMap, int index,
3150                        SqlInfo sqlInfo, Map queryObject, IdentifierInputForm nameForm) {
3151                if (queryObject == null) {
3152                        return;
3153                }
3154                EDbVendor vendor = option.getVendor();
3155                if (!SQLUtil.isEmpty(sqlInfo.getDbVendor())) {
3156                        vendor = EDbVendor.valueOf(sqlInfo.getDbVendor());
3157                }
3158
3159                boolean supportCatalog = TSQLEnv.supportCatalog(vendor);
3160                boolean supportSchema = TSQLEnv.supportSchema(vendor);
3161
3162                String groupName = (String) queryObject.get("groupName");
3163                if (DlineageUtil.isProcedureExcluded(groupName)) {
3164                        return;
3165                }
3166
3167                // sourceUnavailable: the DDL body was not retrieved (encrypted proc,
3168                // permission denied). Any sourceCode present is a placeholder or a
3169                // reason string, not the real definition, so it must NOT be parsed as
3170                // live SQL — doing so fabricates lineage from text that is not the
3171                // object's body.
3172                if (isSourceUnavailable(queryObject)) {
3173                        return;
3174                }
3175
3176                String content = (String) queryObject.get("sourceCode");
3177                if (SQLUtil.isEmpty(content)) {
3178                        return;
3179                }
3180                StringBuilder builder = new StringBuilder();
3181                if (supportCatalog) {
3182                        String database = (String) queryObject.get("database");
3183                        if (database.indexOf(".") != -1) {
3184                                database = SQLUtil.quoteDottedName(vendor, ESQLDataObjectType.dotCatalog, database);
3185                        }
3186                        builder.append(database);
3187                }
3188                if (supportSchema) {
3189                        String schema = (String) queryObject.get("schema");
3190                        if (schema.indexOf(".") != -1) {
3191                                schema = SQLUtil.quoteDottedName(vendor, ESQLDataObjectType.dotSchema, schema);
3192                        }
3193                        if (builder.length() > 0) {
3194                                builder.append(".");
3195                        }
3196                        builder.append(schema);
3197                }
3198                String group = builder.toString();
3199                String sqlHash = SHA256.getMd5(content);
3200                String hash = SHA256.getMd5(sqlHash);
3201                if (!databaseMap.containsKey(sqlHash)) {
3202                        databaseMap.put(sqlHash,
3203                                        new Pair3<StringBuilder, AtomicInteger, String>(new StringBuilder(), new AtomicInteger(), group));
3204                        if (nameForm == IdentifierInputForm.CATALOG_STORED) {
3205                                groupNameForms.put(sqlHash, nameForm);
3206                        }
3207                }
3208                String delimiterChar = String.valueOf(TGSqlParser.getDelimiterChar(option.getVendor()));
3209                StringBuilder buffer = new StringBuilder(content);
3210                if (content.trim().endsWith(delimiterChar) || content.trim().endsWith(";")) {
3211                        buffer.append("\n");
3212                } else if(vendor == EDbVendor.dbvredshift
3213                                || vendor == EDbVendor.dbvgaussdb
3214                                || vendor == EDbVendor.dbvedb
3215                                || vendor == EDbVendor.dbvpostgresql
3216                                || vendor == EDbVendor.dbvmysql
3217                                || vendor == EDbVendor.dbvteradata){
3218                        buffer.append("\n\n-- " + TBaseType.sqlflow_stmt_delimiter_str + "\n\n");
3219                } else{
3220                        SQLUtil.endTrim(buffer);
3221                        buffer.append(";").append("\n");
3222                }
3223
3224                int lineStart = databaseMap.get(sqlHash).first.toString().split("\n", -1).length - 1;
3225                if (databaseMap.get(sqlHash).first.toString().length() == 0) {
3226                        lineStart = 0;
3227                }
3228                if (commentBindingCollector != null) commentBindingCollector.register(sqlHash, buffer.toString(),
3229                databaseMap.get(sqlHash).first.length(), index, sqlInfo.getFileName(), true);
3230        databaseMap.get(sqlHash).first.append(buffer.toString());
3231                SqlInfo sqlInfoItem = new SqlInfo();
3232                sqlInfoItem.setServer(sqlInfo.getServer());
3233                sqlInfoItem.setDbVendor(sqlInfo.getDbVendor());
3234                sqlInfoItem.setFileName(sqlInfo.getFileName());
3235                sqlInfoItem.setFilePath(sqlInfo.getFilePath());
3236                sqlInfoItem.setSql(buffer.toString());
3237                sqlInfoItem.setOriginIndex(index);
3238                sqlInfoItem.setOriginLineStart(0);
3239                sqlInfoItem.setOriginLineEnd(buffer.toString().split("\n", -1).length - 1);
3240                sqlInfoItem.setIndex(databaseMap.get(sqlHash).second.getAndIncrement());
3241                sqlInfoItem.setLineStart(lineStart);
3242                sqlInfoItem.setLineEnd(databaseMap.get(sqlHash).first.toString().split("\n", -1).length - 1);
3243                sqlInfoItem.setGroup(group);
3244                sqlInfoItem.setHash(hash);
3245
3246                if (!sqlInfoMap.containsKey(hash)) {
3247                        sqlInfoMap.put(hash, new ArrayList<SqlInfo>());
3248                }
3249                sqlInfoMap.get(hash).add(sqlInfoItem);
3250        }
3251
3252        private void readGzipBlockSource(String fullPath, Map<String, Pair3<StringBuilder, AtomicInteger, String>> databaseMap, SqlInfo sqlInfo,
3253                        IdentifierInputForm nameForm) {
3254                try (FileInputStream fis = new FileInputStream(fullPath);
3255                         GZIPInputStream gzis = new GZIPInputStream(fis);
3256                         BufferedReader reader = new BufferedReader(new InputStreamReader(gzis, "UTF-8"))) {
3257                        String line;
3258                        int j = 0;
3259                        while ((line = reader.readLine()) != null) {
3260                                line = line.trim();
3261                                if (line.isEmpty()) {
3262                                        continue;
3263                                }
3264                                try {
3265                                        Map sourceObject = (Map) JSON.parseObject(line);
3266                                        String sourceCode = (String) sourceObject.get("sourceCode");
3267                                        if (sourceCode != null && !sourceCode.isEmpty()
3268                                                        && !isSourceUnavailable(sourceObject)) {
3269                                                SqlInfo sourceSqlInfo = new SqlInfo();
3270                                                sourceSqlInfo.setFileName(sqlInfo.getFileName());
3271                                                sourceSqlInfo.setFilePath(sqlInfo.getFilePath());
3272                                                sourceSqlInfo.setSql(sourceCode);
3273                                                sourceSqlInfo.setOriginIndex(j);
3274                                                // Pass the record (not null): appendSqlInfo dereferences it
3275                                                // for groupName/database/schema; null NPE'd and the catch
3276                                                // silently discarded every source record on this path.
3277                                                appendSqlInfo(databaseMap, j, sourceSqlInfo, sourceObject, nameForm);
3278                                        }
3279                                } catch (Exception e) {
3280                                        logger.warn("Parse gzip source jsonl line failed.", e);
3281                                }
3282                                j++;
3283                        }
3284                } catch (Exception e) {
3285                        logger.warn("Read gzip source file failed: " + fullPath, e);
3286                }
3287        }
3288
3289        static String getTextOutput(dataflow dataflow) {
3290                StringBuffer buffer = new StringBuffer();
3291                List<relationship> relations = dataflow.getRelationships();
3292                if (relations != null) {
3293                        for (int i = 0; i < relations.size(); i++) {
3294                                relationship relation = relations.get(i);
3295                                targetColumn target = relation.getTarget();
3296                                List<sourceColumn> sources = relation.getSources();
3297                                if (target != null && sources != null && sources.size() > 0) {
3298                                        buffer.append(target.getColumn()).append(" depends on: ");
3299                                        Set<String> columnSet = new LinkedHashSet<String>();
3300                                        for (int j = 0; j < sources.size(); j++) {
3301                                                sourceColumn sourceColumn = sources.get(j);
3302                                                String columnName = sourceColumn.getColumn();
3303                                                if (sourceColumn.getParent_name() != null && sourceColumn.getParent_name().length() > 0) {
3304                                                        columnName = sourceColumn.getParent_name() + "." + columnName;
3305                                                }
3306                                                columnSet.add(columnName);
3307                                        }
3308                                        String[] columns = columnSet.toArray(new String[0]);
3309                                        for (int j = 0; j < columns.length; j++) {
3310                                                buffer.append(columns[j]);
3311                                                if (j == columns.length - 1) {
3312                                                        buffer.append("\n");
3313                                                } else
3314                                                        buffer.append(", ");
3315                                        }
3316                                }
3317                        }
3318                }
3319                return buffer.toString();
3320        }
3321
3322        private String mergeRelationType(List<Pair<sourceColumn, List<String>>> typePaths) {
3323                RelationshipType relationType = RelationshipType.join;
3324                for (int i = 0; i < typePaths.size(); i++) {
3325                        List<String> path = typePaths.get(i).second;
3326                        RelationshipType type = RelationshipType.valueOf(getRelationType(path));
3327                        if (type.ordinal() < relationType.ordinal()) {
3328                                relationType = type;
3329                        }
3330                }
3331                return relationType.name();
3332        }
3333
3334        private String getRelationType(List<String> typePaths) {
3335                if (typePaths.contains("join"))
3336                        return "join";
3337                if (typePaths.contains("fdr"))
3338                        return "fdr";
3339                if (typePaths.contains("frd"))
3340                        return "frd";
3341                if (typePaths.contains("fddi"))
3342                        return "fddi";
3343                return "fdd";
3344        }
3345
3346        public dataflow getSimpleDataflow(dataflow instance, boolean simpleOutput) throws Exception {
3347                return getSimpleDataflow(instance, simpleOutput, Arrays.asList("fdd"));
3348        }
3349
3350        public dataflow getSimpleDataflow(dataflow instance, boolean simpleOutput, List<String> types) throws Exception {
3351                ModelBindingManager.setGlobalVendor(option.getVendor());
3352                allMap.clear();
3353                targetTables.clear();
3354                resultSetMap.clear();
3355                tableMap.clear();
3356                viewMap.clear();
3357                cursorMap.clear();
3358                variableMap.clear();
3359                fileMap.clear();
3360                stageMap.clear();
3361                sequenceMap.clear();
3362                dataSourceMap.clear();
3363                databaseMap.clear();
3364                schemaMap.clear();
3365                streamMap.clear();
3366                dataflow simple = new dataflow();
3367        simple.setCommentBindings(instance.getCommentBindings());
3368                List<relationship> simpleRelations = new ArrayList<relationship>();
3369                List<relationship> relations = instance.getRelationships();
3370                if (instance.getResultsets() != null) {
3371                        for (table t : instance.getResultsets()) {
3372                                resultSetMap.put(t.getId().toLowerCase(Locale.ROOT), t);
3373                                allMap.put(t.getId().toLowerCase(Locale.ROOT), t);
3374                        }
3375                        if (option.isSimpleRetainIntermediate()) {
3376                                for (table t : instance.getResultsets()) {
3377                                        if (t.isFunction() || t.getProcedureId() != null) {
3378                                                t.setIsTarget("true");
3379                                        }
3380                                }
3381                        }
3382                }
3383
3384                if (instance.getTables() != null) {
3385                        for (table t : instance.getTables()) {
3386                                tableMap.put(t.getId().toLowerCase(Locale.ROOT), t);
3387                                allMap.put(t.getId().toLowerCase(Locale.ROOT), t);
3388                        }
3389                }
3390
3391                if (instance.getViews() != null) {
3392                        for (table t : instance.getViews()) {
3393                                viewMap.put(t.getId().toLowerCase(Locale.ROOT), t);
3394                                allMap.put(t.getId().toLowerCase(Locale.ROOT), t);
3395                        }
3396                }
3397
3398                if (instance.getPaths() != null) {
3399                        for (table t : instance.getPaths()) {
3400                                fileMap.put(t.getId().toLowerCase(Locale.ROOT), t);
3401                                allMap.put(t.getId().toLowerCase(Locale.ROOT), t);
3402                        }
3403                }
3404
3405                if (instance.getStages() != null) {
3406                        for (table t : instance.getStages()) {
3407                                stageMap.put(t.getId().toLowerCase(Locale.ROOT), t);
3408                                allMap.put(t.getId().toLowerCase(Locale.ROOT), t);
3409                        }
3410                }
3411                
3412                if (instance.getSequences() != null) {
3413                        for (table t : instance.getSequences()) {
3414                                sequenceMap.put(t.getId().toLowerCase(Locale.ROOT), t);
3415                                allMap.put(t.getId().toLowerCase(Locale.ROOT), t);
3416                        }
3417                }
3418
3419                if (instance.getDatasources() != null) {
3420                        for (table t : instance.getDatasources()) {
3421                                dataSourceMap.put(t.getId().toLowerCase(Locale.ROOT), t);
3422                                allMap.put(t.getId().toLowerCase(Locale.ROOT), t);
3423                        }
3424                }
3425
3426                if (instance.getDatabases() != null) {
3427                        for (table t : instance.getDatabases()) {
3428                                databaseMap.put(t.getId().toLowerCase(Locale.ROOT), t);
3429                                allMap.put(t.getId().toLowerCase(Locale.ROOT), t);
3430                        }
3431                }
3432
3433                if (instance.getSchemas() != null) {
3434                        for (table t : instance.getSchemas()) {
3435                                schemaMap.put(t.getId().toLowerCase(Locale.ROOT), t);
3436                                allMap.put(t.getId().toLowerCase(Locale.ROOT), t);
3437                        }
3438                }
3439
3440                if (instance.getStreams() != null) {
3441                        for (table t : instance.getStreams()) {
3442                                streamMap.put(t.getId().toLowerCase(Locale.ROOT), t);
3443                                allMap.put(t.getId().toLowerCase(Locale.ROOT), t);
3444                        }
3445                }
3446
3447                if (instance.getVariables() != null) {
3448                        for (table t : instance.getVariables()) {
3449                                if(SubType.cursor.name().equals(t.getSubType())){
3450                                        cursorMap.put(t.getId().toLowerCase(Locale.ROOT), t);
3451                                }
3452                                else {
3453                                        variableMap.put(t.getId().toLowerCase(Locale.ROOT), t);
3454                                }
3455                                allMap.put(t.getId().toLowerCase(Locale.ROOT), t);
3456                        }
3457                }
3458
3459                if (relations != null) {
3460                        
3461                        List<relationship> filterRelations = new ArrayList<>();
3462                        for (relationship relationElem : relations) {
3463                                if (!types.contains(relationElem.getType())) {
3464                                        if (option.isSimpleRetainIntermediate()
3465                                                        && RelationshipType.call.name().equals(relationElem.getType())) {
3466                                                filterRelations.add(relationElem);
3467                                        }
3468                                        continue;
3469                                }
3470                                else {
3471                                        filterRelations.add(relationElem);
3472                                }
3473                        }
3474                        
3475                        relations = filterRelations;
3476                        
3477                        outputColumnsById = DlineageUtil.outputColumnsById(instance);
3478                        Map<String, Set<relationship>> targetIdRelationMap = new LinkedHashMap<String, Set<relationship>>();
3479                        for (relationship relation : relations) {
3480                                if (relation.getTarget() != null) {
3481                                        String key = relation.getTarget().getParent_id() + "." + relation.getTarget().getId();
3482                                        if (!targetIdRelationMap.containsKey(key)) {
3483                                                targetIdRelationMap.put(key, new TreeSet<relationship>(new Comparator<relationship>() {
3484                                                        @Override
3485                                                        public int compare(relationship o1, relationship o2) {
3486                                                                return o1.getId().compareTo(o2.getId());
3487                                                        }
3488                                                }));
3489                                        }
3490                                        targetIdRelationMap.get(key).add(relation);
3491                                }
3492                        }
3493
3494                        Iterator<String> keys = targetIdRelationMap.keySet().iterator();
3495                        while (keys.hasNext()) {
3496                                String key = keys.next();
3497                                if (targetIdRelationMap.get(key).size() > 500) {
3498                                        keys.remove();
3499                                }
3500                        }
3501
3502                        for (relationship relationElem : relations) {
3503                                if (RelationshipType.call.name().equals(relationElem.getType())) {
3504                                        continue;
3505                                }
3506                                if (RelationshipType.er.name().equals(relationElem.getType())) {
3507                                        continue;
3508                                }
3509                                targetColumn target = relationElem.getTarget();
3510                                String targetParent = target.getParent_id();
3511                                if (isTarget(instance, targetParent, simpleOutput)) {
3512                                        List<Pair<sourceColumn, List<String>>> relationSources = new ArrayList<Pair<sourceColumn, List<String>>>();
3513                                        Map<sourceColumn, String> filterClauses = new IdentityHashMap<sourceColumn, String>();
3514                                        findSourceRelations(target, instance, targetIdRelationMap, relationElem, relationSources,
3515                                                        filterClauses, new String[] { relationElem.getType() }, simpleOutput);
3516                                        if (relationSources.size() > 0) {
3517                                                Map<sourceColumn, List<Pair<sourceColumn, List<String>>>> columnMap = new LinkedHashMap<sourceColumn, List<Pair<sourceColumn, List<String>>>>();
3518                                                for (Pair<sourceColumn, List<String>> t : relationSources) {
3519                                                        sourceColumn key = ((Pair<sourceColumn, List<String>>) t).first;
3520                                                        if (!columnMap.containsKey(key)) {
3521                                                                columnMap.put(key, new ArrayList<Pair<sourceColumn, List<String>>>());
3522                                                        }
3523                                                        columnMap.get(key).add(t);
3524                                                }
3525                                                Iterator<sourceColumn> iter = columnMap.keySet().iterator();
3526                                                Map<String, List<sourceColumn>> relationSourceMap = new LinkedHashMap<String, List<sourceColumn>>();
3527                                                Set<sourceColumn> passthroughColumns = new HashSet<sourceColumn>();
3528                                                while (iter.hasNext()) {
3529                                                        sourceColumn key = iter.next();
3530                                                        String relationType = mergeRelationType(columnMap.get(key));
3531                                                        // The paths of that type justify the edge (Mantis 4867): its source is their copy, with
3532                                                        // their transforms, clause and passthrough tag, not those of a path of another type.
3533                                                        List<Pair<sourceColumn, List<String>>> typePaths = new ArrayList<Pair<sourceColumn, List<String>>>();
3534                                                        for (Pair<sourceColumn, List<String>> typePath : columnMap.get(key)) {
3535                                                                if (relationType.equals(getRelationType(typePath.second))) {
3536                                                                        typePaths.add(typePath);
3537                                                                }
3538                                                        }
3539                                                        sourceColumn column = typePaths.get(0).first;
3540                                                        if (RelationshipType.fdr.name().equals(relationType)) {
3541                                                                applyFilterClause(column, typePaths, filterClauses);
3542                                                        }
3543                                                        if (!relationSourceMap.containsKey(relationType)) {
3544                                                                relationSourceMap.put(relationType, new ArrayList<sourceColumn>());
3545                                                        }
3546                                                        relationSourceMap.get(relationType).add(column);
3547                                                        // Mark columns that reached this target through an assumed passthrough edge.
3548                                                        for (Pair<sourceColumn, List<String>> typePath : typePaths) {
3549                                                                if (typePath.second != null
3550                                                                        && typePath.second.contains(EffectType.external_script_passthrough.name())) {
3551                                                                        passthroughColumns.add(column);
3552                                                                        break;
3553                                                                }
3554                                                        }
3555                                                }
3556
3557                                                Iterator<String> sourceIter = relationSourceMap.keySet().iterator();
3558                                                while (sourceIter.hasNext()) {
3559                                                        String relationType = sourceIter.next();
3560                                                        relationship simpleRelation = (relationship) relationElem.clone();
3561                                                        List<sourceColumn> groupSources = relationSourceMap.get(relationType);
3562                                                        simpleRelation.setSources(groupSources);
3563                                                        simpleRelation.setType(relationType);
3564                                                        simpleRelation.setId(String.valueOf(++ModelBindingManager.get().RELATION_ID));
3565                                                        // If any source in this group reached the target through an assumed external-
3566                                                        // script passthrough edge, tag the flattened edge so simple output does not
3567                                                        // present assumed lineage as proven.
3568                                                        for (sourceColumn groupSource : groupSources) {
3569                                                                if (passthroughColumns.contains(groupSource)) {
3570                                                                        simpleRelation.setEffectType(EffectType.external_script_passthrough.name());
3571                                                                        break;
3572                                                                }
3573                                                        }
3574                                                        simpleRelations.add(simpleRelation);
3575                                                }
3576                                        }
3577                                }
3578                        }
3579                }
3580
3581                simple.setProcedures(instance.getProcedures());
3582                simple.setPackages(instance.getPackages());
3583                simple.setProcesses(instance.getProcesses());
3584                simple.setErrors(instance.getErrors());
3585
3586                List<table> tables = new ArrayList<table>();
3587                for (table t : instance.getTables()) {
3588                        if (!SQLUtil.isTempTable(t)) {
3589                                tables.add(t);
3590                        }
3591                        else {
3592                                if (option.isIgnoreTemporaryTable()) {
3593                                        continue;
3594                                }
3595                                else {
3596                                        tables.add(t);
3597                                }
3598                        }
3599                }
3600                simple.setStages(instance.getStages());
3601                simple.setSequences(instance.getSequences());
3602                simple.setDatasources(instance.getDatasources());
3603                simple.setStreams(instance.getStreams());
3604                simple.setPaths(instance.getPaths());
3605                simple.setTables(tables);
3606                simple.setViews(instance.getViews());
3607                if(option.isSimpleShowVariable()) {
3608                        simple.setVariables(instance.getVariables());
3609                }
3610                else if(option.isSimpleShowCursor()) {
3611                        simple.setVariables(instance.getVariables().stream().filter(t->SubType.cursor.name().equals(t.getSubType())).collect(Collectors.toList()));
3612                }
3613        else{
3614            simple.setVariables(new ArrayList());
3615        }
3616
3617        if (option.isSimpleRetainIntermediate() && instance.getVariables() != null) {
3618                        for (table var : instance.getVariables()) {
3619                                if (var.isTarget()) {
3620                    simple.getVariables().add(var);
3621                                }
3622                        }
3623                }
3624                if (instance.getResultsets() != null) {
3625                        List<table> resultSets = new ArrayList<table>();
3626                        for (int i = 0; i < instance.getResultsets().size(); i++) {
3627                                table resultSet = instance.getResultsets().get(i);
3628                                if (isTargetResultSet(instance, resultSet.getId(), simpleOutput)) {
3629                                        // special handle function #524 #296
3630                                        resultSets.add(resultSet);
3631                                }
3632                                else if (option.isSimpleRetainIntermediate() && (resultSet.isFunction() || resultSet.getProcedureId() != null)) {
3633                                        resultSet.setIsTarget("true");
3634                                        resultSets.add(resultSet);
3635                                }
3636                        }
3637                        simple.setResultsets(resultSets);
3638                }
3639
3640                List<table> functions = new ArrayList<table>();
3641                if (option.isShowCallRelation()) {
3642                        for (int i = 0; i < relations.size(); i++) {
3643                                relationship relationElem = relations.get(i);
3644                                if (!RelationshipType.call.name().equals(relationElem.getType())) {
3645                                        continue;
3646                                }
3647                                simpleRelations.add(relationElem);
3648                                for (sourceColumn callee : relationElem.getCallees()) {
3649                                        String calleeId = callee.getId();
3650                                        if (resultSetMap.containsKey(calleeId)) {
3651                                                table function = resultSetMap.get(calleeId);
3652                                                function.setIsTarget("true");
3653                                                functions.add(function);
3654                                        }
3655                                }
3656                        }
3657                }
3658
3659                if (option.isShowERDiagram()) {
3660                        for (int i = 0; i < relations.size(); i++) {
3661                                relationship relationElem = relations.get(i);
3662                                if (!RelationshipType.er.name().equals(relationElem.getType())) {
3663                                        continue;
3664                                }
3665                                simpleRelations.add(relationElem);
3666                                for (sourceColumn callee : relationElem.getCallees()) {
3667                                        String calleeId = callee.getId();
3668                                        if (resultSetMap.containsKey(calleeId)) {
3669                                                table function = resultSetMap.get(calleeId);
3670                                                function.setIsTarget("true");
3671                                                functions.add(function);
3672                                        }
3673                                }
3674                        }
3675                }
3676                
3677                if (option.isSimpleRetainIntermediate() && !option.isShowCallRelation()) {
3678                        for (int i = 0; i < relations.size(); i++) {
3679                                relationship relationElem = relations.get(i);
3680                                if (RelationshipType.call.name().equals(relationElem.getType())) {
3681                                        simpleRelations.add(relationElem);
3682                                }
3683                        }
3684                }
3685
3686                if (!functions.isEmpty()) {
3687                        if (simple.getResultsets() == null) {
3688                                simple.setResultsets(functions);
3689                        } else {
3690                                simple.getResultsets().addAll(functions);
3691                        }
3692                }
3693
3694                simple.setRelationships(simpleRelations);
3695                simple.setOrientation(instance.getOrientation());
3696
3697                targetTables.clear();
3698                resultSetMap.clear();
3699                tableMap.clear();
3700                viewMap.clear();
3701                cursorMap.clear();
3702                variableMap.clear();
3703                fileMap.clear();
3704                stageMap.clear();
3705                dataSourceMap.clear();
3706                databaseMap.clear();
3707                schemaMap.clear();
3708                streamMap.clear();
3709        CommentBindingProjection.retain(simple);
3710                // the simple output folds hops and drops coordinates, so two relationships of the full output can meet as one
3711                if (!endpointLabelsDeferred) {
3712                        DataflowUtility.labelEndpoints(simple);
3713                }
3714                return simple;
3715        }
3716
3717        private void findSourceRelations(targetColumn target, dataflow instance, Map<String, Set<relationship>> sourceIdRelationMap,
3718                        relationship targetRelation, List<Pair<sourceColumn, List<String>>> relationSources,
3719                        Map<sourceColumn, String> filterClauses, String[] pathTypes, boolean simpleOutput) {
3720                findStarSourceRelations(target, instance, null, false, sourceIdRelationMap, targetRelation, relationSources,
3721                                pathTypes, new HashMap<String, Integer>(), new LinkedHashSet<transform>(), new LinkedHashSet<candidateTable>(),
3722                                0, simpleOutput, null, filterClauses);
3723        }
3724
3725        /**
3726         * The rank {@link #mergeRelationType} gives a path ({@link #getRelationType}, as a {@link RelationshipType}
3727         * ordinal): the lower rank wins, so a value path (fdd) beats a path through a filter (fdr).
3728         */
3729        private int pathRank(String[] pathTypes) {
3730                return RelationshipType.valueOf(getRelationType(Arrays.asList(pathTypes))).ordinal();
3731        }
3732
3733        /**
3734         * The clause of the filter a folded source reaches its target through (Mantis 4866): the clauseType of the first
3735         * fdr hop, from the target, whose source is a WHERE, ON or HAVING operand. {@code t.x -fdd-> ABS.ABS -fdr(where)->
3736         * RelationRows} folds to {@code t.x -> RelationRows}, and t.x is read in that WHERE as it is in {@code WHERE x > 5}.
3737         */
3738        private static String filterHopClause(relationship relation, sourceColumn source, String inherited) {
3739                if (inherited != null || !RelationshipType.fdr.name().equals(relation.getType())) {
3740                        return inherited;
3741                }
3742                String clause = source.getClauseType();
3743                return ESqlClause.where.name().equals(clause) || ESqlClause.joinCondition.name().equals(clause)
3744                                || ESqlClause.having.name().equals(clause) ? clause : null;
3745        }
3746
3747        /**
3748         * Gives a folded source with no clause of its own the clause of the filter one of its paths passed through
3749         * ({@link #filterHopClause}). The caller asks only for an fdr edge: a source that also reaches the target as a
3750         * value is an fdd source, and a value has no clause.
3751         */
3752        private static void applyFilterClause(sourceColumn column, List<Pair<sourceColumn, List<String>>> typePaths,
3753                        Map<sourceColumn, String> filterClauses) {
3754                if (column.getClauseType() != null) {
3755                        return;
3756                }
3757                for (Pair<sourceColumn, List<String>> typePath : typePaths) {
3758                        String clause = filterClauses.get(typePath.first);
3759                        if (clause != null) {
3760                                column.setClauseType(clause);
3761                                return;
3762                        }
3763                }
3764        }
3765
3766        /**
3767         * @param positionUnproven whether the {@code *} the walk follows is read at a position its members' unknown
3768         *        order cannot tell ({@link sourceColumn#isPositionUnproven()}): the {@code *} of a table that fills it
3769         *        stands for it, never a column of that table named like {@code starRelationTarget} (Mantis 4856)
3770         * @param paths the best {@link #pathRank} each intermediate node was reached with in this walk, per star state the
3771         *        walk carries past it: a node is walked again only by a better path, so neither the type a source gets nor
3772         *        the sources reached depend on which path came first (Mantis 4867)
3773         * @param filterClause the clause of the filter the walk passed through ({@link #filterHopClause})
3774         * @param filterClauses receives that clause for each folded source copy, applied once the copy's relation type is
3775         *        known ({@link #applyFilterClause})
3776         */
3777        private void findStarSourceRelations(targetColumn target, dataflow instance, targetColumn starRelationTarget,
3778                        boolean positionUnproven, Map<String, Set<relationship>> sourceIdRelationMap, relationship targetRelation,
3779                        List<Pair<sourceColumn, List<String>>> relationSources, String[] pathTypes, Map<String, Integer> paths,
3780                        Set<transform> transforms, Set<candidateTable> candidateTables, int level, boolean simpleOutput,
3781                        String filterClause, Map<sourceColumn, String> filterClauses) {
3782                if (targetRelation != null && targetRelation.getSources() != null) {
3783
3784                        //获取source为*的Column Parent
3785                        String starParentId = null;
3786                        // the parents of every * the relation reads, when the walk's * has no member proven at the position
3787                        Set<String> starParentIds = positionUnproven ? new HashSet<String>() : Collections.<String>emptySet();
3788                        for (int i = 0; i < targetRelation.getSources().size(); i++) {
3789                                sourceColumn source = targetRelation.getSources().get(i);
3790                                if (starRelationTarget != null && DlineageUtil.isStarEndpoint(outputColumnsById, source.getId(), source.getColumn())) {
3791                                        starParentId = source.getParent_id();
3792                                        if (positionUnproven) {
3793                                                starParentIds.add(starParentId);
3794                                        }
3795                                }
3796                        }
3797
3798                        for (int i = 0; i < targetRelation.getSources().size(); i++) {
3799                                sourceColumn source = targetRelation.getSources().get(i);
3800                                if (positionUnproven && source.getParent_id() != null && starParentIds.contains(source.getParent_id())
3801                                                && !resultSetMap.containsKey(source.getParent_id().toLowerCase(Locale.ROOT))
3802                                                && !DlineageUtil.isStarEndpoint(outputColumnsById, source.getId(), source.getColumn())) {
3803                                        continue;
3804                                }
3805                                if (starRelationTarget != null && !DlineageUtil.isStarEndpoint(outputColumnsById, source.getId(), source.getColumn())
3806                                                && !DlineageUtil.sameColumnPath(starRelationTarget.getColumn(), source.getColumn())) {
3807                                        table parent = allMap.get(source.getParent_id());
3808                                        if (parent != null && isFunction(parent)) {
3809                                                // function返回值未知,不对星号做处理
3810                                        } 
3811                                        else if (parent == null) {
3812                                                continue;
3813                                        } else if(starParentId!=null && starParentId.equals(parent.getId())){
3814                                                //如果source和 * column的parent相同,则跳过
3815                                                continue;
3816                                        }
3817                                }
3818
3819                                String sourceColumnId = source.getId();
3820                                String sourceParentId = source.getParent_id();
3821                                if (sourceParentId == null || sourceColumnId == null) {
3822                                        continue;
3823                                }
3824                                String hopClause = filterHopClause(targetRelation, source, filterClause);
3825                                if (isTarget(instance, sourceParentId, simpleOutput)) {
3826                                        List<transform> transforms2 = new ArrayList<transform>(transforms.size());
3827                                        transforms2.addAll(transforms);
3828                                        Collections.reverse(transforms2);
3829                                        
3830                                        List<candidateTable> candidateTables2 = new ArrayList<candidateTable>(candidateTables.size());
3831                                        candidateTables2.addAll(candidateTables);
3832                                        
3833                                        sourceColumn sourceColumnCopy = DlineageUtil.copySourceColumn(source);
3834                                        if (hopClause != null) {
3835                                                filterClauses.put(sourceColumnCopy, hopClause);
3836                                        }
3837                                        for (transform t : transforms2) {
3838                                                sourceColumnCopy.addTransform(t);
3839                                        }
3840                                        
3841                                        for (candidateTable t : candidateTables2) {
3842                                                sourceColumnCopy.addCandidateParent(t);
3843                                        }
3844
3845                                        // A STRUCT field node folds onto the source its path reaches, whatever either one is named:
3846                                        // STRUCT(customer.name AS who) gives buyer.who <- customer.name, and the path of the full output
3847                                        // through the constructor's field is the proof. A test of the last part of the two names dropped
3848                                        // every field that renames what it reads (nested-access plan, section 14.6).
3849                                        relationSources.add(new Pair<sourceColumn, List<String>>(sourceColumnCopy, Arrays.asList(pathTypes)));
3850                                } else {
3851                                        Set<relationship> sourceRelations = sourceIdRelationMap
3852                                                        .get(source.getParent_id() + "." + source.getId());
3853                                        if (sourceRelations != null) {
3854                                                // the star state the walk carries past this node decides which sources it reaches there, so
3855                                                // a better path dominates another only in the same state
3856                                                boolean star = DlineageUtil.isStarEndpoint(outputColumnsById, source.getId(), source.getColumn());
3857                                                targetColumn nextStarTarget = !star ? null
3858                                                                : starRelationTarget == null ? targetRelation.getTarget() : starRelationTarget;
3859                                                boolean nextPositionUnproven = star && (positionUnproven || source.isPositionUnproven());
3860                                                String node = source.getParent_id() + "." + source.getId()
3861                                                                + (nextStarTarget == null ? "" : "|" + nextStarTarget.getParent_id() + "." + nextStarTarget.getId())
3862                                                                + (nextPositionUnproven ? "|unproven" : "");
3863                                                int rank = pathRank(pathTypes);
3864                                                Integer reached = paths.get(node);
3865                                                if (reached != null && reached <= rank) {
3866                                                        continue;
3867                                                } else {
3868                                                        paths.put(node, rank);
3869                                                        if (source.getTransforms() != null) {
3870                                                                transforms.addAll(source.getTransforms());
3871                                                        }
3872                                                        if (source.getCandidateParents() != null) {
3873                                                                candidateTables.addAll(source.getCandidateParents());
3874                                                        }
3875                                                }
3876                                                for (relationship relation : sourceRelations) {
3877                                                        LinkedHashSet<transform> transforms2 = new LinkedHashSet<transform>(transforms.size());
3878                                                        transforms2.addAll(transforms);
3879                                                        LinkedHashSet<candidateTable> candidateTables2 = new LinkedHashSet<candidateTable>(candidateTables.size());
3880                                                        candidateTables2.addAll(candidateTables);
3881                                                        // Carry an assumed external-script passthrough marker down the path
3882                                                        // (alongside the relationship type) so a flattened simple-output edge
3883                                                        // that traversed it can be tagged and never look proven. The marker is
3884                                                        // ignored by getRelationType, so type computation is unaffected.
3885                                                        boolean passthroughHop = EffectType.external_script_passthrough.name()
3886                                                                        .equals(relation.getEffectType());
3887                                                        String[] types = new String[pathTypes.length + (passthroughHop ? 2 : 1)];
3888                                                        int typeOffset = 0;
3889                                                        types[typeOffset++] = relation.getType();
3890                                                        if (passthroughHop) {
3891                                                                types[typeOffset++] = EffectType.external_script_passthrough.name();
3892                                                        }
3893                                                        System.arraycopy(pathTypes, 0, types, typeOffset, pathTypes.length);
3894                                                        if (!star) {
3895                                                                findStarSourceRelations(target, instance, null, false, sourceIdRelationMap, relation,
3896                                                                                relationSources, types, paths, transforms2, candidateTables2, level + 1, simpleOutput,
3897                                                                                hopClause, filterClauses);
3898                                                        } else {
3899                                                                findStarSourceRelations(target, instance, nextStarTarget, nextPositionUnproven,
3900                                                                                sourceIdRelationMap, relation, relationSources, types, paths, transforms,
3901                                                                                candidateTables2, level + 1, simpleOutput, hopClause, filterClauses);
3902                                                        }
3903                                                }
3904                                        }
3905                                }
3906                        }
3907                }
3908        }
3909
3910        private Map<String, Boolean> targetTables = new HashMap<String, Boolean>();
3911        /** The simple-output walk's published columns by id, for {@link DlineageUtil#isStarEndpoint}. */
3912        private Map<String, column> outputColumnsById = Collections.emptyMap();
3913        private Map<String, table> resultSetMap = new HashMap<String, table>();
3914        private Map<String, table> tableMap = new HashMap<String, table>();
3915        private Map<String, table> viewMap = new HashMap<String, table>();
3916        private Map<String, table> cursorMap = new HashMap<String, table>();
3917        private Map<String, table> variableMap = new HashMap<String, table>();
3918        private Map<String, table> fileMap = new HashMap<String, table>();
3919        private Map<String, table> stageMap = new HashMap<String, table>();
3920        private Map<String, table> sequenceMap = new HashMap<String, table>();
3921        private Map<String, table> dataSourceMap = new HashMap<String, table>();
3922        private Map<String, table> databaseMap = new HashMap<String, table>();
3923        private Map<String, table> schemaMap = new HashMap<String, table>();
3924        private Map<String, table> streamMap = new HashMap<String, table>();
3925        private Map<String, table> allMap = new HashMap<String, table>();
3926
3927        private boolean isTarget(dataflow instance, String targetParentId, boolean simpleOutput) {
3928                if (targetTables.containsKey(targetParentId))
3929                        return targetTables.get(targetParentId);
3930                if (isTable(instance, targetParentId)) {
3931                        targetTables.put(targetParentId, true);
3932                        return true;
3933                } else if (isView(instance, targetParentId)) {
3934                        targetTables.put(targetParentId, true);
3935                        return true;
3936                } else if (isFile(instance, targetParentId)) {
3937                        targetTables.put(targetParentId, true);
3938                        return true;
3939                } else if (isDatabase(instance, targetParentId)) {
3940                        targetTables.put(targetParentId, true);
3941                        return true;
3942                } else if (isSchema(instance, targetParentId)) {
3943                        targetTables.put(targetParentId, true);
3944                        return true;
3945                } else if (isStage(instance, targetParentId)) {
3946                        targetTables.put(targetParentId, true);
3947                        return true;
3948                } else if (isSequence(instance, targetParentId)) {
3949                        targetTables.put(targetParentId, true);
3950                        return true;
3951                } else if (isDataSource(instance, targetParentId)) {
3952                        targetTables.put(targetParentId, true);
3953                        return true;
3954                } else if (isStream(instance, targetParentId)) {
3955                        targetTables.put(targetParentId, true);
3956                        return true;
3957                } else if (isCursor(instance, targetParentId) && option.isSimpleShowCursor()) {
3958                        targetTables.put(targetParentId, true);
3959                        return true;
3960                } else if ((isVariable(instance, targetParentId) || isCursor(instance, targetParentId)) && option.isSimpleShowVariable()) {
3961                        targetTables.put(targetParentId, true);
3962                        return true;
3963                } else if ((isVariable(instance, targetParentId) || isCursor(instance, targetParentId)) && option.isSimpleRetainIntermediate()) {
3964                        table var = variableMap.get(targetParentId.toLowerCase(Locale.ROOT));
3965                        if (var != null && var.isTarget()) {
3966                                targetTables.put(targetParentId, true);
3967                                return true;
3968                        }
3969                } else if (isTargetResultSet(instance, targetParentId, simpleOutput)) {
3970                        targetTables.put(targetParentId, true);
3971                        return true;
3972                }
3973                targetTables.put(targetParentId, false);
3974                return false;
3975        }
3976
3977        private boolean isTargetResultSet(dataflow instance, String targetParent, boolean simpleOutput) {
3978                if (resultSetMap.containsKey(targetParent.toLowerCase(Locale.ROOT))) {
3979                        table result = resultSetMap.get(targetParent.toLowerCase(Locale.ROOT));
3980                        boolean isTarget = result.isTarget();
3981                        Option option = ModelBindingManager.getGlobalOption();
3982                        if (option != null && option.isSqlflowIgnoreFunction() && isFunction(result)) {
3983                                return false;
3984                        }
3985                        if (isTarget && simpleOutput) {
3986                                if (option != null && option.isSimpleShowFunction() && isFunction(result)) {
3987                                        return true;
3988
3989                                } else if (option != null && option.isSimpleShowTopSelectResultSet()) {
3990                                        return true;
3991                                }
3992                                if (option != null && option.isSimpleRetainIntermediate() && (isFunction(result) || result.getProcedureId() != null)) {
3993                                        return true;
3994                                }
3995                                if (ResultSetType.of(result.getType()) != null && option.containsResultSetType(ResultSetType.of(result.getType()))) {
3996                                        return true;
3997                                }
3998                        } else
3999                                return isTarget;
4000                }
4001                return false;
4002        }
4003
4004        private boolean isFunction(table resultSet) {
4005                if("function".equals(resultSet.getType())){
4006                        return true;
4007                }
4008                else if("resultset".equals(resultSet.getType()) && "function".equals(resultSet.getSubType())){
4009                        return true;
4010                }
4011                return false;
4012        }
4013
4014        private boolean isView(dataflow instance, String targetParent) {
4015                if (viewMap.containsKey(targetParent.toLowerCase(Locale.ROOT))) {
4016                        return true;
4017                }
4018                return false;
4019        }
4020
4021        private boolean isCursor(dataflow instance, String targetParent) {
4022                if (cursorMap.containsKey(targetParent.toLowerCase(Locale.ROOT))) {
4023                        return true;
4024                }
4025                return false;
4026        }
4027
4028        private boolean isVariable(dataflow instance, String targetParent) {
4029                if (variableMap.containsKey(targetParent.toLowerCase(Locale.ROOT))) {
4030                        return true;
4031                }
4032                return false;
4033        }
4034
4035        private boolean isFile(dataflow instance, String targetParent) {
4036                if (fileMap.containsKey(targetParent.toLowerCase(Locale.ROOT))) {
4037                        return true;
4038                }
4039                return false;
4040        }
4041
4042        private boolean isStage(dataflow instance, String targetParent) {
4043                if (stageMap.containsKey(targetParent.toLowerCase(Locale.ROOT))) {
4044                        return true;
4045                }
4046                return false;
4047        }
4048        
4049        private boolean isSequence(dataflow instance, String targetParent) {
4050                if (sequenceMap.containsKey(targetParent.toLowerCase(Locale.ROOT))) {
4051                        return true;
4052                }
4053                return false;
4054        }
4055
4056        private boolean isDataSource(dataflow instance, String targetParent) {
4057                if (dataSourceMap.containsKey(targetParent.toLowerCase(Locale.ROOT))) {
4058                        return true;
4059                }
4060                return false;
4061        }
4062
4063        private boolean isDatabase(dataflow instance, String targetParent) {
4064                if (databaseMap.containsKey(targetParent.toLowerCase(Locale.ROOT))) {
4065                        return true;
4066                }
4067                return false;
4068        }
4069
4070        private boolean isSchema(dataflow instance, String targetParent) {
4071                if (schemaMap.containsKey(targetParent.toLowerCase(Locale.ROOT))) {
4072                        return true;
4073                }
4074                return false;
4075        }
4076
4077        private boolean isStream(dataflow instance, String targetParent) {
4078                if (streamMap.containsKey(targetParent.toLowerCase(Locale.ROOT))) {
4079                        return true;
4080                }
4081                return false;
4082        }
4083
4084        private boolean isTable(dataflow instance, String targetParent) {
4085                if (tableMap.containsKey(targetParent.toLowerCase(Locale.ROOT))) {
4086                        if (SQLUtil.isTempTable(tableMap.get(targetParent))) {
4087                                if (option.isIgnoreTemporaryTable()) {
4088                                        return false;
4089                                }
4090                        }
4091                        if (tableMap.get(targetParent).isFunction()) {
4092                                if (option != null && option.isSimpleShowFunction()) {
4093                                        return true;
4094                                } else {
4095                                        return false;
4096                                }
4097                        }
4098                        if (SubType.synonym.name().equals(tableMap.get(targetParent).getSubType())) {
4099                                if (option != null && option.isSimpleShowSynonym()) {
4100                                        return true;
4101                                } else {
4102                                        return false;
4103                                }
4104                        }
4105                        return true;
4106                } else {
4107                        return false;
4108                }
4109        }
4110
4111        private void init() {
4112                arrayRelationNames.clear();
4113                statementOrdinals.clear();
4114                declarationStacks.clear();
4115                declarationEnds.clear();
4116                noOpCreates.clear();
4117                analyzedUnits = 0;
4118        if (commentBindingCollector != null) commentBindingCollector.clear();
4119        commentBindingCollector = option.isCollectCommentBindings() ? new CommentBindingCollector(option) : null;
4120                metadataErrors.clear();
4121                sqlInfoMap.clear();
4122                errorInfos.clear();
4123                dynamicSqlSites.clear();
4124                dynamicSiteIndexes.clear();
4125                dynamicInnerSiteIndexes.clear();
4126                cteSelectRelationKeys.clear();
4127                authoritativeEvidenceCollector.reset();
4128                authoritativeLineageEvidence = AuthoritativeLineageEvidence.empty();
4129                dynamicEvalCache.clear();
4130                routineCatalog = null;
4131                crossFileParseCache.clear();
4132                plsqlEvaluatedTexts.clear();
4133                deferredExecuteBindings.clear();
4134                dataflow = null;
4135                dataflowString = null;
4136                ModelBindingManager.removeGlobalDatabase();
4137                ModelBindingManager.removeGlobalSchema();
4138                ModelBindingManager.removeGlobalVendor();
4139                ModelBindingManager.removeGlobalSQLEnv();
4140                ModelBindingManager.removeGlobalHash();
4141                appendResultSets.clear();
4142                appendStarColumns.clear();
4143                appendTableStarColumns.clear();
4144                columnNameIndexCache.clear();
4145                resultSetColumnLookupCache.clear();
4146                tableColumnLookupCache.clear();
4147                collisionScanPending = false;
4148                modelManager.TABLE_COLUMN_ID = option.getStartId();
4149                modelManager.RELATION_ID = option.getStartId();
4150                modelManager.DISPLAY_ID.clear();
4151                modelManager.DISPLAY_NAME.clear();
4152                tableIds.clear();
4153                clickhouseExternalSourceModels.clear();
4154                selectIntoWithUnknownColumns.clear();
4155                ModelBindingManager.setGlobalVendor(option.getVendor());
4156                modelManager.reset();
4157        }
4158
4159        /**
4160         * The ErrorInfo of one syntax error, as the analysis records it; {@code note} is appended to the message (empty
4161         * for an ordinary error; routine recovery marks the statements it skipped).
4162         */
4163        private ErrorInfo syntaxErrorInfo(TSyntaxError error, String note) {
4164                ErrorInfo errorInfo = new ErrorInfo();
4165                errorInfo.setErrorType(ErrorInfo.SYNTAX_ERROR);
4166                errorInfo.setErrorMessage(getErrorMessage(error, ErrorInfo.SYNTAX_ERROR) + note);
4167                errorInfo.setStartPosition(new Pair3<Long, Long, String>(error.lineNo, error.columnNo,
4168                                ModelBindingManager.getGlobalHash()));
4169                String[] segments = error.tokentext.split("\n");
4170                if (segments.length <= 1) {
4171                        errorInfo.setEndPosition(new Pair3<Long, Long, String>(error.lineNo,
4172                                        error.columnNo + error.tokentext.length(),
4173                                        ModelBindingManager.getGlobalHash()));
4174                } else {
4175                        errorInfo.setEndPosition(
4176                                        new Pair3<Long, Long, String>(error.lineNo + segments.length - 1,
4177                                                        (long) segments[segments.length - 1].length() + 1,
4178                                                        ModelBindingManager.getGlobalHash()));
4179                }
4180                errorInfo.fillInfo(this);
4181                return errorInfo;
4182        }
4183
4184        /**
4185         * "statement at lines a-b" for the statement routine recovery skipped around {@code error}: the run of ignorable
4186         * tokens it left there, so a skipped statement shows how far it reached (a broken statement with no terminator
4187         * can take the next one with it). Plain "statement" when no such token is found.
4188         */
4189        private static String skippedStatement(TGSqlParser parser, TSyntaxError error) {
4190                TSourceTokenList tokens = parser.sourcetokenlist;
4191                for (int i = 0; tokens != null && i < tokens.size(); i++) {
4192                        TSourceToken token = tokens.get(i);
4193                        if (token.lineNo != error.lineNo || token.columnNo != error.columnNo
4194                                        || token.tokencode != TBaseType.sqlpluscmd) {
4195                                continue;
4196                        }
4197                        long first = token.lineNo, last = lastLineOf(token);
4198                        for (int j = i - 1; j >= 0; j--) {
4199                                TSourceToken previous = tokens.get(j);
4200                                if (!previous.issolidtoken()) continue;
4201                                if (previous.tokencode != TBaseType.sqlpluscmd) break;
4202                                first = previous.lineNo;
4203                        }
4204                        for (int j = i + 1; j < tokens.size(); j++) {
4205                                TSourceToken next = tokens.get(j);
4206                                if (!next.issolidtoken()) continue;
4207                                if (next.tokencode != TBaseType.sqlpluscmd) break;
4208                                last = lastLineOf(next);
4209                        }
4210                        return first == last ? "statement at line " + first : "statement at lines " + first + "-" + last;
4211                }
4212                return "statement";
4213        }
4214
4215        /** The line a token ends on: a string literal or comment can run over several. */
4216        private static long lastLineOf(TSourceToken token) {
4217                String text = token.getAstext();
4218                long line = token.lineNo;
4219                for (int i = 0; text != null && i < text.length(); i++) {
4220                        if (text.charAt(i) == '\n') line++;
4221                }
4222                return line;
4223        }
4224
4225        private String getErrorMessage(TSyntaxError error, String errorType) {
4226                String s = "", hint = "Syntax error";
4227                if (ErrorInfo.SYNTAX_HINT.equals(errorType)) {
4228                        hint = "Syntax hint";
4229                }
4230                if (error.hint.length() > 0)
4231                        hint = error.hint;
4232                s = s + hint + "(" + error.errorno + ") near: " + error.tokentext;
4233                s = s + "(" + error.lineNo;
4234                s = s + "," + error.columnNo + ")";
4235                return s;
4236        }
4237
4238//      boolean OLD_ENABLE_RESOLVER = TBaseType.isEnableResolver();
4239        private void analyzeAndOutputResult(TGSqlParser sqlparser) {
4240                try {
4241                        accessedSubqueries.clear();
4242                        accessedStatements.clear();
4243                        stmtStack.clear();
4244                        viewDDLMap.clear();
4245                        procedureDDLAliases.clear();
4246                        pendingOracleCallArguments.clear();
4247                        structObjectMap.clear();
4248                        usingColumnsByStatement.clear();
4249                        scopeBuildResult = null;
4250                        try {
4251                                if(sqlenv!=null) {
4252                                        sqlparser.setSqlEnv(sqlenv);
4253                                }
4254                                int result = sqlparser.parse();
4255                                if (modelManager.getSqllrObservations() != null) {
4256                                        modelManager.getSqllrObservations().parsedUnit(sqlparser, result);
4257                                }
4258                                TSQLResolver2 parsedResolver = sqlparser.getResolver2();
4259                                scopeBuildResult = parsedResolver != null ? parsedResolver.getScopeBuildResult() : null;
4260                                if (collisionScanPending) {
4261                                        collisionScanPending = false;
4262                                        publishContestedNormalNames(NormalNameCollisions.scanTokens(collisionService(),
4263                                                        Collections.singletonList(sqlparser.sourcetokenlist)));
4264                                }
4265                if (commentBindingCollector != null) commentBindingCollector.capture(commentSourceKey, sqlparser, result != 0);
4266                                if (result != 0) {
4267                                        ArrayList<TSyntaxError> errors = sqlparser.getSyntaxErrors();
4268                                        if ((errors == null || errors.isEmpty())
4269                                                        && sqlparser.getErrormessage() != null
4270                                                        && !sqlparser.getErrormessage().isEmpty()) {
4271                                                // A refusal with no syntax errors -- the trial size cap
4272                                                // is the one that behaves this way. Recording nothing
4273                                                // here turned the rejection into an ordinary EMPTY
4274                                                // lineage result, which reads as "this SQL has no
4275                                                // dependencies" rather than "this SQL was not analyzed".
4276                                                ErrorInfo errorInfo = new ErrorInfo();
4277                                                errorInfo.setErrorType(ErrorInfo.SYNTAX_ERROR);
4278                                                errorInfo.setErrorMessage(sqlparser.getErrormessage());
4279                                                errorInfo.setStartPosition(new Pair3<Long, Long, String>(1L, 1L,
4280                                                                ModelBindingManager.getGlobalHash()));
4281                                                errorInfo.setEndPosition(new Pair3<Long, Long, String>(1L, 1L,
4282                                                                ModelBindingManager.getGlobalHash()));
4283                                                errorInfo.fillInfo(this);
4284                                                errorInfos.add(errorInfo);
4285                                        }
4286                                        if (errors != null && !errors.isEmpty()) {
4287                                                for (int i = 0; i < errors.size(); i++) {
4288                                                        TSyntaxError error = errors.get(i);
4289                                                        ErrorInfo errorInfo = syntaxErrorInfo(error, "");
4290                                                        errorInfos.add(errorInfo);
4291                                                }
4292                                        }
4293                                }
4294                                // Statements routine recovery skipped (Option.setRecoverRoutineBodyErrors): the routine parsed and is
4295                                // analyzed with the rest of its body, but each skipped statement is still a syntax error of this SQL.
4296                                for (TSyntaxError error : sqlparser.getRecoveredSyntaxErrors()) {
4297                                        errorInfos.add(syntaxErrorInfo(error,
4298                                                        " (" + skippedStatement(sqlparser, error) + " skipped, rest of the routine analyzed)"));
4299                                }
4300                                if (option.getHandleListener() != null) {
4301                                        option.getHandleListener().endParse(result == 0);
4302                                }
4303                        } catch (Exception e) {
4304                                logger.error("analyze sql failed.", e);
4305                                if (option.getHandleListener() != null) {
4306                                        option.getHandleListener().endParse(false);
4307                                }
4308                                ErrorInfo errorInfo = new ErrorInfo();
4309                                errorInfo.setErrorType(ErrorInfo.ANALYZE_ERROR);
4310                                if (e.getMessage() == null) {
4311                                        if (e.getStackTrace() != null && e.getStackTrace().length > 0) {
4312                                                errorInfo
4313                                                                .setErrorMessage(e.getClass().getSimpleName() + ": " + e.getStackTrace()[0].toString());
4314                                        } else {
4315                                                errorInfo.setErrorMessage(e.getClass().getSimpleName());
4316                                        }
4317                                } else {
4318                                        errorInfo.setErrorMessage(e.getClass().getSimpleName() + ": " + e.getMessage());
4319                                }
4320                                errorInfo.fillInfo(this);
4321                                errorInfos.add(errorInfo);
4322                                return;
4323                        }
4324
4325                        
4326                        TSQLResolver2 resolver = sqlparser.getResolver2();
4327                        if (resolver != null && option.getVendor() == EDbVendor.dbvbigquery) {
4328                                ScopeBuildResult buildResult = resolver.getScopeBuildResult();
4329                                List<TObjectName> columns = buildResult.getAllColumnReferences();
4330                                for (TObjectName col : columns) {
4331                                        structObjectMap.putIfAbsent(col.getSourceTable(), new TObjectNameList());
4332                                        structObjectMap.get(col.getSourceTable()).addObjectName(col);
4333                                }
4334                        }
4335                        
4336                        if (option.getHandleListener() != null) {
4337                                option.getHandleListener().startAnalyzeDataFlow(sqlparser);
4338                        }
4339
4340                        ModelBindingManager.setSchemaUnsetStatements(collectSchemaUnsetStatements(sqlparser));
4341                        recordStatementOrdinals(sqlparser);
4342
4343                        for (int i = 0; i < sqlparser.getSqlstatements().size(); i++) {
4344                                if (option.getHandleListener() != null && option.getHandleListener().isCanceled()) {
4345                                        break;
4346                                }
4347
4348                                TCustomSqlStatement stmt = sqlparser.getSqlstatements().get(i);
4349                                if (stmt.getErrorCount() == 0) {
4350                                        if (stmt.getParentStmt() == null) {
4351                                                modelManager.collectSqlHash(stmt);
4352                                                if (stmt instanceof TUseDatabase || stmt instanceof TUseSchema
4353                                                                || stmt instanceof TCreateTableSqlStatement
4354                                                                || stmt instanceof TCreateExternalDataSourceStmt || stmt instanceof TCreateStageStmt
4355                                                                || stmt instanceof TMssqlCreateType
4356                                                                || stmt instanceof TMssqlDeclare
4357                                                                || stmt instanceof TPlsqlCreateType_Placeholder
4358                                                                || stmt instanceof TPlsqlCreateType
4359                                                                || stmt instanceof TPlsqlTableTypeDefStmt
4360                                                                || (stmt instanceof TCreateFunctionStmt && hasDb2ReturnStmt((TCreateFunctionStmt)stmt))
4361                                                                || (stmt instanceof TMssqlCreateFunction
4362                                                                                && (((TMssqlCreateFunction) stmt).getReturnTableDefinitions() != null
4363                                                                                                || ((TMssqlCreateFunction) stmt).getReturnStmt() != null))) {
4364                                                        boolean listen = false;
4365                                                        if (option.getHandleListener() != null && !accessedStatements.contains(stmt)) {
4366                                                                option.getHandleListener().startAnalyzeStatment(stmt);
4367                                                                listen = true;
4368                                                        }
4369                                                        analyzeCustomSqlStmt(stmt);
4370                                                        if (listen && option.getHandleListener() != null) {
4371                                                                option.getHandleListener().endAnalyzeStatment(stmt);
4372                                                        }
4373                                                }
4374                                        }
4375                                }
4376                        }
4377
4378                        for (int i = 0; i < sqlparser.sqlstatements.size(); i++) {
4379                                if (option.getHandleListener() != null && option.getHandleListener().isCanceled()) {
4380                                        break;
4381                                }
4382
4383                                TCustomSqlStatement stmt = sqlparser.getSqlstatements().get(i);
4384                                if (stmt.getErrorCount() == 0) {
4385                                        if (stmt.getParentStmt() == null) {
4386                                                if (stmt instanceof TUseDatabase 
4387                                                                || stmt instanceof TUseSchema
4388                                                                || stmt instanceof TCreateViewSqlStatement 
4389                                                                || stmt instanceof TCreateSynonymStmt 
4390                                                                || stmt instanceof TStoredProcedureSqlStatement) {
4391                                                        boolean listen = false;
4392                                                        if (option.getHandleListener() != null && !accessedStatements.contains(stmt)) {
4393                                                                option.getHandleListener().startAnalyzeStatment(stmt);
4394                                                                listen = true;
4395                                                        }
4396                                                        if (stmt instanceof TUseDatabase || stmt instanceof TUseSchema) {
4397                                                                analyzeCustomSqlStmt(stmt);
4398                                                        } else if (stmt instanceof TCreateViewSqlStatement) {
4399                                                                TCreateViewSqlStatement view = (TCreateViewSqlStatement) stmt;
4400                                                                if(view.getViewName()!=null) {
4401                                                                        viewDDLMap.put(DlineageUtil.getTableIdentityKey(view.getViewName().toString()), view);
4402                                                                }
4403                                                        } else if (stmt instanceof TCreateSynonymStmt) {
4404                                                                TCreateSynonymStmt synonym = (TCreateSynonymStmt) stmt;
4405                                                                if(synonym.getSynonymName()!=null) {
4406                                                                        viewDDLMap.put(DlineageUtil.getTableIdentityKey(synonym.getSynonymName().toString()), synonym);
4407                                                                }
4408                                                        } else if (stmt instanceof TStoredProcedureSqlStatement) {
4409                                                                TStoredProcedureSqlStatement procedure = (TStoredProcedureSqlStatement) stmt;
4410                                                                if(procedure.getStoredProcedureName() == null) {
4411                                                                        continue;
4412                                                                }
4413                                                                procedureDDLAliases.put(nameAliasKey(DlineageUtil.getProcedureOrFunctionName(procedure),
4414                                                                                RoutineAliases.argumentCount(procedure, ModelBindingManager.getGlobalVendor())), procedure);
4415                                                                if (gudusoft.gsqlparser.dlineage.routine.RoutineShadow.enabled()) {
4416                                                                        gudusoft.gsqlparser.dlineage.routine.RoutineShadow.definition(modelManager, procedure);
4417                                                                }
4418                                                        }
4419                                                        if (listen && option.getHandleListener() != null) {
4420                                                                option.getHandleListener().endAnalyzeStatment(stmt);
4421                                                        }
4422                                                }
4423                                        }
4424                                }
4425                        }
4426
4427                        // A view reading a table that an ALTER TABLE before it adds columns to, or reading such a view, and a synonym
4428                        // of such a view, is analyzed with the statements after the views, in script order: after the ALTER, so it
4429                        // reads the added columns, while the statements before the ALTER still see the table without them
4430                        // (Mantis 4846). A table is known by its name and by the catalog table TSQLResolver2 bound it to, so
4431                        // events and public.events are one table.
4432                        Set<TCustomSqlStatement> deferredViews = new LinkedHashSet<TCustomSqlStatement>();
4433                        Set<Object> alteredTables = new HashSet<Object>();
4434                        for (int i = 0; i < sqlparser.sqlstatements.size(); i++) {
4435                                if (option.getHandleListener() != null && option.getHandleListener().isCanceled()) {
4436                                        break;
4437                                }
4438
4439                                TCustomSqlStatement stmt = sqlparser.getSqlstatements().get(i);
4440                                if (stmt.getErrorCount() == 0) {
4441                                        if (stmt.getParentStmt() == null) {
4442                                                if (stmt instanceof TAlterTableStatement && addsColumns((TAlterTableStatement) stmt)) {
4443                                                        TAlterTableStatement alter = (TAlterTableStatement) stmt;
4444                                                        alteredTables.add(DlineageUtil.getTableIdentityKey(alter.getTableName().toString()));
4445                                                        if (alter.getTargetTable() != null && alter.getTargetTable().getResolvedTable() != null) {
4446                                                                alteredTables.add(alter.getTargetTable().getResolvedTable());
4447                                                        }
4448                                                }
4449                                                if ((stmt instanceof TCreateViewSqlStatement || stmt instanceof TCreateSynonymStmt)
4450                                                                && !alteredTables.isEmpty() && readsAlteredTable(stmt, alteredTables)) {
4451                                                        deferredViews.add(stmt);
4452                                                        TObjectName name = deferredName(stmt);
4453                                                        if (name != null) {
4454                                                                alteredTables.add(DlineageUtil.getTableIdentityKey(name.toString()));
4455                                                        }
4456                                                        continue;
4457                                                }
4458                                                if (stmt instanceof TUseDatabase 
4459                                                                || stmt instanceof TUseSchema
4460                                                                || stmt instanceof TCreateViewSqlStatement 
4461                                                                || stmt instanceof TCreateSynonymStmt) {
4462                                                        boolean listen = false;
4463                                                        if (option.getHandleListener() != null && !accessedStatements.contains(stmt)) {
4464                                                                option.getHandleListener().startAnalyzeStatment(stmt);
4465                                                                listen = true;
4466                                                        }
4467                                                        analyzeCustomSqlStmt(stmt);
4468                                                        if (listen && option.getHandleListener() != null) {
4469                                                                option.getHandleListener().endAnalyzeStatment(stmt);
4470                                                        }
4471                                                }
4472                                        }
4473                                }
4474                        }
4475
4476                        Set<TCustomSqlStatement> deferredRoutines = deferredRoutines(sqlparser, deferredViews);
4477                        for (int i = 0; i < sqlparser.sqlstatements.size(); i++) {
4478                                if (option.getHandleListener() != null && option.getHandleListener().isCanceled()) {
4479                                        break;
4480                                }
4481
4482                                TCustomSqlStatement stmt = sqlparser.getSqlstatements().get(i);
4483                                if (stmt.getErrorCount() == 0) {
4484                                        if (stmt.getParentStmt() == null) {
4485                                                if (deferredRoutines.contains(stmt)) {
4486                                                        continue;
4487                                                }
4488                                                if (stmt instanceof TUseDatabase || stmt instanceof TUseSchema
4489                                                                || stmt instanceof TStoredProcedureSqlStatement) {
4490                                                        boolean listen = false;
4491                                                        if (option.getHandleListener() != null && !accessedStatements.contains(stmt)) {
4492                                                                option.getHandleListener().startAnalyzeStatment(stmt);
4493                                                                listen = true;
4494                                                        }
4495                                                        if (stmt instanceof TPlsqlCreateTrigger)
4496                                                                continue;
4497                                                        analyzeDeferredViewsReadBy(stmt, deferredViews);
4498                                                        analyzeCustomSqlStmt(stmt);
4499                                                        if (listen && option.getHandleListener() != null) {
4500                                                                option.getHandleListener().endAnalyzeStatment(stmt);
4501                                                        }
4502                                                }
4503                                        }
4504                                }
4505                        }
4506
4507                        for (int i = 0; i < sqlparser.sqlstatements.size(); i++) {
4508                                if (option.getHandleListener() != null && option.getHandleListener().isCanceled()) {
4509                                        break;
4510                                }
4511
4512                                TCustomSqlStatement stmt = sqlparser.getSqlstatements().get(i);
4513
4514                                if (option.isIgnoreTopSelect()) {
4515                                        if ((stmt instanceof TSelectSqlStatement && ((TSelectSqlStatement)stmt).getIntoClause() == null && ((TSelectSqlStatement)stmt).getIntoTableClause() == null ) || stmt instanceof TRedshiftDeclare
4516                                                        || stmt instanceof TRedshiftDeclare) {
4517                                                continue;
4518                                        }
4519                                }
4520
4521                                if (stmt.getErrorCount() == 0) {
4522                                        if (stmt.getParentStmt() == null) {
4523                                                if ((!(stmt instanceof TCreateViewSqlStatement) || deferredViews.contains(stmt))
4524                                                                && !(stmt instanceof TCreateStageStmt)
4525                                                                && !(stmt instanceof TCreateExternalDataSourceStmt)
4526                                                                && !(stmt instanceof TMssqlDeclare)
4527                                                                && !(stmt instanceof TMssqlCreateFunction
4528                                                                                && ((TMssqlCreateFunction) stmt).getReturnTableDefinitions() != null)) {
4529                                                        boolean listen = false;
4530                                                        if (option.getHandleListener() != null && !accessedStatements.contains(stmt)) {
4531                                                                option.getHandleListener().startAnalyzeStatment(stmt);
4532                                                                listen = true;
4533                                                        }
4534                                                        analyzeDeferredViewsReadBy(stmt, deferredViews);
4535                                                        analyzeCustomSqlStmt(stmt);
4536                                                        if (listen && option.getHandleListener() != null) {
4537                                                                option.getHandleListener().endAnalyzeStatment(stmt);
4538                                                        }
4539                                                }
4540                                        }
4541                                }
4542                        }
4543
4544                        if (option.getHandleListener() != null) {
4545                                option.getHandleListener().endAnalyzeDataFlow(sqlparser);
4546                        }
4547
4548                        // Finalize pipelined function stitching after all passes
4549                        if (!modelManager.getPendingPipelinedCallSites().isEmpty() && sqlparser.getSqlstatements().size() > 0 && pipelinedAnalyzer != null) {
4550                                TCustomSqlStatement lastStmt = sqlparser.getSqlstatements().get(sqlparser.getSqlstatements().size() - 1);
4551                                stmtStack.push(lastStmt);
4552                                // This statement is a stand-in for a non-empty stack, not the call site these
4553                                // relationships came from; say so, so nothing reads provenance off it.
4554                                ModelBindingManager.setSyntheticStmtContext(true);
4555                                try {
4556                                        pipelinedAnalyzer.stitchPendingCallSites();
4557                                } catch (Exception ex) {
4558                                        // Don't let pipelined stitching failure break main flow
4559                                } finally {
4560                                        ModelBindingManager.setSyntheticStmtContext(false);
4561                                        stmtStack.pop();
4562                                }
4563                        }
4564                } catch (Throwable e) {
4565                        if (e instanceof java.lang.Error && sqllrProducerMode()) {
4566                                throw (java.lang.Error) e; // SQL-LR production never turns an Error into a partial document
4567                        }
4568                        logger.error("analyze sql failed.", e);
4569                        ErrorInfo errorInfo = new ErrorInfo();
4570                        errorInfo.setErrorType(ErrorInfo.ANALYZE_ERROR);
4571                        if (e.getMessage() == null) {
4572                                if (e.getStackTrace() != null && e.getStackTrace().length > 0) {
4573                                        errorInfo.setErrorMessage(e.getClass().getSimpleName() + ": " + e.getStackTrace()[0].toString());
4574                                } else {
4575                                        errorInfo.setErrorMessage(e.getClass().getSimpleName());
4576                                }
4577                        } else {
4578                                errorInfo.setErrorMessage(e.getClass().getSimpleName() + ": " + e.getMessage());
4579                        }
4580                        errorInfo.fillInfo(this);
4581                        errorInfos.add(errorInfo);
4582                }
4583
4584        }
4585
4586        private static boolean containsSame(List<TableColumn> columns, TableColumn column) {
4587                for (TableColumn c : columns) {
4588                        if (c == column) {
4589                                return true;
4590                        }
4591                }
4592                return false;
4593        }
4594
4595        /**
4596         * The routines analyzed with the statements after the routines, in script order (Mantis 4883), as such a view is
4597         * (Mantis 4846): behind an ALTER TABLE that adds columns, a routine reading the altered table, a view deferred for
4598         * it, or a table a deferred routine makes or fills (a later reader would see it without columns), and a routine
4599         * calling a deferred one, whose call would otherwise analyze it before the ALTER.
4600         */
4601        private Set<TCustomSqlStatement> deferredRoutines(TGSqlParser sqlparser, Set<TCustomSqlStatement> deferredViews) {
4602                Set<TCustomSqlStatement> deferred = new LinkedHashSet<TCustomSqlStatement>();
4603                Map<TCustomSqlStatement, Set<Object>> read = new IdentityHashMap<TCustomSqlStatement, Set<Object>>();
4604                Map<TCustomSqlStatement, Set<Object>> called = new IdentityHashMap<TCustomSqlStatement, Set<Object>>();
4605                Map<TCustomSqlStatement, Set<Object>> made = new IdentityHashMap<TCustomSqlStatement, Set<Object>>();
4606                for (boolean changed = true; changed;) {
4607                        changed = false;
4608                        Set<Object> deferredNames = new HashSet<Object>();
4609                        for (TCustomSqlStatement routine : deferred) {
4610                                addRoutineKey(deferredNames, DlineageUtil.getProcedureOrFunctionName((TStoredProcedureSqlStatement) routine));
4611                        }
4612                        Set<Object> altered = new HashSet<Object>();
4613                        for (int i = 0; i < sqlparser.sqlstatements.size(); i++) {
4614                                TCustomSqlStatement stmt = sqlparser.getSqlstatements().get(i);
4615                                if (stmt.getErrorCount() != 0 || stmt.getParentStmt() != null) {
4616                                        continue;
4617                                }
4618                                if (!(stmt instanceof TStoredProcedureSqlStatement)) {
4619                                        addAlteredTables(altered, stmt);
4620                                        if (deferredViews.contains(stmt) && deferredName(stmt) != null) {
4621                                                altered.add(DlineageUtil.getTableIdentityKey(deferredName(stmt).toString()));
4622                                        }
4623                                } else if (!(stmt instanceof TPlsqlCreateTrigger) && !altered.isEmpty()) {
4624                                        if (!read.containsKey(stmt)) {
4625                                                read.put(stmt, tablesRead(stmt));
4626                                                called.put(stmt, routinesCalled(stmt));
4627                                        }
4628                                        if (!deferred.contains(stmt) && (!Collections.disjoint(read.get(stmt), altered)
4629                                                        || !Collections.disjoint(called.get(stmt), deferredNames))) {
4630                                                deferred.add(stmt);
4631                                                changed = true;
4632                                        }
4633                                        if (deferred.contains(stmt)) {
4634                                                if (!made.containsKey(stmt)) {
4635                                                        made.put(stmt, tablesMade(stmt));
4636                                                }
4637                                                altered.addAll(made.get(stmt));
4638                                        }
4639                                }
4640                        }
4641                }
4642                return deferred;
4643        }
4644
4645        /** Adds the tables an ALTER TABLE adding columns changes, {@code stmt} itself or one in its block, to {@code altered}. */
4646        private static void addAlteredTables(final Set<Object> altered, TCustomSqlStatement stmt) {
4647                List<TAlterTableStatement> alters = new ArrayList<TAlterTableStatement>();
4648                if (stmt instanceof TAlterTableStatement) {
4649                        alters.add((TAlterTableStatement) stmt);
4650                } else if (stmt.getStatements() != null && stmt.getStatements().size() > 0) {
4651                        // a block: BEGIN ALTER TABLE ... END
4652                        final List<TAlterTableStatement> nested = alters;
4653                        stmt.acceptChildren(new TParseTreeVisitor() {
4654                                @Override
4655                                public void preVisit(TAlterTableStatement alter) {
4656                                        nested.add(alter);
4657                                }
4658                        });
4659                }
4660                for (TAlterTableStatement alter : alters) {
4661                        if (addsColumns(alter)) {
4662                                altered.add(DlineageUtil.getTableIdentityKey(alter.getTableName().toString()));
4663                                if (alter.getTargetTable() != null && alter.getTargetTable().getResolvedTable() != null) {
4664                                        altered.add(alter.getTargetTable().getResolvedTable());
4665                                }
4666                        }
4667                }
4668        }
4669
4670        /** The tables {@code stmt} makes: SELECT ... INTO targets and CREATE TABLE tables, by identity key. */
4671        private static Set<Object> tablesMade(TCustomSqlStatement stmt) {
4672                final Set<Object> keys = new HashSet<Object>();
4673                stmt.acceptChildren(new TParseTreeVisitor() {
4674                        @Override
4675                        public void preVisit(TSelectSqlStatement select) {
4676                                TIntoClause into = select.getIntoClause();
4677                                if (into != null && into.getIntoName() != null) {
4678                                        keys.add(DlineageUtil.getTableIdentityKey(into.getIntoName().toString()));
4679                                }
4680                                if (into != null && into.getExprList() != null) {
4681                                        for (int i = 0; i < into.getExprList().size(); i++) {
4682                                                keys.add(DlineageUtil.getTableIdentityKey(into.getExprList().getExpression(i).toString()));
4683                                        }
4684                                }
4685                        }
4686
4687                        @Override
4688                        public void preVisit(TCreateTableSqlStatement create) {
4689                                if (create.getTargetTable() != null && create.getTargetTable().getTableName() != null) {
4690                                        keys.add(DlineageUtil.getTableIdentityKey(create.getTargetTable().getTableName().toString()));
4691                                }
4692                        }
4693                });
4694                return keys;
4695        }
4696
4697        /** The routines {@code stmt} calls, by {@link #addRoutineKey}: EXEC, INSERT ... EXEC, CALL and function calls. */
4698        private Set<Object> routinesCalled(TCustomSqlStatement stmt) {
4699                final Set<Object> keys = new HashSet<Object>();
4700                stmt.acceptChildren(new TParseTreeVisitor() {
4701                        @Override
4702                        public void preVisit(TMssqlExecute execute) {
4703                                addRoutineKey(keys, execute.getModuleName());
4704                        }
4705
4706                        @Override
4707                        public void preVisit(TInsertSqlStatement insert) {
4708                                // acceptChildren does not visit the EXEC of INSERT ... EXEC
4709                                if (insert.getExecuteStmt() != null) {
4710                                        addRoutineKey(keys, insert.getExecuteStmt().getModuleName());
4711                                }
4712                        }
4713
4714                        @Override
4715                        public void preVisit(TCallStatement call) {
4716                                // nor the routine of CALL
4717                                addRoutineKey(keys, call.getRoutineName());
4718                                if (call.getRoutineExpr() != null && call.getRoutineExpr().getFunctionCall() != null) {
4719                                        addRoutineKey(keys, call.getRoutineExpr().getFunctionCall().getFunctionName());
4720                                }
4721                        }
4722
4723                        @Override
4724                        public void preVisit(TFunctionCall call) {
4725                                addRoutineKey(keys, call.getFunctionName());
4726                        }
4727                });
4728                return keys;
4729        }
4730
4731        /**
4732         * The routine identity key of the last part of {@code name}, which an unqualified call names a routine by; a schema
4733         * the call does not write is not compared, so a call can only defer one routine too many.
4734         */
4735        private void addRoutineKey(Set<Object> keys, TObjectName name) {
4736                if (name != null && name.getObjectString() != null) {
4737                        keys.add(SQLUtil.canonKey(option.getVendor(), ESQLDataObjectType.dotFunction, name.getObjectString()));
4738                }
4739        }
4740
4741        /** Whether {@code alter} adds a column: ADD COLUMN, ADD COLUMNS, ADD COLUMN IF NOT EXISTS. */
4742        private static boolean addsColumns(TAlterTableStatement alter) {
4743                if (alter.getTableName() == null || alter.getAlterTableOptionList() == null) {
4744                        return false;
4745                }
4746                for (int i = 0; i < alter.getAlterTableOptionList().size(); i++) {
4747                        EAlterTableOptionType type = alter.getAlterTableOptionList().getAlterTableOption(i).getOptionType();
4748                        if (type == EAlterTableOptionType.AddColumn || type == EAlterTableOptionType.addColumnIfNotExists) {
4749                                return true;
4750                        }
4751                }
4752                return false;
4753        }
4754
4755        /**
4756         * The tables {@code stmt} reads, anywhere in it: the identity key of each name and the catalog table TSQLResolver2
4757         * bound it to.
4758         */
4759        private static Set<Object> tablesRead(TCustomSqlStatement stmt) {
4760                final Set<Object> keys = new HashSet<Object>();
4761                stmt.acceptChildren(new TParseTreeVisitor() {
4762                        @Override
4763                        public void preVisit(TTable table) {
4764                                if (table.getTableName() != null) {
4765                                        keys.add(DlineageUtil.getTableIdentityKey(table.getTableName().toString()));
4766                                }
4767                                if (table.getResolvedTable() != null) {
4768                                        keys.add(table.getResolvedTable());
4769                                }
4770                        }
4771                });
4772                return keys;
4773        }
4774
4775        /** Whether the view {@code stmt} reads, or the synonym {@code stmt} names, a table of {@code alteredTables}. */
4776        private static boolean readsAlteredTable(TCustomSqlStatement stmt, Set<Object> alteredTables) {
4777                if (stmt instanceof TCreateSynonymStmt) {
4778                        TObjectName forName = ((TCreateSynonymStmt) stmt).getForName();
4779                        return forName != null && alteredTables.contains(DlineageUtil.getTableIdentityKey(forName.toString()));
4780                }
4781                return !Collections.disjoint(tablesRead(stmt), alteredTables);
4782        }
4783
4784        /** The name a deferred view or synonym defines. */
4785        private static TObjectName deferredName(TCustomSqlStatement stmt) {
4786                if (stmt instanceof TCreateViewSqlStatement) {
4787                        return ((TCreateViewSqlStatement) stmt).getViewName();
4788                }
4789                if (stmt instanceof TCreateSynonymStmt) {
4790                        return ((TCreateSynonymStmt) stmt).getSynonymName();
4791                }
4792                return null;
4793        }
4794
4795        /**
4796         * Analyzes, before {@code stmt}, each view of {@code deferredViews} that {@code stmt} reads and that is not analyzed
4797         * yet: a view is analyzed before a statement that reads it, as when it is not deferred (Mantis 4846).
4798         */
4799        private void analyzeDeferredViewsReadBy(TCustomSqlStatement stmt, Set<TCustomSqlStatement> deferredViews) {
4800                if (deferredViews.isEmpty() || deferredViews.contains(stmt) || accessedStatements.contains(stmt)) {
4801                        return;
4802                }
4803                Set<Object> read = null;
4804                for (TCustomSqlStatement view : deferredViews) {
4805                        TObjectName viewName = deferredName(view);
4806                        if (accessedStatements.contains(view) || viewName == null) {
4807                                continue;
4808                        }
4809                        if (read == null) {
4810                                read = tablesRead(stmt);
4811                        }
4812                        if (read.contains(DlineageUtil.getTableIdentityKey(viewName.toString()))) {
4813                                analyzeCustomSqlStmt(view);
4814                        }
4815                }
4816        }
4817
4818        private boolean hasDb2ReturnStmt(TCreateFunctionStmt stmt) {
4819                if (stmt.getReturnStmt() != null) {
4820                        return true;
4821                }
4822                if (stmt.getBodyStatements() != null) {
4823                        for (int i = 0; i < stmt.getBodyStatements().size(); i++) {
4824                                if(stmt.getBodyStatements().get(i) instanceof TDb2ReturnStmt) {
4825                                        return true;
4826                                }
4827                        }
4828                }
4829                return false;
4830        }
4831
4832
4833        /**
4834         * The statements that a Snowflake {@code USE <database>} left with no current
4835         * schema: everything after such a switch, up to the next schema switch.
4836         *
4837         * <p>Computed once, in statement order, because the analysis below walks the
4838         * list several times and replays the {@code USE} statements in every pass. A
4839         * running flag would still be set when a later pass reached a statement that
4840         * PRECEDES the switch, and would strip that statement's already-exact
4841         * qualification. Keyed by statement, the answer does not depend on the pass.
4842         *
4843         * <p>Only Snowflake can populate this, so no other vendor's output moves.
4844         * See GitHub #715 / MantisBT 4677.
4845         */
4846        private Set<TCustomSqlStatement> collectSchemaUnsetStatements(TGSqlParser sqlparser) {
4847                Set<TCustomSqlStatement> unset = new HashSet<TCustomSqlStatement>();
4848                if (sqlparser == null || sqlparser.getSqlstatements() == null) {
4849                        return unset;
4850                }
4851                boolean schemaUnset = false;
4852                for (int i = 0; i < sqlparser.getSqlstatements().size(); i++) {
4853                        TCustomSqlStatement stmt = sqlparser.getSqlstatements().get(i);
4854                        if (stmt == null) {
4855                                continue;
4856                        }
4857                        if (schemaUnset) {
4858                                unset.add(stmt);
4859                        }
4860                        if (stmt.dbvendor != EDbVendor.dbvsnowflake) {
4861                                continue;
4862                        }
4863                        if (stmt instanceof TUseSchema || stmt.sqlstatementtype == ESqlStatementType.sstSetSchema) {
4864                                schemaUnset = false;
4865                        } else if (stmt instanceof TUseDatabase) {
4866                                schemaUnset = !((TUseDatabase) stmt).isSchema();
4867                        }
4868                }
4869                return unset;
4870        }
4871
4872        private int parseWithCommentBindings(TGSqlParser parser) {
4873        int result = parser.parse();
4874        if (commentBindingCollector != null) commentBindingCollector.captureDerived(parser, result != 0);
4875        return result;
4876    }
4877
4878    private void analyzeCustomSqlStmt(TCustomSqlStatement stmt) {
4879                analysisPath.add(stmt);
4880                Long ordinal = stmt == null ? null : statementOrdinals.get(stmt);
4881                long previousOrdinal = modelManager.getCurrentStatementOrdinal();
4882                if (ordinal != null) {
4883                        modelManager.setCurrentStatementOrdinal(ordinal);
4884                }
4885                SqllrObservations sqllr = modelManager.getSqllrObservations();
4886                boolean sqllrTop = sqllr != null && stmtStack.isEmpty();
4887                int sqllrDepth = stmtStack.size();
4888                Throwable sqllrFailure = null;
4889                if (sqllr != null) {
4890                        // every statement the analysis enters, so its place on the stack is known even when it records nothing
4891                        sqllr.enterStatement(stmt);
4892                }
4893                try {
4894                        if (sqllr != null && dynamicFoldDepth > 0) {
4895                                // a statement of dynamic SQL text: the statement before it on the path parsed it (SQL-LR derived units)
4896                                sqllr.derivedStatement(stmt, analysisPath.size() > 1 ? analysisPath.get(analysisPath.size() - 2)
4897                                                : null);
4898                        }
4899                        if (sqllrTop) {
4900                                sqllr.beginStatement(stmt);
4901                        }
4902                        analyzeCustomSqlStmtOnPath(stmt);
4903                        analyzeUnreadDataChangingCtes(stmt);
4904                        if (sqllrTop) {
4905                                sqllr.checkInjectedFailure(stmt, false);
4906                        }
4907                } catch (RuntimeException e) {
4908                        sqllrFailure = e;
4909                        if (!sqllrTop || !sqllr.isProducerMode()) {
4910                                throw e;
4911                        }
4912                        // SQL-LR production isolates a failed top-level statement: the statement is reported FAILED and its
4913                        // partial relationships are not published; the statements after it are still analyzed. Every other run
4914                        // (no recorder, or an observe-only one) keeps failing as before.
4915                        while (stmtStack.size() > sqllrDepth) {
4916                                stmtStack.pop();
4917                        }
4918                } catch (java.lang.Error e) {
4919                        sqllrFailure = e;
4920                        throw e;
4921                } finally {
4922                        if (sqllrTop) {
4923                                sqllr.endStatement(stmt, sqllrFailure);
4924                        }
4925                        modelManager.setCurrentStatementOrdinal(previousOrdinal);
4926                        analysisPath.remove(analysisPath.size() - 1);
4927                }
4928        }
4929
4930        /**
4931         * A data-changing statement in a CTE runs whether or not the statement reads the CTE (PostgreSQL executes every
4932         * data-modifying statement in WITH exactly once): its body is analyzed where a FROM item reads the CTE, and here,
4933         * after the statement, when nothing did, so the writes of {@code WITH x AS (INSERT INTO t SELECT a FROM s)
4934         * SELECT 1} are not lost (plan 09). The statement stays on the stack: the body is part of it, never a statement of
4935         * its own.
4936         */
4937        private void analyzeUnreadDataChangingCtes(TCustomSqlStatement stmt) {
4938                if (stmt == null || stmt.getCteList() == null) {
4939                        return;
4940                }
4941                for (int i = 0; i < stmt.getCteList().size(); i++) {
4942                        TCTE cte = stmt.getCteList().getCTE(i);
4943                        if (cte == null || cte.getSubquery() != null) {
4944                                continue;
4945                        }
4946                        TCustomSqlStatement body = cte.getUpdateStmt() != null ? cte.getUpdateStmt()
4947                                        : cte.getInsertStmt() != null ? cte.getInsertStmt()
4948                                        : cte.getDeleteStmt() != null ? cte.getDeleteStmt() : cte.getMergeStmt();
4949                        if (body == null || accessedStatements.contains(body)) {
4950                                continue;
4951                        }
4952                        stmtStack.push(stmt);
4953                        try {
4954                                analyzeCustomSqlStmt(body);
4955                        } finally {
4956                                stmtStack.pop();
4957                        }
4958                }
4959        }
4960
4961        /**
4962         * Plan rule R10: the position of each top-level statement in the script, the unit first and the statement in
4963         * the unit second, so a read is validated against the CREATE TABLE in force where it stands even though every
4964         * CREATE TABLE of a unit is analyzed before its other statements.
4965         */
4966        private final Map<TCustomSqlStatement, Long> statementOrdinals = new IdentityHashMap<TCustomSqlStatement, Long>();
4967
4968        /**
4969         * Plan rule R10: the lifetime of each top-level CREATE TABLE's declaration, found by pairing the script's CREATE
4970         * and DROP TABLE statements per table name as a stack, in script order: a DROP ends the declaration on top (a
4971         * temporary table's, which shadows a permanent one of its name, before the permanent one); a CREATE ... IF NOT
4972         * EXISTS of a table that exists declares nothing; a plain CREATE of a permanent table that exists replaces it.
4973         */
4974        private final Map<Object, Deque<long[]>> declarationStacks = new HashMap<Object, Deque<long[]>>();
4975
4976        /** The ordinal at which each CREATE TABLE's declaration ends, by the CREATE's ordinal. */
4977        private final Map<Long, Long> declarationEnds = new HashMap<Long, Long>();
4978
4979        /** The CREATE TABLE ... IF NOT EXISTS statements whose table already existed: they declare nothing. */
4980        private final Set<Long> noOpCreates = new HashSet<Long>();
4981
4982        /** How many units have been analyzed: the first part of a statement ordinal. */
4983        private long analyzedUnits = 0;
4984
4985        private static final long STATEMENTS_PER_UNIT = 1L << 32;
4986
4987        private void recordStatementOrdinals(TGSqlParser sqlparser) {
4988                long unit = analyzedUnits++;
4989                unitParser = sqlparser;
4990                unitTableNames = null;
4991                for (int i = 0; i < sqlparser.getSqlstatements().size(); i++) {
4992                        TCustomSqlStatement stmt = sqlparser.getSqlstatements().get(i);
4993                        long ordinal = unit * STATEMENTS_PER_UNIT + i;
4994                        statementOrdinals.put(stmt, ordinal);
4995                        if (stmt instanceof TCreateTableSqlStatement && ((TCreateTableSqlStatement) stmt).getTargetTable() != null
4996                                        && ((TCreateTableSqlStatement) stmt).getTargetTable().getTableName() != null) {
4997                                TCreateTableSqlStatement create = (TCreateTableSqlStatement) stmt;
4998                                Deque<long[]> stack = declarationStack(create.getTargetTable().getTableName().toString());
4999                                boolean temporary = isTemporaryTable(create);
5000                                if (create.isIfNotExists() && !stack.isEmpty()) {
5001                                        noOpCreates.add(ordinal);
5002                                } else {
5003                                        if (!temporary && !stack.isEmpty() && stack.peek()[1] == 0) {
5004                                                // a permanent table created again replaces the one it names
5005                                                declarationEnds.put(stack.pop()[0], ordinal);
5006                                        }
5007                                        stack.push(new long[]{ordinal, temporary ? 1 : 0});
5008                                }
5009                        } else if (stmt instanceof TDropTableSqlStatement && ((TDropTableSqlStatement) stmt).getTargetTable() != null
5010                                        && ((TDropTableSqlStatement) stmt).getTargetTable().getTableName() != null) {
5011                                Deque<long[]> stack = declarationStack(
5012                                                ((TDropTableSqlStatement) stmt).getTargetTable().getTableName().toString());
5013                                if (!stack.isEmpty()) {
5014                                        declarationEnds.put(stack.pop()[0], ordinal);
5015                                }
5016                        }
5017                }
5018        }
5019
5020        private Deque<long[]> declarationStack(String tableName) {
5021                Object key = DlineageUtil.getTableIdentityKey(tableName);
5022                Deque<long[]> stack = declarationStacks.get(key);
5023                if (stack == null) {
5024                        stack = new ArrayDeque<long[]>();
5025                        declarationStacks.put(key, stack);
5026                }
5027                return stack;
5028        }
5029
5030        private static boolean isTemporaryTable(TCreateTableSqlStatement create) {
5031                EnumSet<ETableKind> kinds = create.getTableKinds();
5032                if (kinds == null) {
5033                        return false;
5034                }
5035                for (ETableKind kind : kinds) {
5036                        switch (kind) {
5037                        case etkTemporary:
5038                        case etkGlobalTemporary:
5039                        case etkLocalTemporary:
5040                        case etkTemp:
5041                        case etkGlobalTemp:
5042                        case etkLocalTemp:
5043                        case etkVolatile:
5044                        case etkLocalVolatile:
5045                        case etkGlobalVolatile:
5046                        case etkPrivateTemporary:
5047                        case etkScopedTemporary:
5048                                return true;
5049                        default:
5050                                break;
5051                        }
5052                }
5053                return false;
5054        }
5055
5056        /** The ordinal at which the declaration of the CREATE TABLE {@code created} ends, else NEVER_DROPPED. */
5057        private long declarationEnd(long created) {
5058                Long end = created < 0 ? null : declarationEnds.get(created);
5059                return end != null ? end : gudusoft.gsqlparser.dlineage.dataflow.model.FieldDeclaration.NEVER_DROPPED;
5060        }
5061
5062    private void analyzeCustomSqlStmtOnPath(TCustomSqlStatement stmt) {
5063                if (stmt != null && dynamicFoldDepth == 0 && anyDynamicTemplateMarked) {
5064                        // A STATIC statement analyzed after a fold marked something: its references
5065                        // withdraw marks on shared nodes (see the field javadoc).
5066                        applyStaticTemplateVetoes(stmt);
5067                }
5068                if (!accessedStatements.contains(stmt)) {
5069                        accessedStatements.add(stmt);
5070                } else if (!(stmt instanceof TUseDatabase || stmt instanceof TUseSchema)) {
5071                        return;
5072                }
5073
5074                ArrayList<TSyntaxError> errors = stmt.getSyntaxHints();
5075                if (errors != null && !errors.isEmpty()) {
5076                        for (int i = 0; i < errors.size(); i++) {
5077                                TSyntaxError error = errors.get(i);
5078                                ErrorInfo errorInfo = new ErrorInfo();
5079                                errorInfo.setErrorType(ErrorInfo.SYNTAX_HINT);
5080                                errorInfo.setErrorMessage(getErrorMessage(error, ErrorInfo.SYNTAX_HINT));
5081                                errorInfo.setStartPosition(new Pair3<Long, Long, String>(error.lineNo, error.columnNo,
5082                                                ModelBindingManager.getGlobalHash()));
5083                                String[] segments = error.tokentext.split("\n");
5084                                if (segments.length == 1) {
5085                                        errorInfo.setEndPosition(new Pair3<Long, Long, String>(error.lineNo,
5086                                                        error.columnNo + error.tokentext.length(), ModelBindingManager.getGlobalHash()));
5087                                } else {
5088                                        errorInfo.setEndPosition(new Pair3<Long, Long, String>(error.lineNo + segments.length - 1,
5089                                                        (long) segments[segments.length - 1].length() + 1, ModelBindingManager.getGlobalHash()));
5090                                }
5091                                errorInfo.fillInfo(this);
5092                                errorInfos.add(errorInfo);
5093                        }
5094                }
5095
5096                if (option.getAnalyzeMode() == AnalyzeMode.dynamic) {
5097                        if (!(stmt instanceof TStoredProcedureSqlStatement
5098                                        || stmt instanceof TExecuteSqlStatement
5099                                        || stmt instanceof TMssqlExecute
5100                                        || stmt instanceof TExecImmeStmt)) {
5101                                return;
5102                        }
5103                }
5104
5105                if(DlineageUtil.getTopStmt(stmt) == stmt){
5106                        modelManager.collectSqlHash(stmt);
5107                }
5108
5109                try {
5110                        if (stmt instanceof TUseDatabase) {
5111                                if (((TUseDatabase) stmt).getDatabaseName() != null) {
5112                                        // Snowflake: a database switch invalidates the schema carried
5113                                        // over from the PREVIOUS database. Leaving it attached makes
5114                                        // later unqualified names resolve as <newdb>.<oldschema>, a
5115                                        // pair that need not exist. Clear rather than assign PUBLIC:
5116                                        // that the old schema is invalid is provable from the
5117                                        // statement, that PUBLIC exists in the new database is not.
5118                                        // Matches TDDLSQLEnv.analyzeUseDatabase and
5119                                        // DatabaseContextTracker.processUseDatabase. See GitHub #715.
5120                                        //
5121                                        // The clearing happens HERE, in the statement-by-statement
5122                                        // analysis, and NOT in TUseDatabase.doParseStatement: the whole
5123                                        // batch is parsed before any of it is analyzed and one TSQLEnv
5124                                        // is shared across it, so a parse-time clear would also apply
5125                                        // to the statements BEFORE the switch and would downgrade their
5126                                        // already-exact names to unqualified ones. The env fallback is
5127                                        // handled per statement instead - see
5128                                        // ModelBindingManager.setSchemaUnsetStatements.
5129                                        if (stmt.dbvendor == EDbVendor.dbvsnowflake
5130                                                        && !((TUseDatabase) stmt).isSchema()) {
5131                                                ModelBindingManager.removeGlobalSchema();
5132                                        }
5133                                        ModelBindingManager.setGlobalDatabase(((TUseDatabase) stmt).getDatabaseName().toString());
5134                                }
5135                        } else if (stmt instanceof TUseSchema) {
5136                                if (((TUseSchema) stmt).getSchemaName() != null) {
5137                                        String schemaName = ((TUseSchema) stmt).getSchemaName().toString();
5138                                        List<String> splits = SQLUtil.parseNames(schemaName);
5139                                        if (splits.size() == 1) {
5140                                                ModelBindingManager.setGlobalSchema(schemaName);
5141                                        } else if (splits.size() > 1) {
5142                                                ModelBindingManager.setGlobalSchema(splits.get(splits.size() - 1));
5143                                                ModelBindingManager.setGlobalDatabase(splits.get(splits.size() - 2));
5144                                        }
5145                                }
5146                        } else if (stmt instanceof TPlsqlRecordTypeDefStmt) {
5147                                this.stmtStack.push(stmt);
5148                                this.analyzePlsqlRecordTypeDefStmt((TPlsqlRecordTypeDefStmt) stmt);
5149                                this.stmtStack.pop();
5150                        } else if (stmt instanceof TPlsqlCreateType_Placeholder) {
5151                                this.stmtStack.push(stmt);
5152                                TPlsqlCreateType_Placeholder placeholder = (TPlsqlCreateType_Placeholder) stmt;
5153                                if (placeholder.getObjectStatement() != null && pipelinedAnalyzer != null) {
5154                                        this.pipelinedAnalyzer.indexObjectType(placeholder.getObjectStatement());
5155                                }
5156                                if (placeholder.getNestedTableStatement() != null) {
5157                                        if (pipelinedAnalyzer != null) {
5158                                                this.pipelinedAnalyzer.indexCollectionType(placeholder.getNestedTableStatement());
5159                                        }
5160                                        this.analyzePlsqlTableTypeDefStmt(placeholder.getNestedTableStatement());
5161                                }
5162                                this.stmtStack.pop();
5163                        } else if (stmt instanceof TPlsqlCreateType) {
5164                                this.stmtStack.push(stmt);
5165                                if (pipelinedAnalyzer != null) {
5166                                        this.pipelinedAnalyzer.indexObjectType((TPlsqlCreateType) stmt);
5167                                }
5168                                this.stmtStack.pop();
5169                        } else if (stmt instanceof TPlsqlTableTypeDefStmt) {
5170                                this.stmtStack.push(stmt);
5171                                this.analyzePlsqlTableTypeDefStmt((TPlsqlTableTypeDefStmt) stmt);
5172                                if (pipelinedAnalyzer != null) {
5173                                        this.pipelinedAnalyzer.indexCollectionType((TPlsqlTableTypeDefStmt) stmt);
5174                                }
5175                                this.stmtStack.pop();
5176                        } else if (stmt instanceof TStoredProcedureSqlStatement) {
5177                                this.stmtStack.push(stmt);
5178                                this.analyzeStoredProcedureStmt((TStoredProcedureSqlStatement) stmt);
5179                                this.stmtStack.pop();
5180                        } else if (stmt instanceof TCreateTableSqlStatement) {
5181                                stmtStack.push(stmt);
5182                                analyzeCreateTableStmt((TCreateTableSqlStatement) stmt);
5183                                stmtStack.pop();
5184                        } else if (stmt instanceof TCreateStageStmt) {
5185                                stmtStack.push(stmt);
5186                                analyzeCreateStageStmt((TCreateStageStmt) stmt);
5187                                stmtStack.pop();
5188                        } else if (stmt instanceof TCreateExternalDataSourceStmt) {
5189                                stmtStack.push(stmt);
5190                                analyzeCreateExternalDataSourceStmt((TCreateExternalDataSourceStmt) stmt);
5191                                stmtStack.pop();
5192                        } else if (stmt instanceof TCreateStreamStmt) {
5193                                stmtStack.push(stmt);
5194                                analyzeCreateStreamStmt((TCreateStreamStmt) stmt);
5195                                stmtStack.pop();
5196                        } else if (stmt instanceof TSelectSqlStatement) {
5197                                analyzeSelectStmt((TSelectSqlStatement) stmt);
5198                        } else if (stmt instanceof TDropTableSqlStatement) {
5199                                stmtStack.push(stmt);
5200                                analyzeDropTableStmt((TDropTableSqlStatement) stmt);
5201                                stmtStack.pop();
5202                        } else if (stmt instanceof TTruncateStatement) {
5203                                stmtStack.push(stmt);
5204                                analyzeTruncateTableStmt((TTruncateStatement) stmt);
5205                                stmtStack.pop();
5206                        } else if (stmt instanceof TCreateMaterializedSqlStatement) {
5207                                stmtStack.push(stmt);
5208                                TCreateMaterializedSqlStatement view = (TCreateMaterializedSqlStatement) stmt;
5209                                analyzeCreateViewStmt(view, view.getSubquery(), view.getViewAliasClause(), view.getViewName());
5210                                stmtStack.pop();
5211                        } else if (stmt instanceof TCreateViewSqlStatement) {
5212                                stmtStack.push(stmt);
5213                                TCreateViewSqlStatement view = (TCreateViewSqlStatement) stmt;
5214                                analyzeCreateViewStmt(view, view.getSubquery(), view.getViewAliasClause(), view.getViewName());
5215                                stmtStack.pop();
5216                        } else if(stmt instanceof TDb2SqlVariableDeclaration){
5217                                stmtStack.push(stmt);
5218                                analyzeDb2Declare((TDb2SqlVariableDeclaration)stmt);
5219                                stmtStack.pop();
5220                        } else if (stmt instanceof TMssqlCreateType) {
5221                                stmtStack.push(stmt);
5222                                TMssqlCreateType createType = (TMssqlCreateType) stmt;
5223                                analyzeMssqlCreateType(createType);
5224                                stmtStack.pop();
5225                        } else if (stmt instanceof TMssqlDeclare) {
5226                                stmtStack.push(stmt);
5227                                TMssqlDeclare declare = (TMssqlDeclare) stmt;
5228                                analyzeMssqlDeclare(declare);
5229                                stmtStack.pop();
5230                        } else if (stmt instanceof TInsertSqlStatement) {
5231                                stmtStack.push(stmt);
5232                                TInsertSqlStatement insert = (TInsertSqlStatement)stmt;
5233                                analyzeInsertStmt(insert);
5234                                if(insert.getMultiInsertStatements()!=null) {
5235                                        for(int i=0;i<insert.getMultiInsertStatements().size();i++) {
5236                                                analyzeInsertStmt(insert.getMultiInsertStatements().get(i));
5237                                        }
5238                                }
5239                                stmtStack.pop();
5240                        } else if (stmt instanceof TRedshiftCopy) {
5241                                stmtStack.push(stmt);
5242                                analyzeRedshiftCopyStmt((TRedshiftCopy) stmt);
5243                                stmtStack.pop();
5244                        } else if (stmt instanceof TMssqlBulkInsert) {
5245                                stmtStack.push(stmt);
5246                                analyzeMssqlBulkInsertStmt((TMssqlBulkInsert) stmt);
5247                                stmtStack.pop();
5248                        } else if (stmt instanceof TSnowflakeCopyIntoStmt) {
5249                                stmtStack.push(stmt);
5250                                analyzeCopyIntoStmt((TSnowflakeCopyIntoStmt) stmt);
5251                                stmtStack.pop();
5252                        } else if (stmt instanceof TUnloadStmt) {
5253                                stmtStack.push(stmt);
5254                                analyzeUnloadStmt((TUnloadStmt) stmt);
5255                                stmtStack.pop();
5256                        } else if (stmt instanceof TUpdateSqlStatement) {
5257                                stmtStack.push(stmt);
5258                                analyzeUpdateStmt((TUpdateSqlStatement) stmt);
5259                                stmtStack.pop();
5260                        } else if (stmt instanceof TMergeSqlStatement) {
5261                                stmtStack.push(stmt);
5262                                analyzeMergeStmt((TMergeSqlStatement) stmt);
5263                                stmtStack.pop();
5264                        } else if (stmt instanceof TDeleteSqlStatement) {
5265                                stmtStack.push(stmt);
5266                                analyzeDeleteStmt((TDeleteSqlStatement) stmt);
5267                                stmtStack.pop();
5268                        } else if (stmt instanceof TCursorDeclStmt) {
5269                                stmtStack.push(stmt);
5270                                analyzeCursorDeclStmt((TCursorDeclStmt) stmt);
5271                                stmtStack.pop();
5272                        } else if (stmt instanceof TFetchStmt) {
5273                                stmtStack.push(stmt);
5274                                analyzeFetchStmt((TFetchStmt) stmt);
5275                                stmtStack.pop();
5276                        } else if (stmt instanceof TMssqlFetch) {
5277                                stmtStack.push(stmt);
5278                                analyzeFetchStmt((TMssqlFetch) stmt);
5279                                stmtStack.pop();
5280                        } else if (stmt instanceof TForStmt) {
5281                                stmtStack.push(stmt);
5282                                analyzeForStmt((TForStmt) stmt);
5283                                stmtStack.pop();
5284                        } else if (stmt instanceof TOpenforStmt) {
5285                                stmtStack.push(stmt);
5286                                analyzeOpenForStmt((TOpenforStmt) stmt);
5287                                stmtStack.pop();
5288                        } else if (stmt instanceof TLoopStmt) {
5289                                stmtStack.push(stmt);
5290                                analyzeLoopStmt((TLoopStmt) stmt);
5291                                stmtStack.pop();
5292                        } else if (stmt instanceof TAssignStmt) {
5293                                stmtStack.push(stmt);
5294                                analyzeAssignStmt((TAssignStmt) stmt);
5295                                stmtStack.pop();
5296                        } else if (stmt instanceof TSetStmt) {
5297                                stmtStack.push(stmt);
5298                                analyzeSetStmt((TSetStmt) stmt);
5299                                stmtStack.pop();
5300                        } else if (stmt instanceof TMssqlSet) {
5301                                stmtStack.push(stmt);
5302                                analyzeMssqlSetStmt((TMssqlSet) stmt);
5303                                stmtStack.pop();
5304                        } else if (stmt instanceof TVarDeclStmt) {
5305                                stmtStack.push(stmt);
5306                                analyzeVarDeclStmt((TVarDeclStmt) stmt);
5307                                stmtStack.pop();
5308                        } else if (stmt instanceof TCreateDatabaseSqlStatement) {
5309                                stmtStack.push(stmt);
5310                                analyzeCloneDatabaseStmt((TCreateDatabaseSqlStatement) stmt);
5311                                stmtStack.pop();
5312                        } else if (stmt instanceof TCreateSchemaSqlStatement) {
5313                                stmtStack.push(stmt);
5314                                analyzeCloneSchemaStmt((TCreateSchemaSqlStatement) stmt);
5315                                stmtStack.pop();
5316                        } else if (stmt instanceof TAlterTableStatement) {
5317                                stmtStack.push(stmt);
5318                                analyzeAlterTableStmt((TAlterTableStatement) stmt);
5319                                stmtStack.pop();
5320                        } else if (stmt instanceof TAlterViewStatement) {
5321                                stmtStack.push(stmt);
5322                                analyzeAlterViewStmt((TAlterViewStatement) stmt);
5323                                stmtStack.pop();
5324                        } else if (stmt instanceof TRenameStmt) {
5325                                stmtStack.push(stmt);
5326                                analyzeRenameStmt((TRenameStmt) stmt);
5327                                stmtStack.pop();
5328                        } else if (stmt instanceof TCreateSynonymStmt) {
5329                                stmtStack.push(stmt);
5330                                analyzeCreateSynonymStmt((TCreateSynonymStmt) stmt);
5331                                stmtStack.pop();
5332                        } else if (stmt instanceof TLoadDataStmt) {
5333                                stmtStack.push(stmt);
5334                                analyzeLoadDataStmt((TLoadDataStmt) stmt);
5335                                stmtStack.pop();
5336                        } else if (stmt instanceof THiveLoad) {
5337                                stmtStack.push(stmt);
5338                                analyzeHiveLoadStmt((THiveLoad) stmt);
5339                                stmtStack.pop();
5340                        } else if (stmt instanceof TDb2ReturnStmt) {
5341                                stmtStack.push(stmt);
5342                                analyzeDb2ReturnStmt((TDb2ReturnStmt) stmt);
5343                                stmtStack.pop();
5344                        } else if (stmt instanceof TReturnStmt) {
5345                                stmtStack.push(stmt);
5346                                analyzeReturnStmt((TReturnStmt) stmt);
5347                                stmtStack.pop();
5348                        } else if (stmt instanceof TMssqlReturn) {
5349                                stmtStack.push(stmt);
5350                                analyzeMssqlReturnStmt((TMssqlReturn) stmt);
5351                                stmtStack.pop();
5352                        } else if (stmt instanceof TMssqlUpdateText) {
5353                                stmtStack.push(stmt);
5354                                analyzeMssqlUpdateTextStmt((TMssqlUpdateText) stmt);
5355                                stmtStack.pop();
5356                        } else if (stmt instanceof TMssqlWriteText) {
5357                                stmtStack.push(stmt);
5358                                analyzeMssqlWriteTextStmt((TMssqlWriteText) stmt);
5359                                stmtStack.pop();
5360                        } else if (stmt instanceof TMssqlReadText) {
5361                                stmtStack.push(stmt);
5362                                analyzeMssqlReadTextStmt((TMssqlReadText) stmt);
5363                                stmtStack.pop();
5364                        } else if (stmt instanceof TExecuteSqlStatement) {
5365                                String sqlText = ((TExecuteSqlStatement) stmt).getPreparedSqlText();
5366                                if(sqlText == null) {
5367                                        sqlText = ((TExecuteSqlStatement) stmt).getSqlText();
5368                                }
5369                                if (!option.isAnalyzeDynamicSql()) {
5370                                        if (sqlText != null
5371                                                        || ((TExecuteSqlStatement) stmt).getStmtString() != null) {
5372                                                modelManager.collectDynamicSqlHash(stmt);
5373                                                recordDynamicSqlSite(stmt, DynamicSqlSite.Kind.OTHER,
5374                                                                DynamicSqlSite.Status.UNRESOLVED,
5375                                                                "dynamic SQL inner analysis disabled");
5376                                        }
5377                                } else if (sqlText != null) {
5378                                        modelManager.collectDynamicSqlHash(stmt);
5379                                        TGSqlParser sqlparser = createSqlParser(option.getVendor());
5380                                        sqlparser.sqltext = SQLUtil.trimColumnStringQuote(sqlText);
5381                                        // each %s of format() a runtime argument fills: the endpoint it names is a template, not a
5382                                        // table named PLACEHOLDER (Mantis 4880)
5383                                        List<FoldHole> formatHoles = formatHoles((TExecuteSqlStatement) stmt, sqlparser.sqltext);
5384                                        Set<Table> templateTablesBefore = snapshotTemplateCandidateTables(!formatHoles.isEmpty());
5385                                        int result = parseWithCommentBindings(sqlparser);
5386                                        if (result != 0) {
5387                                                errors = sqlparser.getSyntaxErrors();
5388                                                if (errors != null && !errors.isEmpty()) {
5389                                                        for (int i = 0; i < errors.size(); i++) {
5390                                                                TSyntaxError error = errors.get(i);
5391                                                                ErrorInfo errorInfo = new ErrorInfo();
5392                                                                errorInfo.setErrorType(ErrorInfo.SYNTAX_ERROR);
5393                                                                errorInfo.setErrorMessage(getErrorMessage(error, ErrorInfo.SYNTAX_ERROR));
5394                                                                errorInfo.setStartPosition(new Pair3<Long, Long, String>(error.lineNo, error.columnNo,
5395                                                                                ModelBindingManager.getGlobalHash()));
5396                                                                String[] segments = error.tokentext.split("\n");
5397                                                                if (segments.length == 1) {
5398                                                                        errorInfo.setEndPosition(new Pair3<Long, Long, String>(error.lineNo,
5399                                                                                        error.columnNo + error.tokentext.length(),
5400                                                                                        ModelBindingManager.getGlobalHash()));
5401                                                                } else {
5402                                                                        errorInfo.setEndPosition(
5403                                                                                        new Pair3<Long, Long, String>(error.lineNo + segments.length - 1,
5404                                                                                                        (long) segments[segments.length - 1].length() + 1,
5405                                                                                                        ModelBindingManager.getGlobalHash()));
5406                                                                }
5407                                                                errorInfo.fillInfo(this);
5408                                                                errorInfos.add(errorInfo);
5409                                                        }
5410                                                }
5411                                        } else if (sqlparser.sqlstatements != null) {
5412                                                dynamicFoldDepth++;
5413                                                try {
5414                                                        for (int i = 0; i < sqlparser.sqlstatements.size(); i++) {
5415                                                                if (formatHoles.isEmpty()) {
5416                                                                        // text without a hole names real objects, as a static statement does
5417                                                                        applyStaticTemplateVetoes(sqlparser.sqlstatements.get(i));
5418                                                                }
5419                                                                analyzeCustomSqlStmt(sqlparser.sqlstatements.get(i));
5420                                                        }
5421                                                } finally {
5422                                                        dynamicFoldDepth--;
5423                                                }
5424                                                markDynamicTemplateEndpoints(templateTablesBefore, sqlparser, formatHoles);
5425                                        }
5426                                }
5427                                else if (((TExecuteSqlStatement) stmt).getStmtString() != null) {
5428                                        modelManager.collectDynamicSqlHash(stmt);
5429                                }
5430                        } else if (stmt instanceof TMssqlExecute) {
5431                                TMssqlExecute executeStmt = (TMssqlExecute)stmt;
5432                                if (!option.isAnalyzeDynamicSql()
5433                                                && expandsDynamicSql(executeStmt)) {
5434                                        modelManager.collectDynamicSqlHash(stmt);
5435                                        recordDynamicSqlSite(stmt, dynamicKindOf(executeStmt),
5436                                                        DynamicSqlSite.Status.UNRESOLVED,
5437                                                        "dynamic SQL inner analysis disabled");
5438                                } else if (executeStmt.getSqlText() != null) {
5439                                        modelManager.collectDynamicSqlHash(stmt);
5440                                        boolean evalHandled = false;
5441                                        DynamicSqlTrustMode trustMode = option.getDynamicSqlTrustMode();
5442                                        boolean nonLiteralDynamicArg = !isLiteralDynamicArg(executeStmt);
5443                                        gudusoft.gsqlparser.dlineage.dynamicsql.DynamicSqlLineageResolver.MaterializedSite evalSite = null;
5444                                        if (nonLiteralDynamicArg) {
5445                                                evalSite = evaluatedDynamicSqlFor(stmt);
5446                                                // Abstract evaluation fully materialized one or more strings
5447                                                // (constant relation rows and/or literal call bindings). Analyze
5448                                                // every distinct concrete variant in stable evidence order.
5449                                                evalHandled = analyzeEvaluatedDynamicSqlVariants(stmt,
5450                                                                dynamicKindOf(executeStmt), true);
5451                                        }
5452                                        boolean evaluatorProofUnavailable = nonLiteralDynamicArg
5453                                                        && (evalSite == null || evalSite.sqlText == null
5454                                                                        || evalSite.provenanceIncomplete);
5455                                        boolean provenanceIncomplete = trustMode == DynamicSqlTrustMode.SHADOW
5456                                                        && evaluatorProofUnavailable;
5457                                        // Hole evidence is a property of the ARGUMENT's provenance as the
5458                                        // evaluator saw it (which variable, assigned from what, where), not
5459                                        // of the text analyzed below - the evaluator and the legacy fold
5460                                        // render the same expression differently (@DeleteWhere vs a dropped
5461                                        // tail), but its holes are the same holes. Hole SPANS inside the
5462                                        // analyzed text remain text-gated (markDynamicTemplateEndpoints).
5463                                        // Whenever the evaluator saw the node its verdict stands, including
5464                                        // "not known" (-1). Only without an evaluator view (no enclosing
5465                                        // procedure) does the legacy rule apply: SHADOW says unknown, LEGACY
5466                                        // keeps its historical zero.
5467                                        DynamicHoleEvidence holeEvidence = dynamicHoleEvidence(evalSite);
5468                                        int trustHoleCount = evalSite != null ? holeEvidence.count
5469                                                        : (provenanceIncomplete ? -1 : 0);
5470                                        boolean trustHoleCountExact = evalSite != null ? holeEvidence.exact
5471                                                        : !provenanceIncomplete;
5472                                        if (!evalHandled && nonLiteralDynamicArg && provenNullReasonOf(stmt) != null) {
5473                                                // every variant of the string is NULL for a reason the evaluation proves (EXEC of NULL runs
5474                                                // nothing): the token fold must not publish what it would make of it. A variant without text
5475                                                // for any other reason leaves the fold its say: it keeps the edges the literal parts prove
5476                                                // (Mantis 4962)
5477                                                recordUnresolvedDynamicSqlSite(stmt, executeStmt, dynamicKindOf(executeStmt));
5478                                                evalHandled = true;
5479                                        }
5480                                        if (!evalHandled) {
5481                                        TGSqlParser sqlparser = createSqlParser(option.getVendor());
5482                                        sqlparser.sqltext = ((TMssqlExecute) stmt).getSqlText();
5483                                        Set<Relationship> shadowBefore = provenanceIncomplete
5484                                                        && trustMode == DynamicSqlTrustMode.SHADOW
5485                                                        ? snapshotDynamicRelationships() : null;
5486                                        int relsBeforeDynamic = modelManager.getRelations().length;
5487                                        int dynamicSitesBefore = dynamicSqlSites.size();
5488                                        // A data-driven argument (variable / expression) is at most PARTIALLY folded:
5489                                        // GSP keeps the literal pieces but drops the unresolved trailing @var, leaving a
5490                                        // legitimately truncated fragment (e.g. "... WHERE " with no predicate -> end of
5491                                        // input). A parse failure on such text says nothing about the real SQL, so we must
5492                                        // not surface it as a unit SYNTAX_ERROR — that would discard the whole procedure's
5493                                        // lineage even though the outer parse succeeded. Only a true compile-time literal
5494                                        // EXEC('...') surfaces inner parse failures (mirrors the EXECUTE IMMEDIATE
5495                                        // isDynamicSQLPartial() guard below).
5496                                        boolean literalArg = isLiteralDynamicArg(executeStmt);
5497                                        // A partial fold leaves the unresolved @var / @param remnant in the folded
5498                                        // text. Decided BEFORE the inner analysis so the endpoints this site
5499                                        // introduces can be told apart from the ones that already existed, and so
5500                                        // a fully-resolved fold pays nothing (snapshot returns null).
5501                                        boolean maybePartialFold = !literalArg
5502                                                        && ((TMssqlExecute) stmt).getSqlText().matches("(?s).*@\\w+.*");
5503                                        // The conservative @\w+ test is not the only way a hole can be present:
5504                                        // the evaluator may know this fold has value-unknown holes whose legacy
5505                                        // rendering carries no '@' (UPPER(@X) folds to the bare function name).
5506                                        // Snapshot whenever EITHER tier could fire, so provenance is never wasted.
5507                                        Set<Table> templateTablesBefore = snapshotTemplateCandidateTables(maybePartialFold
5508                                                        || hasValueUnknownHole(evalSite));
5509                                        boolean foldStatementsAnalyzed = false;
5510                                        int result = parseWithCommentBindings(sqlparser);
5511                                        if (result == 0 && sqlparser.sqltext != null) {
5512                                                // where the text came from (Mantis 4851): a literal argument's own token; else the
5513                                                // evaluator's fragments, when they spell the same text. Otherwise positions stay the text's
5514                                                List<gudusoft.gsqlparser.dlineage.dynamicsql.SqlFragment> provenance = null;
5515                                                if (literalArg) {
5516                                                        TExpression arg = dynamicArgOf(executeStmt);
5517                                                        gudusoft.gsqlparser.dlineage.dynamicsql.SqlFragment.SourcePiece piece = arg != null
5518                                                                        && arg.getStartToken() != null && arg.getStartToken() == arg.getEndToken()
5519                                                                        ? gudusoft.gsqlparser.dlineage.dynamicsql.SqlFragment.SourcePiece.of(
5520                                                                                        arg.getStartToken(), sqlparser.sqltext)
5521                                                                        : null;
5522                                                        if (piece != null) {
5523                                                                provenance = Collections.singletonList(
5524                                                                                gudusoft.gsqlparser.dlineage.dynamicsql.SqlFragment.literal(sqlparser.sqltext, piece));
5525                                                        } else {
5526                                                                // several literals ('a' + 'b'): the evaluator's fragments hold one piece each
5527                                                                gudusoft.gsqlparser.dlineage.dynamicsql.DynamicSqlLineageResolver.MaterializedSite literalSite =
5528                                                                                evaluatedDynamicSqlFor(stmt);
5529                                                                if (literalSite != null && sqlparser.sqltext.equals(literalSite.sqlText)) {
5530                                                                        provenance = literalSite.fragments;
5531                                                                }
5532                                                        }
5533                                                } else if (evalSite != null && sqlparser.sqltext.equals(evalSite.sqlText)) {
5534                                                        provenance = evalSite.fragments;
5535                                                }
5536                                                if (provenance != null) {
5537                                                        remapDynamicPositions(sqlparser, provenance, stmt);
5538                                                }
5539                                        }
5540                                        if (result != 0) {
5541                                                errors = literalArg ? sqlparser.getSyntaxErrors() : null;
5542                                                if (errors != null && !errors.isEmpty()) {
5543                                                        for (int i = 0; i < errors.size(); i++) {
5544                                                                TSyntaxError error = errors.get(i);
5545                                                                ErrorInfo errorInfo = new ErrorInfo();
5546                                                                errorInfo.setErrorType(ErrorInfo.SYNTAX_ERROR);
5547                                                                errorInfo.setErrorMessage(getErrorMessage(error, ErrorInfo.SYNTAX_ERROR));
5548                                                                errorInfo.setStartPosition(new Pair3<Long, Long, String>(error.lineNo, error.columnNo,
5549                                                                                ModelBindingManager.getGlobalHash()));
5550                                                                String[] segments = error.tokentext.split("\n");
5551                                                                if (segments.length == 1) {
5552                                                                        errorInfo.setEndPosition(new Pair3<Long, Long, String>(error.lineNo,
5553                                                                                        error.columnNo + error.tokentext.length(),
5554                                                                                        ModelBindingManager.getGlobalHash()));
5555                                                                } else {
5556                                                                        errorInfo.setEndPosition(
5557                                                                                        new Pair3<Long, Long, String>(error.lineNo + segments.length - 1,
5558                                                                                                        (long) segments[segments.length - 1].length() + 1,
5559                                                                                                        ModelBindingManager.getGlobalHash()));
5560                                                                }
5561                                                                errorInfo.fillInfo(this);
5562                                                                errorInfos.add(errorInfo);
5563                                                        }
5564                                                }
5565                                        } else if (sqlparser.sqlstatements != null) {
5566                                                foldStatementsAnalyzed = true;
5567                                                List<TConstant> templatedFiles = templateTablesBefore == null
5568                                                                ? Collections.<TConstant>emptyList() : noteTemplatedLoadFiles(evalSite, sqlparser);
5569                                                dynamicFoldDepth++;
5570                                                try {
5571                                                        for (int i = 0; i < sqlparser.sqlstatements.size(); i++) {
5572                                                                analyzeCustomSqlStmt(sqlparser.sqlstatements.get(i));
5573                                                        }
5574                                                } finally {
5575                                                        dynamicFoldDepth--;
5576                                                        forgetTemplatedLoadFiles(templatedFiles);
5577                                                }
5578                                        }
5579                                        // getSqlText()!=null means GSP folded the dynamic text. RESOLVED only when the argument
5580                                        // was a compile-time literal AND it parsed; a variable/expression argument is data-driven
5581                                        // (often only partially folded, e.g. an unresolved @parameter left in) -> UNRESOLVED.
5582                                        // NOTE: a variable
5583                                        // argument whose folded text fails to parse deliberately stays UNRESOLVED,
5584                                        // not PARSE_ERROR — the fold is flow-insensitive (the variable may hold a
5585                                        // different value at runtime), so a parse failure on folded text says
5586                                        // nothing definitive about the real SQL. Only a compile-time literal
5587                                        // argument earns PARSE_ERROR.
5588                                        // Land the placeholder verdict on the model NODES, not only on the site
5589                                        // status: exact hole spans when the evaluator described this very text,
5590                                        // literal-aligned spans when it described the same fold's other rendering,
5591                                        // otherwise the conservative @\w+ scan of the legacy fold.
5592                                        if (foldStatementsAnalyzed) {
5593                                                markDynamicTemplateEndpoints(templateTablesBefore, evalSite, sqlparser);
5594                                        }
5595                                        int[] dynCounts = classifyDynamicSiteLineage(relsBeforeDynamic, maybePartialFold);
5596                                        int unprovenCount = countNewDynamicRelationships(shadowBefore);
5597                                        if (!literalArg) {
5598                                                // Truthful status: a fold that yielded clean lineage into base tables
5599                                                // — or a pure dynamic SELECT whose only product is its projection —
5600                                                // is RESOLVED (with counts). Placeholder-contaminated, partial-target,
5601                                                // or empty outcomes stay UNRESOLVED with an accurate reason.
5602                                                boolean usable = dynCounts[2] == 0
5603                                                                && (dynCounts[0] > 0 || (dynCounts[1] == 0 && dynCounts[3] > 0));
5604                                                if (provenanceIncomplete && trustMode == DynamicSqlTrustMode.SHADOW) {
5605                                                        recordDynamicSqlSite(stmt, dynamicKindOf(executeStmt),
5606                                                                        unprovenCount > 0 ? DynamicSqlSite.Status.PARTIAL
5607                                                                                        : DynamicSqlSite.Status.UNRESOLVED,
5608                                                                dynamicTrustReason("SHADOW retained legacy publication behavior",
5609                                                                                unprovenCount, trustHoleCount, trustHoleCountExact),
5610                                                                dynCounts[0], dynCounts[1], unprovenCount,
5611                                                                trustHoleCount, trustHoleCountExact, trustMode,
5612                                                                relsBeforeDynamic, dynamicSitesBefore, null,
5613                                                                evalSite == null ? null : evalSite.executionNote,
5614                                                                holeEvidence.fragments, holeEvidence.materializedText,
5615                                                                evalSite == null || evalSite.executionCertain);
5616                                                } else {
5617                                                        recordDynamicSqlSite(stmt, dynamicKindOf(executeStmt),
5618                                                                        usable ? DynamicSqlSite.Status.RESOLVED : DynamicSqlSite.Status.UNRESOLVED,
5619                                                                        usable ? null
5620                                                                                        : (dynCounts[2] != 0
5621                                                                                                        ? "dynamic SQL references a runtime-built object name; lineage source is a placeholder"
5622                                                                                                        : dynamicReasonOf(executeStmt)),
5623                                                                        dynCounts[0], dynCounts[1], 0,
5624                                                                        trustHoleCount, trustHoleCountExact, trustMode,
5625                                                                        relsBeforeDynamic, dynamicSitesBefore, null,
5626                                                                        evalSite == null ? null : evalSite.executionNote,
5627                                                                        holeEvidence.fragments, holeEvidence.materializedText,
5628                                                                        evalSite == null || evalSite.executionCertain);
5629                                                }
5630                                        } else {
5631                                                // a literal argument needs no evaluation, but whether the statement runs is the evaluator's
5632                                                // to say: in a CATCH block or behind an undecidable condition it may not (Mantis 4962)
5633                                                gudusoft.gsqlparser.dlineage.dynamicsql.DynamicSqlLineageResolver.MaterializedSite position =
5634                                                                evaluatedDynamicSqlFor(stmt);
5635                                                recordDynamicSqlSite(stmt, dynamicKindOf(executeStmt),
5636                                                                result != 0 ? DynamicSqlSite.Status.PARSE_ERROR : DynamicSqlSite.Status.RESOLVED,
5637                                                                result != 0 ? "inner dynamic SQL failed to parse" : null,
5638                                                                dynCounts[0], dynCounts[1], 0, 0, true, trustMode,
5639                                                                relsBeforeDynamic, dynamicSitesBefore, null,
5640                                                                position == null ? null : position.executionNote,
5641                                                                Collections.<UnresolvedFragment>emptyList(), executeStmt.getSqlText(),
5642                                                                position == null || position.executionCertain);
5643                                        }
5644                                        }
5645                                } else if (executeStmt.getModuleName() != null) {
5646                                        // getSqlText()==null here: a folded literal would have set sqlText. So this is either an
5647                                        // opaque sp_executesql (data-driven argument), a dynamic proc name (EXEC @var), or an
5648                                        // ordinary static procedure call. Record the dynamic ones; leave static EXEC proc alone.
5649                                        boolean evalHandledSite = false;
5650                                        if (isSpExecutesql(executeStmt.getModuleName())) {
5651                                                evalHandledSite = analyzeEvaluatedDynamicSqlVariants(stmt,
5652                                                                DynamicSqlSite.Kind.SP_EXECUTESQL, false);
5653                                                if (!evalHandledSite) {
5654                                                        recordUnresolvedDynamicSqlSite(stmt, executeStmt,
5655                                                                        DynamicSqlSite.Kind.SP_EXECUTESQL);
5656                                                }
5657                                        } else if (executeStmt.getModuleName().toString().startsWith("@")) {
5658                                                // EXEC @var is SQL Server's @module_name_var form: the variable
5659                                                // holds a PROCEDURE NAME, not SQL text (parenthesized EXEC(@var)
5660                                                // arrives as metExecStringCmd below) - never evaluate it as SQL.
5661                                                recordDynamicSqlSite(stmt, DynamicSqlSite.Kind.OTHER,
5662                                                                DynamicSqlSite.Status.UNRESOLVED, "EXEC target procedure name is a runtime variable");
5663                                        }
5664                                        if (!evalHandledSite && isSpExecutesql(executeStmt.getModuleName())
5665                                                        && provenNullReasonOf(stmt) != null) {
5666                                                // its string is NULL for certain: the evaluation decided the site although it gave no text, and
5667                                                // the legacy token path must not publish its own rendering (Mantis 4962)
5668                                                evalHandledSite = true;
5669                                        }
5670                                        if (!evalHandledSite) {
5671                                                // When evaluation already analyzed the materialized batch, the
5672                                                // legacy path would re-execute the same argument and duplicate
5673                                                // every process/edge - skip it.
5674                                                stmtStack.push(stmt);
5675                                                analyzeMssqlExecute(executeStmt);
5676                                                stmtStack.pop();
5677                                        }
5678                                } else if (executeStmt.getExecType() == TBaseType.metExecStringCmd) {
5679                                        // EXEC(<expr>) whose string was not folded: try the no-bindings abstract
5680                                        // evaluation (template/constant-built strings); otherwise honestly UNRESOLVED.
5681                                        if (!analyzeEvaluatedDynamicSqlVariants(stmt,
5682                                                        DynamicSqlSite.Kind.EXEC_STRING, false)) {
5683                                                recordUnresolvedDynamicSqlSite(stmt, executeStmt,
5684                                                                DynamicSqlSite.Kind.EXEC_STRING);
5685                                        }
5686                                }
5687                        } else if (stmt instanceof TExecImmeStmt) {
5688                                TExecImmeStmt execImmeStmt = (TExecImmeStmt) stmt;
5689                                modelManager.collectDynamicSqlHash(stmt);
5690                                if (!option.isAnalyzeDynamicSql()) {
5691                                        recordDynamicSqlSite(stmt,
5692                                                        DynamicSqlSite.Kind.EXECUTE_IMMEDIATE,
5693                                                        DynamicSqlSite.Status.UNRESOLVED,
5694                                                        "dynamic SQL inner analysis disabled");
5695                                        return;
5696                                }
5697                                synchronized (DataFlowAnalyzer.class) {
5698                                        int relsBeforeDynamic = modelManager.getRelations().length;
5699                                        int dynamicSitesBefore = dynamicSqlSites.size();
5700                                        boolean analyzedInner = false;
5701                                        boolean innerParseFailed = false;
5702                                        // PL/SQL call-site binding (plan Phase 5): when the token fold left
5703                                        // this site partial or empty, interpret the file's statements (plus
5704                                        // literal callers harvested from other files of a multi-file
5705                                        // analysis) so parameter-dependent templates can fold fully.
5706                                        // getDynamicStatements() below then reads the evaluated text.
5707                                        maybeEvaluatePlsqlDynamicSql(execImmeStmt);
5708                                        DynamicSqlTrustMode trustMode = option.getDynamicSqlTrustMode();
5709                                        boolean legacyPartialFold = execImmeStmt.isDynamicSQLPartial();
5710                                        boolean shadowIncomplete = legacyPartialFold
5711                                                        && execImmeStmt.getEvaluatedDynamicSQLs().isEmpty();
5712                                        Set<Relationship> shadowBefore = shadowIncomplete
5713                                                        && trustMode == DynamicSqlTrustMode.SHADOW
5714                                                        ? snapshotDynamicRelationships() : null;
5715                                        TStatementList stmts = execImmeStmt.getDynamicStatements();
5716                                        if (stmts != null && stmts.size() > 0) {
5717                                                dynamicFoldDepth++;
5718                                                try {
5719                                                        for (int i = 0; i < stmts.size(); i++) {
5720                                                                analyzeCustomSqlStmt(stmts.get(i));
5721                                                        }
5722                                                } finally {
5723                                                        dynamicFoldDepth--;
5724                                                }
5725                                                analyzedInner = true;
5726                                        }
5727
5728                                        // Only re-parse the raw dynamic SQL when getDynamicStatements()
5729                                        // produced nothing. getDynamicStatements() already parses and
5730                                        // analyzes the fragment with coordinates remapped back to the
5731                                        // original file position; re-parsing it here with a fresh parser
5732                                        // would analyze the same SQL a second time and emit duplicate
5733                                        // hints/lineage carrying fragment-relative (un-remapped)
5734                                        // coordinates. This fallback still covers the case where
5735                                        // getDynamicStatements() failed (e.g. dynamic SQL syntax error),
5736                                        // reporting the parse error below.
5737                                        String dynamicSql = (stmts == null || stmts.size() == 0) ? execImmeStmt.getDynamicSQL() : null;
5738                                        if (!SQLUtil.isEmpty(dynamicSql)) {
5739                                                TGSqlParser sqlparser = createSqlParser(option.getVendor());
5740                                                sqlparser.sqltext = dynamicSql;
5741                                                int result = parseWithCommentBindings(sqlparser);
5742                                                        // This fresh parse runs the dynamic SQL un-padded, so its
5743                                                        // coordinates are relative to the fragment (first char at
5744                                                        // line 1). Shift them back to the position the fragment
5745                                                        // occupies in the original file, mirroring the shift
5746                                                        // TExecImmeStmt.getDynamicStatements() applies; otherwise
5747                                                        // fallback parse errors for EXECUTE IMMEDIATE text located
5748                                                        // later in the file would report fragment-relative lines.
5749                                                        if (execImmeStmt.getDynamicStringExpr() != null
5750                                                                        && execImmeStmt.getDynamicStringExpr().getPlainTextLineNo() != -1) {
5751                                                                int deltaLine = (int) execImmeStmt.getDynamicStringExpr().getPlainTextLineNo() - 1;
5752                                                                int deltaColumn = (int) execImmeStmt.getDynamicStringExpr().getPlainTextColumnNo();
5753                                                                if ((deltaLine != 0 || deltaColumn != 0) && sqlparser.getSyntaxErrors() != null) {
5754                                                                        for (int ei = 0; ei < sqlparser.getSyntaxErrors().size(); ei++) {
5755                                                                                TSyntaxError dynErr = sqlparser.getSyntaxErrors().get(ei);
5756                                                                                if (dynErr == null) continue;
5757                                                                                boolean onFirstLine = (dynErr.lineNo == 1);
5758                                                                                dynErr.lineNo += deltaLine;
5759                                                                                if (onFirstLine) dynErr.columnNo += deltaColumn;
5760                                                                        }
5761                                                                }
5762                                                        }
5763                                                if (result != 0) {
5764                                                        innerParseFailed = true;
5765                                                        // a partially resolved value contains placeholder identifiers
5766                                                        // for unknowable parts; a parse failure on such text says
5767                                                        // nothing about the real SQL, so don't report it
5768                                                        errors = execImmeStmt.isDynamicSQLPartial() ? null : sqlparser.getSyntaxErrors();
5769                                                        if (errors != null && !errors.isEmpty()) {
5770                                                                for (int i = 0; i < errors.size(); i++) {
5771                                                                        TSyntaxError error = errors.get(i);
5772                                                                        ErrorInfo errorInfo = new ErrorInfo();
5773                                                                        errorInfo.setErrorType(ErrorInfo.SYNTAX_ERROR);
5774                                                                        errorInfo.setErrorMessage(getErrorMessage(error, ErrorInfo.SYNTAX_ERROR));
5775                                                                        errorInfo.setStartPosition(new Pair3<Long, Long, String>(error.lineNo, error.columnNo,
5776                                                                                        ModelBindingManager.getGlobalHash()));
5777                                                                        String[] segments = error.tokentext.split("\n");
5778                                                                        if (segments.length == 1) {
5779                                                                                errorInfo.setEndPosition(new Pair3<Long, Long, String>(error.lineNo,
5780                                                                                                error.columnNo + error.tokentext.length(),
5781                                                                                                ModelBindingManager.getGlobalHash()));
5782                                                                        } else {
5783                                                                                errorInfo.setEndPosition(
5784                                                                                                new Pair3<Long, Long, String>(error.lineNo + segments.length - 1,
5785                                                                                                                (long) segments[segments.length - 1].length() + 1,
5786                                                                                                                ModelBindingManager.getGlobalHash()));
5787                                                                        }
5788                                                                        errorInfo.fillInfo(this);
5789                                                                        errorInfos.add(errorInfo);
5790                                                                }
5791                                                        }
5792                                                } else if (sqlparser.sqlstatements != null) {
5793                                                        dynamicFoldDepth++;
5794                                                        try {
5795                                                                for (int i = 0; i < sqlparser.sqlstatements.size(); i++) {
5796                                                                        analyzeCustomSqlStmt(sqlparser.sqlstatements.get(i));
5797                                                                }
5798                                                        } finally {
5799                                                                dynamicFoldDepth--;
5800                                                        }
5801                                                        analyzedInner = true;
5802                                                }
5803                                        }
5804                                        // Site accounting mirrors the T-SQL EXEC branch: every
5805                                        // EXECUTE IMMEDIATE is a dynamic site; status reflects what
5806                                        // the analyzer actually did with it.
5807                                        boolean partialFold = legacyPartialFold;
5808                                        int[] dynCounts = classifyDynamicSiteLineage(relsBeforeDynamic, partialFold);
5809                                        int unprovenCount = countNewDynamicRelationships(shadowBefore);
5810                                        boolean fullyKnown = analyzedInner && !partialFold;
5811                                        boolean usableLineage = dynCounts[2] == 0
5812                                                        && (dynCounts[0] > 0 || (dynCounts[1] == 0 && dynCounts[3] > 0));
5813                                        if (shadowIncomplete && trustMode == DynamicSqlTrustMode.SHADOW) {
5814                                                DynamicSqlSite.Status status = analyzedInner && unprovenCount > 0
5815                                                                ? DynamicSqlSite.Status.PARTIAL : DynamicSqlSite.Status.UNRESOLVED;
5816                                                recordDynamicSqlSite(stmt, DynamicSqlSite.Kind.EXECUTE_IMMEDIATE, status,
5817                                                                dynamicTrustReason("SHADOW retained legacy publication behavior",
5818                                                                                unprovenCount, -1, false),
5819                                                                dynCounts[0], dynCounts[1], unprovenCount,
5820                                                                -1, false, trustMode,
5821                                                                relsBeforeDynamic, dynamicSitesBefore);
5822                                        } else if (analyzedInner && (fullyKnown || usableLineage)) {
5823                                                recordDynamicSqlSite(stmt, DynamicSqlSite.Kind.EXECUTE_IMMEDIATE,
5824                                                                DynamicSqlSite.Status.RESOLVED, null, dynCounts[0], dynCounts[1],
5825                                                                0, 0, true, trustMode,
5826                                                                relsBeforeDynamic, dynamicSitesBefore);
5827                                        } else if (innerParseFailed && !partialFold) {
5828                                                recordDynamicSqlSite(stmt, DynamicSqlSite.Kind.EXECUTE_IMMEDIATE,
5829                                                                DynamicSqlSite.Status.PARSE_ERROR, "inner dynamic SQL failed to parse",
5830                                                                dynCounts[0], dynCounts[1], 0, 0, true, trustMode,
5831                                                                relsBeforeDynamic, dynamicSitesBefore);
5832                                        } else if (innerParseFailed) {
5833                                                recordDynamicSqlSite(stmt, DynamicSqlSite.Kind.EXECUTE_IMMEDIATE,
5834                                                                DynamicSqlSite.Status.UNRESOLVED,
5835                                                                "partially folded dynamic SQL failed to parse; unresolved parts remain",
5836                                                                dynCounts[0], dynCounts[1], 0, -1, false, trustMode,
5837                                                                relsBeforeDynamic, dynamicSitesBefore);
5838                                        } else if (analyzedInner) {
5839                                                recordDynamicSqlSite(stmt, DynamicSqlSite.Kind.EXECUTE_IMMEDIATE,
5840                                                                DynamicSqlSite.Status.UNRESOLVED,
5841                                                                        dynCounts[2] != 0
5842                                                                                        ? "dynamic SQL references a runtime-built object name; lineage source is a placeholder"
5843                                                                                        : "dynamic SQL only partially folded; no resolved lineage produced",
5844                                                                dynCounts[0], dynCounts[1], 0, -1, false, trustMode,
5845                                                                relsBeforeDynamic, dynamicSitesBefore);
5846                                        } else {
5847                                                boolean runtimeTextUnavailable = !analyzedInner
5848                                                                && execImmeStmt.getDynamicSQL() == null;
5849                                                boolean unresolvedFragments = partialFold || runtimeTextUnavailable;
5850                                                recordDynamicSqlSite(stmt, DynamicSqlSite.Kind.EXECUTE_IMMEDIATE,
5851                                                                DynamicSqlSite.Status.UNRESOLVED,
5852                                                                "dynamic SQL string is a runtime value; no statically-known text to analyze",
5853                                                                dynCounts[0], dynCounts[1], 0,
5854                                                                unresolvedFragments ? -1 : 0, !unresolvedFragments, trustMode,
5855                                                                relsBeforeDynamic, dynamicSitesBefore);
5856                                        }
5857                                }
5858                        } else if (stmt instanceof TCallStatement) {
5859                                TCallStatement callStmt = (TCallStatement)stmt;
5860                                stmtStack.push(stmt);
5861                                analyzeCallStmt(callStmt);
5862                                stmtStack.pop();
5863                        } else if (stmt instanceof TDb2CallStmt) {
5864                                TDb2CallStmt db2CallStmt = (TDb2CallStmt) stmt;
5865                                stmtStack.push(stmt);
5866                                analyzeDb2CallStmt(db2CallStmt);
5867                                stmtStack.pop();
5868                        } else if (stmt instanceof TBasicStmt) {
5869                                TBasicStmt oracleBasicStmt = (TBasicStmt) stmt;
5870                                stmtStack.push(stmt);
5871                                analyzeOracleBasicStmt(oracleBasicStmt);
5872                                stmtStack.pop();
5873                        } else if (stmt instanceof TIfStmt) {
5874                                TIfStmt ifStmt = (TIfStmt) stmt;
5875                                stmtStack.push(stmt);
5876                                analyzeIfStmt(ifStmt);
5877                                stmtStack.pop();
5878                        } else if (stmt instanceof TElsifStmt) {
5879                                TElsifStmt elsIfStmt = (TElsifStmt) stmt;
5880                                stmtStack.push(stmt);
5881                                analyzeElsIfStmt(elsIfStmt);
5882                                stmtStack.pop();
5883                        } else if (stmt instanceof TMssqlIfElse) {
5884                                TMssqlIfElse ifStmt = (TMssqlIfElse) stmt;
5885                                stmtStack.push(stmt);
5886                                analyzeMssqlIfElseStmt(ifStmt);
5887                                stmtStack.pop();
5888                        } else if (stmt.getStatements() != null && stmt.getStatements().size() > 0) {
5889                                for (int i = 0; i < stmt.getStatements().size(); i++) {
5890                                        analyzeCustomSqlStmt(stmt.getStatements().get(i));
5891                                }
5892                        } else if (stmt instanceof TCreateIndexSqlStatement) {
5893                                stmtStack.push(stmt);
5894                                analyzeCreateIndexStageStmt((TCreateIndexSqlStatement) stmt);
5895                                stmtStack.pop();
5896                        } else if (stmt instanceof gudusoft.gsqlparser.stmt.mdx.TMdxSelect) {
5897                                stmtStack.push(stmt);
5898                                analyzeMdxSelectStmt((gudusoft.gsqlparser.stmt.mdx.TMdxSelect) stmt);
5899                                stmtStack.pop();
5900                        } else if (stmt instanceof TPowerQueryDocumentStmt) {
5901                                stmtStack.push(stmt);
5902                                analyzePowerQueryDocumentStmt((TPowerQueryDocumentStmt) stmt);
5903                                stmtStack.pop();
5904                        }
5905                } catch (Exception e) {
5906                        if (sqllrProducerMode()) {
5907                                // SQL-LR production: a statement whose analysis failed is reported FAILED by the top-level boundary
5908                                // in analyzeCustomSqlStmt, which also restores the statement stack; nothing it built is published
5909                                if (e instanceof RuntimeException) {
5910                                        throw (RuntimeException) e;
5911                                }
5912                                throw new IllegalStateException(e);
5913                        }
5914            StringBuffer errorMessage = new StringBuffer();
5915            errorMessage.append("analyze sql stmt failed, ");
5916            if (stmt.getStartToken() != null) {
5917                errorMessage.append("line: " + stmt.getStartToken().lineNo + ", column: " + stmt.getStartToken().columnNo).append(", ");
5918            }
5919            if (stmt.getGsqlparser() != null && !SQLUtil.isEmpty(stmt.getGsqlparser().sqlfilename)) {
5920                errorMessage.append("file: "+ stmt.getGsqlparser().sqlfilename).append(", ");
5921            }
5922            if (stmt.toString() != null) {
5923                errorMessage.append("sql:\n" + stmt.toString());
5924            }
5925            logger.error(errorMessage.toString(), e);
5926                        ErrorInfo errorInfo = new ErrorInfo();
5927                        errorInfo.setErrorType(ErrorInfo.ANALYZE_ERROR);
5928                        if (e.getMessage() == null) {
5929                                if (e.getStackTrace() != null && e.getStackTrace().length > 0) {
5930                                        errorInfo.setErrorMessage(e.getClass().getSimpleName() + ": " + e.getStackTrace()[0].toString());
5931                                } else {
5932                                        errorInfo.setErrorMessage(e.getClass().getSimpleName());
5933                                }
5934                        } else {
5935                                errorInfo.setErrorMessage(e.getClass().getSimpleName() + ": " + e.getMessage());
5936                        }
5937                        errorInfo.setStartPosition(new Pair3<Long, Long, String>(stmt.getStartToken().lineNo,
5938                                        stmt.getStartToken().columnNo, ModelBindingManager.getGlobalHash()));
5939                        String[] segments = stmt.getEndToken().getAstext().split("\n", -1);
5940                        if (segments.length == 1) {
5941                                errorInfo.setEndPosition(new Pair3<Long, Long, String>(stmt.getEndToken().lineNo,
5942                                                stmt.getEndToken().columnNo + stmt.getEndToken().getAstext().length(),
5943                                                ModelBindingManager.getGlobalHash()));
5944                        } else {
5945                                errorInfo.setEndPosition(new Pair3<Long, Long, String>(stmt.getEndToken().lineNo + segments.length - 1,
5946                                                (long) segments[segments.length - 1].length() + 1, ModelBindingManager.getGlobalHash()));
5947                        }
5948                        errorInfo.fillInfo(this);
5949                        errorInfos.add(errorInfo);
5950                }
5951        }
5952
5953        private void analyzePlsqlRecordTypeDefStmt(TPlsqlRecordTypeDefStmt stmt) {
5954                TObjectName typeName = stmt.getTypeName();
5955                Variable variable = modelFactory.createVariable(typeName);
5956                variable.setSubType(SubType.record_type);
5957        
5958                if (stmt.getFieldDeclarations() != null) {
5959                        for (int i = 0; i < stmt.getFieldDeclarations().size(); i++) {
5960                                TParameterDeclaration param = stmt.getFieldDeclarations().getParameterDeclarationItem(i);
5961                                TTypeName dataType = param.getDataType();
5962                                TObjectName columnName = param.getParameterName();      
5963                                TableColumn variableProperty = modelFactory.createTableColumn(variable, columnName, true);
5964                                boolean columnTypeAnchor = false;
5965                                for (int j = 0; j < dataType.getDatatypeAttributeList().size(); j++) {
5966                                        if (dataType.getDatatypeAttributeList().getElement(j).getAttributeType()
5967                                                        == EDataTypeAttribute.plsql_type_t) {
5968                                                columnTypeAnchor = true;
5969                                                break;
5970                                        }
5971                                }
5972                                TObjectName typeColumn = dataType.getDataTypeObjectName();
5973                                if (columnTypeAnchor && typeColumn != null && typeColumn.getObjectToken() != null) {
5974                                        // Use the parsed qualifier, not the last dot in the rendered name:
5975                                        // a quoted column such as "Last.Name" contains a dot of its own.
5976                                        TObjectName tableName = typeColumn.getSchemaToken() == null
5977                                                        ? TObjectName.createObjectName(option.getVendor(), EDbObjectType.table,
5978                                                                        typeColumn.getObjectToken())
5979                                                        : TObjectName.createObjectName(option.getVendor(), EDbObjectType.table,
5980                                                                        typeColumn.getSchemaToken(), typeColumn.getObjectToken());
5981                                        Table table = modelFactory.createTypeAnchor(tableName);
5982                                        if(table!=null) {
5983                                                TableColumn tableColumn = modelFactory.createInsertTableColumn(table, typeColumn);
5984                                                if (tableColumn != null) {
5985                                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
5986                                                        relation.setEffectType(EffectType.rowtype);
5987                                                        relation.setTarget(new TableColumnRelationshipElement(variableProperty));
5988                                                        relation.addSource(new TableColumnRelationshipElement(tableColumn));
5989                                                }
5990                                        }
5991                                }
5992                        }
5993                        variable.setDetermined(true);
5994                }
5995                else {
5996                        TObjectName starColumn = DlineageUtil.syntheticStarName();
5997                }
5998        }
5999        
6000        private void analyzePlsqlTableTypeDefStmt(TPlsqlTableTypeDefStmt stmt) {
6001                TTypeName typeName = stmt.getElementDataType();
6002                if (typeName != null && typeName.toString().toUpperCase(Locale.ROOT).indexOf("ROWTYPE") != -1) {
6003                        Variable cursorVariable = modelFactory.createVariable(stmt.getTypeName());
6004                        cursorVariable.setSubType(SubType.record_type);
6005
6006                        Table variableTable = modelFactory.createRowTypeAnchor(typeName.getDataTypeName());
6007                        if(!variableTable.isCreateTable()) {
6008                                TObjectName starColumn1 = DlineageUtil.syntheticStarName();
6009                                TableColumn variableTableStarColumn = modelFactory.createTableColumn(variableTable, starColumn1, true);
6010                                variableTableStarColumn.setShowStar(false);
6011                                variableTableStarColumn.setExpandStar(true);
6012
6013                                TObjectName starColumn = DlineageUtil.syntheticStarName();
6014                                TableColumn variableProperty = modelFactory.createTableColumn(cursorVariable, starColumn, true);
6015                                variableProperty.setShowStar(false);
6016                                variableProperty.setExpandStar(true);
6017
6018                                DataFlowRelationship dataflowRelation = modelFactory.createDataFlowRelation();
6019                                dataflowRelation.setEffectType(EffectType.rowtype);
6020                                dataflowRelation.addSource(new TableColumnRelationshipElement(variableTableStarColumn));
6021                                dataflowRelation.setTarget(new TableColumnRelationshipElement(variableProperty));
6022                        } else {
6023                                for (TableColumn sourceColumn : variableTable.getColumns()) {
6024                                        String columnName = sourceColumn.getName();
6025                                        TObjectName targetColumn = new TObjectName();
6026                                        targetColumn.setString(columnName);
6027                                        TableColumn variableProperty = modelFactory.createTableColumn(cursorVariable, targetColumn, true);
6028                                        DataFlowRelationship dataflowRelation = modelFactory.createDataFlowRelation();
6029                                        dataflowRelation.setEffectType(EffectType.rowtype);
6030                                        dataflowRelation.addSource(new TableColumnRelationshipElement(sourceColumn));
6031                                        dataflowRelation.setTarget(new TableColumnRelationshipElement(variableProperty));
6032                                }
6033                        }
6034                } 
6035        }
6036
6037        private void analyzeMssqlCreateType(TMssqlCreateType createType) {
6038
6039        }
6040
6041        /**
6042         * UPDATETEXT table.dest_column dest_text_ptr {NULL|offset} {NULL|del_len}
6043         * [WITH LOG] [inserted_data | table.src_column src_text_ptr] (Mantis #4607).
6044         * The written column is a real data-write target: emit inserted_data (variable
6045         * or literal) -&gt; dest column, and for the column-copy form the proven
6046         * src_column -&gt; dest_column edge.
6047         */
6048        private void analyzeMssqlUpdateTextStmt(TMssqlUpdateText stmt) {
6049                TableColumn destColumn = createTextPtrTargetColumn(stmt.getTargetTable(), stmt.getDestColumnName());
6050                if (destColumn == null) {
6051                        return;
6052                }
6053                List<TObjectName> sourceNames = new ArrayList<TObjectName>();
6054                if (stmt.getInsertedVariable() != null) {
6055                        sourceNames.add(stmt.getInsertedVariable());
6056                }
6057                if (stmt.getSourceColumnName() != null && stmt.getCopySourceTable() != null) {
6058                        // make sure the copy-source table has a model so the edge resolves
6059                        modelFactory.createTable(stmt.getCopySourceTable());
6060                        sourceNames.add(stmt.getSourceColumnName());
6061                }
6062                if (!sourceNames.isEmpty()) {
6063                        analyzeDataFlowRelation(destColumn, sourceNames, EffectType.update, null);
6064                }
6065                if (stmt.getInsertedLiteral() != null) {
6066                        List<TParseTreeNode> constants = new ArrayList<TParseTreeNode>();
6067                        constants.add(stmt.getInsertedLiteral());
6068                        analyzeConstantDataFlowRelation(destColumn, constants, EffectType.update, null);
6069                }
6070        }
6071
6072        /** WRITETEXT table.column text_ptr [WITH LOG] data (Mantis #4607): data -&gt; column. */
6073        private void analyzeMssqlWriteTextStmt(TMssqlWriteText stmt) {
6074                TableColumn destColumn = createTextPtrTargetColumn(stmt.getTargetTable(), stmt.getDestColumnName());
6075                if (destColumn == null) {
6076                        return;
6077                }
6078                if (stmt.getWriteDataVariable() != null) {
6079                        List<TObjectName> sourceNames = new ArrayList<TObjectName>();
6080                        sourceNames.add(stmt.getWriteDataVariable());
6081                        analyzeDataFlowRelation(destColumn, sourceNames, EffectType.update, null);
6082                }
6083                if (stmt.getWriteDataLiteral() != null) {
6084                        List<TParseTreeNode> constants = new ArrayList<TParseTreeNode>();
6085                        constants.add(stmt.getWriteDataLiteral());
6086                        analyzeConstantDataFlowRelation(destColumn, constants, EffectType.update, null);
6087                }
6088        }
6089
6090        /**
6091         * READTEXT table.column text_ptr offset size [HOLDLOCK] (Mantis #4607): the
6092         * column is read into the client stream; model the output as the statement's
6093         * result set with the read column so the read is visible in the lineage.
6094         */
6095        private void analyzeMssqlReadTextStmt(TMssqlReadText stmt) {
6096                TObjectName columnRef = stmt.getSourceColumnName();
6097                if (columnRef == null || columnRef.getSourceTable() == null) {
6098                        return;
6099                }
6100                Table tableModel = modelFactory.createTable(columnRef.getSourceTable());
6101                if (tableModel == null) {
6102                        return;
6103                }
6104                TableColumn sourceColumn = modelFactory.createTableColumn(tableModel, columnRef, false);
6105                if (sourceColumn == null) {
6106                        return;
6107                }
6108                ResultSet resultSet = modelFactory.createResultSet(stmt, true);
6109                ResultColumn resultColumn = modelFactory.createResultColumn(resultSet, columnRef);
6110                if (resultColumn == null) {
6111                        return;
6112                }
6113                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
6114                relation.setEffectType(EffectType.select);
6115                relation.setTarget(new ResultColumnRelationshipElement(resultColumn));
6116                relation.addSource(new TableColumnRelationshipElement(sourceColumn));
6117        }
6118
6119        /**
6120         * The write target of an UPDATETEXT/WRITETEXT statement: the model column of
6121         * the fused table.column reference, or null when the statement carries no
6122         * table-qualified column.
6123         */
6124        private TableColumn createTextPtrTargetColumn(TTable targetTable, TObjectName columnRef) {
6125                if (targetTable == null || columnRef == null) {
6126                        return null;
6127                }
6128                Table tableModel = modelFactory.createTable(targetTable);
6129                if (tableModel == null) {
6130                        return null;
6131                }
6132                return modelFactory.createTableColumn(tableModel, columnRef, false);
6133        }
6134
6135        private void analyzeMssqlExecute(TMssqlExecute executeStmt) {
6136                if (executeStmt.getModuleName() != null) {
6137                        TObjectName module = executeStmt.getModuleName();
6138                        if(module.toString().toLowerCase(Locale.ROOT).endsWith("sp_rename")) {
6139                                String oldTableName = SQLUtil.trimColumnStringQuote(executeStmt.getParameters().getExecParameter(0).toString());        
6140                                Table oldNameTableModel = modelFactory.createTableByName(oldTableName, true);
6141                                List<String> oldTableNames = SQLUtil.parseNames(oldNameTableModel.getName());
6142                                TObjectName oldStarColumn = DlineageUtil.syntheticStarName();
6143                                TableColumn oldTableStarColumn = modelFactory.createTableColumn(oldNameTableModel, oldStarColumn, true);
6144                                
6145                                String newTableName = SQLUtil.trimColumnStringQuote(executeStmt.getParameters().getExecParameter(1).toString());
6146                                List<String> newTableNames = SQLUtil.parseNames(newTableName);
6147                                if (oldTableNames.size() > newTableNames.size()) {
6148                                        for (int i = oldTableNames.size() - newTableNames.size() - 1; i >= 0; i--) {
6149                                                newTableName = (oldTableNames.get(i) + ".") + newTableName;
6150                                        }
6151                                }
6152
6153                                Table newNameTableModel = modelFactory.createTableByName(newTableName, true);
6154                                TObjectName newStarColumn = DlineageUtil.syntheticStarName();
6155                                TableColumn newTableStarColumn = modelFactory.createTableColumn(newNameTableModel, newStarColumn, true);
6156                                
6157                                Process process = modelFactory.createProcess(executeStmt);
6158                                newNameTableModel.addProcess(process);
6159                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
6160                                relation.setEffectType(EffectType.rename_table);
6161                                relation.setTarget(new TableColumnRelationshipElement(newTableStarColumn));
6162                                relation.addSource(new TableColumnRelationshipElement(oldTableStarColumn));
6163                                relation.setProcess(process);
6164                                
6165                                if ((oldNameTableModel.isCreateTable() || oldNameTableModel.hasSQLEnv())) {
6166                                        oldTableStarColumn.setShowStar(false);
6167                                        relation.setShowStarRelation(false);
6168                                }
6169
6170                                if ((newNameTableModel.isCreateTable() || newNameTableModel.hasSQLEnv())) {
6171                                        newTableStarColumn.setShowStar(false);
6172                                        relation.setShowStarRelation(false);
6173                                }
6174                        }
6175                        else if(module.toString().toLowerCase(Locale.ROOT).endsWith("sp_executesql")) {
6176                                String sql = SQLUtil.trimColumnStringQuote(executeStmt.getParameters().getExecParameter(0).toString());
6177                                if(sql.startsWith("N")) {
6178                                        sql =  SQLUtil.trimColumnStringQuote(sql.substring(1));
6179                                }
6180                                executeDynamicSql(sql);
6181                        }
6182                        else {
6183                                // sp_execute_external_script runs an R / Python / Java / ONNX script
6184                                // in-database (SQL Server 2016+ Machine Learning Services). Its
6185                                // @input_data_1 parameter is a SQL string literal whose SELECT reads
6186                                // real tables to build the InputDataSet the script consumes. Recover
6187                                // that source-table -> result-set lineage instead of dropping it
6188                                // (MantisBT #4606). This runs IN ADDITION to the generic call-edge
6189                                // handling below, so the call to sp_execute_external_script is still
6190                                // recorded.
6191                                if (option.isAnalyzeDynamicSql()
6192                                                && isSpExecuteExternalScript(module)) {
6193                                        analyzeExternalScriptInputData(executeStmt);
6194                                }
6195                                int argumentSize = executeStmt.getParameters() == null ? 0 : executeStmt.getParameters().size();
6196                                analyzeProcedureDefinition(nameAliasKey(module,
6197                                                argumentSize <= 0 ? null : RoutineAliases.countIfOverloads(argumentSize, option.getVendor())));
6198                                Procedure procedure = modelFactory.createProcedureByName(module, executeStmt.getParameters() == null ? 0 : executeStmt.getParameters().size());
6199                                String procedureParent = getProcedureParentName(executeStmt);
6200                                if (procedureParent != null) {
6201                                        Procedure caller = getEnclosingProcedure(executeStmt);
6202                                        if (caller != null) {
6203                                                CallRelationship callRelation = modelFactory.createCallRelation();
6204                                                callRelation.setCallObject(executeStmt);
6205                                                callRelation.setTarget(new ProcedureRelationshipElement(caller));
6206                                                callRelation.addSource(new ProcedureRelationshipElement(procedure));
6207                                                if(isBuiltInFunctionName(module) || isKeyword(module)){
6208                                                        callRelation.setBuiltIn(true);
6209                                                }
6210                                        }
6211                                }
6212
6213                                if (awaitsDefinition(procedure)) {
6214                                        if (!deferredExecuteBindings.containsKey(executeStmt)) {
6215                                                deferredExecuteBindings.put(executeStmt, new DeferredExecuteBinding(procedure,
6216                                                                CallSiteState.capture(stmtStack, modelManager),
6217                                                                new ArrayList<TCustomSqlStatement>(analysisPath)));
6218                                        }
6219                                } else {
6220                                        bindExecuteArguments(executeStmt, procedure);
6221                                }
6222                        }
6223                }
6224        }
6225
6226        /** Binds each argument of an EXEC to the formal it passes, in both directions for an OUTPUT formal. */
6227        private void bindExecuteArguments(TMssqlExecute executeStmt, Procedure procedure) {
6228                if (procedure.getArguments() == null) {
6229                        return;
6230                }
6231                TObjectName module = executeStmt.getModuleName();
6232                for (int i = 0; i < procedure.getArguments().size(); i++) {
6233                        Argument argument = procedure.getArguments().get(i);
6234                        Variable variable = modelFactory.createVariable(procedure, argument.getName(), false);
6235                        if (variable != null) {
6236                                if (argument.getMode() == EParameterMode.out || argument.getMode() == EParameterMode.output) {
6237                                        Transform transform = new Transform();
6238                                        transform.setType(Transform.FUNCTION);
6239                                        transform.setCode(module);
6240                                        compositeBindingColumn(variable).setTransform(transform);
6241                                }
6242                                Process process = modelFactory.createProcess(executeStmt);
6243                                variable.addProcess(process);
6244                                analyzeFunctionArgumentsDataFlowRelation(compositeBindingColumn(variable), executeStmt, argument.getName(), i, process);
6245                        }
6246                }
6247        }
6248
6249        /**
6250         * True when an EXEC's callee is only the name it was called by: no definition has been analyzed for it, so it
6251         * has no formals to bind. A call to a routine that a later unit defines is one (Mantis 4730): units are analyzed
6252         * one at a time and each knows only its own definitions. A definition with no parameters is not.
6253         */
6254        private static boolean awaitsDefinition(Procedure procedure) {
6255                return procedure.getProcedureObject() instanceof TObjectName
6256                                && (procedure.getArguments() == null || procedure.getArguments().isEmpty());
6257        }
6258
6259        /**
6260         * Binds the arguments of the EXECs that called a routine before any definition of it was analyzed, once the unit
6261         * that defines the routine is analyzed. That definition takes over the model the call created, formals included
6262         * (see ModelFactory.createProcedure); each such call is then bound in the state it was analyzed in, as it is when
6263         * its unit comes right after the definition's. Calls whose routine no unit analyzed so far defines stay pending.
6264         */
6265        private void bindDeferredExecuteArguments() {
6266                Iterator<Map.Entry<TMssqlExecute, DeferredExecuteBinding>> pending = deferredExecuteBindings.entrySet().iterator();
6267                while (pending.hasNext()) {
6268                        Map.Entry<TMssqlExecute, DeferredExecuteBinding> entry = pending.next();
6269                        DeferredExecuteBinding deferred = entry.getValue();
6270                        if (awaitsDefinition(deferred.callee)) {
6271                                continue;
6272                        }
6273                        pending.remove();
6274                        CallSiteState current = CallSiteState.capture(stmtStack, modelManager);
6275                        deferred.callSite.enter(stmtStack, modelManager);
6276                        int relationshipsBefore = modelManager.getRelations().length;
6277                        try {
6278                                bindExecuteArguments(entry.getKey(), deferred.callee);
6279                                attributeToEnclosingDynamicSite(deferred.path, relationshipsBefore);
6280                        } finally {
6281                                deferred.callSite.leave(stmtStack, modelManager);
6282                                current.restore(stmtStack);
6283                        }
6284                }
6285        }
6286
6287        /**
6288         * Adds the relationships that a deferred binding just created to the dynamic SQL site whose text holds the call,
6289         * the innermost one on the call's analysis path, as they are when the call binds inside that site's analysis.
6290         * They count as the site's lineage, and are proven only when every relationship the site recorded is proven and the
6291         * site's own proof filter accepts them; anything short of that leaves them observed, never proven.
6292         */
6293        private void attributeToEnclosingDynamicSite(List<TCustomSqlStatement> path, int relationshipsBefore) {
6294                for (int i = path.size() - 1; i >= 0; i--) {
6295                        // a statement with a site of its own (an EXEC inside a variant's text) belongs to it; another statement
6296                        // of a variant's text, to that variant's site
6297                        Integer index = dynamicSiteIndexes.get(path.get(i));
6298                        if (index == null) {
6299                                index = dynamicInnerSiteIndexes.get(path.get(i));
6300                        }
6301                        if (index == null) {
6302                                continue;
6303                        }
6304                        DynamicSqlSite site = dynamicSqlSites.get(index);
6305                        List<Long> added = dynamicRelationshipIdsSince(relationshipsBefore, dynamicSqlSites.size());
6306                        if (added.isEmpty()) {
6307                                return;
6308                        }
6309                        boolean complete = site.isHoleCountExact() && site.getUnresolvedHoleCount() == 0;
6310                        boolean proven = complete && site.getStatus() == DynamicSqlSite.Status.RESOLVED
6311                                        && site.getUnprovenRelationshipCount() == 0
6312                                        && site.getProvenRelationshipIds().size() == site.getObservedRelationshipIds().size();
6313                        List<Long> addedProven = proven
6314                                        ? provenDynamicRelationshipIdsSince(relationshipsBefore, dynamicSqlSites.size())
6315                                        : Collections.<Long>emptyList();
6316                        int[] counts = classifyDynamicSiteLineage(relationshipsBefore, !complete);
6317                        List<Long> observedIds = new ArrayList<Long>(site.getObservedRelationshipIds());
6318                        observedIds.addAll(added);
6319                        List<Long> provenIds = new ArrayList<Long>(site.getProvenRelationshipIds());
6320                        provenIds.addAll(addedProven);
6321                        int unproven = site.getUnprovenRelationshipCount();
6322                        if (site.getStatus() == DynamicSqlSite.Status.PARTIAL) {
6323                                unproven += added.size() - addedProven.size();
6324                        }
6325                        dynamicSqlSites.set(index, new DynamicSqlSite(site.getKind(), site.getStatus(), site.getReason(),
6326                                        site.getStartPosition(), site.getEndPosition(),
6327                                        site.getResolvedRelationshipCount() + counts[0], site.getPartialRelationshipCount() + counts[1],
6328                                        unproven, site.getUnresolvedHoleCount(), site.isHoleCountExact(), site.getTrustMode(),
6329                                        observedIds, provenIds, site.getDiagnostic(), site.getUnresolvedFragments(),
6330                                        site.getMaterializedText(), site.isExecutionCertain()));
6331                        return;
6332                }
6333        }
6334
6335        private static final class DeferredExecuteBinding {
6336                final Procedure callee;
6337                final CallSiteState callSite;
6338                /** The statements being analyzed when the call was, outermost first (analysisPath). */
6339                final List<TCustomSqlStatement> path;
6340
6341                DeferredExecuteBinding(Procedure callee, CallSiteState callSite, List<TCustomSqlStatement> path) {
6342                        this.callee = callee;
6343                        this.callSite = callSite;
6344                        this.path = path;
6345                }
6346        }
6347
6348        /**
6349         * The analysis state a statement is analyzed in and that the models its relationships create read: the statement
6350         * stack (local symbol scopes), the routine being analyzed, the unit (coordinates) and its current database and
6351         * schema, and the overload pool. A deferred relationship is built in the state of the statement it belongs to.
6352         */
6353        private static final class CallSiteState {
6354                final List<TCustomSqlStatement> stack;
6355                final TStoredProcedureSqlStatement routine;
6356                final String hash;
6357                final String database;
6358                final IdentifierInputForm databaseForm;
6359                final String schema;
6360                final IdentifierInputForm schemaForm;
6361                final TSQLEnv sqlEnv;
6362                final String defaultCatalog;
6363                final String defaultSchema;
6364                final Object poolScope;
6365                final String poolKey;
6366
6367                @SuppressWarnings("deprecation")
6368                private CallSiteState(Stack<TCustomSqlStatement> stmtStack, ModelBindingManager modelManager) {
6369                        stack = new ArrayList<TCustomSqlStatement>(stmtStack);
6370                        routine = ModelBindingManager.getGlobalProcedure();
6371                        hash = ModelBindingManager.getGlobalHash();
6372                        database = ModelBindingManager.getGlobalDatabase();
6373                        databaseForm = ModelBindingManager.getGlobalDatabaseForm();
6374                        schema = ModelBindingManager.getGlobalSchema();
6375                        schemaForm = ModelBindingManager.getGlobalSchemaForm();
6376                        sqlEnv = ModelBindingManager.getGlobalSQLEnv();
6377                        defaultCatalog = sqlEnv == null ? null : sqlEnv.getDefaultCatalogName();
6378                        defaultSchema = sqlEnv == null ? null : sqlEnv.getDefaultSchemaName();
6379                        poolScope = modelManager.peekPoolScope();
6380                        poolKey = modelManager.peekIdentityScope();
6381                }
6382
6383                static CallSiteState capture(Stack<TCustomSqlStatement> stmtStack, ModelBindingManager modelManager) {
6384                        return new CallSiteState(stmtStack, modelManager);
6385                }
6386
6387                /** Makes this state current; {@link #leave} undoes what only entering adds (the overload pool). */
6388                void enter(Stack<TCustomSqlStatement> stmtStack, ModelBindingManager modelManager) {
6389                        restore(stmtStack);
6390                        modelManager.pushIdentityScope(poolKey, poolScope);
6391                }
6392
6393                void leave(Stack<TCustomSqlStatement> stmtStack, ModelBindingManager modelManager) {
6394                        if (poolKey != null || poolScope != null) {
6395                                modelManager.popIdentityScope();
6396                        }
6397                }
6398
6399                /** Makes this state current, the overload pool apart. */
6400                void restore(Stack<TCustomSqlStatement> stmtStack) {
6401                        stmtStack.clear();
6402                        stmtStack.addAll(stack);
6403                        ModelBindingManager.restoreGlobalProcedure(routine);
6404                        if (hash != null) {
6405                                ModelBindingManager.setGlobalHash(hash);
6406                        } else {
6407                                ModelBindingManager.removeGlobalHash();
6408                        }
6409                        if (database != null) {
6410                                ModelBindingManager.setGlobalDatabase(database, databaseForm);
6411                        } else {
6412                                ModelBindingManager.removeGlobalDatabase();
6413                        }
6414                        if (schema != null) {
6415                                ModelBindingManager.setGlobalSchema(schema, schemaForm);
6416                        } else {
6417                                ModelBindingManager.removeGlobalSchema();
6418                        }
6419                        if (sqlEnv != null) {
6420                                ModelBindingManager.setGlobalSQLEnv(sqlEnv);
6421                                sqlEnv.setDefaultCatalogName(defaultCatalog);
6422                                sqlEnv.setDefaultSchemaName(defaultSchema);
6423                        } else {
6424                                ModelBindingManager.removeGlobalSQLEnv();
6425                        }
6426                }
6427        }
6428
6429        private void analyzeDropTableStmt(TDropTableSqlStatement stmt) {
6430                TTable dropTable = stmt.getTargetTable();
6431                if(dropTable == null) {
6432                        return;
6433                }
6434                Table tableModel = modelManager.getTableByName(DlineageUtil.getTableIdentityKey(dropTable.getTableName().toString()));
6435                if(tableModel!=null) {
6436                        modelManager.dropTable(tableModel);
6437                }
6438                
6439                if(option.getAnalyzeMode() == AnalyzeMode.crud) {
6440                        tableModel = modelFactory.createTable(dropTable);
6441                        CrudRelationship crudRelationship = modelFactory.createCrudRelation();
6442                        crudRelationship.setTarget(new TableRelationshipElement(tableModel));
6443                        crudRelationship.setEffectType(EffectType.drop_table);
6444                }
6445        }
6446        
6447        private void analyzeTruncateTableStmt(TTruncateStatement stmt) {
6448                if(option.getAnalyzeMode() == AnalyzeMode.crud) {
6449                        TObjectName table = stmt.getTableName();
6450                        Table tableModel = modelFactory.createTableByName(table);
6451                        CrudRelationship crudRelationship = modelFactory.createCrudRelation();
6452                        crudRelationship.setTarget(new TableRelationshipElement(tableModel));
6453                        crudRelationship.setEffectType(EffectType.truncate_table);
6454                }
6455        }
6456
6457        private void analyzeCallStmt(TCallStatement callStmt) {
6458                if (callStmt.getRoutineExpr() != null && callStmt.getRoutineExpr().getFunctionCall() != null) {
6459
6460                        TFunctionCall functionCall = callStmt.getRoutineExpr().getFunctionCall();
6461                        Procedure callee = modelManager.getProcedure(callAliasKey(functionCall));
6462                        if (callee == null && analyzeProcedureDefinition(callAliasKey(functionCall))) {
6463                                callee = modelManager.getProcedure(callAliasKey(functionCall));
6464                        }
6465                        if (gudusoft.gsqlparser.dlineage.routine.RoutineShadow.enabled()) shadowCallee("analyzeCallStmt.call", functionCall.getFunctionName(), callee, new Integer[] {gudusoft.gsqlparser.dlineage.routine.RoutineShadow.countAliasOf(functionCall)}, gudusoft.gsqlparser.catalog.runtime.CatalogObjectKind.PROCEDURE);
6466                        if (callee != null) {
6467                                String procedureParent = getProcedureParentName(callStmt);
6468                                if (procedureParent != null) {
6469                                        Procedure caller = getEnclosingProcedure(callStmt);
6470                                        if (caller != null) {
6471                                                CallRelationship callRelation = modelFactory.createCallRelation();
6472                                                callRelation.setCallObject(callStmt);
6473                                                callRelation.setTarget(new ProcedureRelationshipElement(caller));
6474                                                callRelation.addSource(new ProcedureRelationshipElement(callee));
6475                                                if (isBuiltInFunctionName(functionCall.getFunctionName())
6476                                                                || isKeyword(functionCall.getFunctionName())) {
6477                                                        callRelation.setBuiltIn(true);
6478                                                }
6479                                        }
6480                                }
6481                                if (callee.getArguments() != null) {
6482                                        for (int i = 0; i < callee.getArguments().size(); i++) {
6483                                                Argument argument = callee.getArguments().get(i);
6484                                                Variable variable = resolveFormalVariable(callee, argument);
6485                                                if (variable != null) {
6486                                                        if (argument.getMode() == EParameterMode.out || argument.getMode() == EParameterMode.output) {
6487                                                                Transform transform = new Transform();
6488                                                                transform.setType(Transform.FUNCTION);
6489                                                                transform.setCode(functionCall);
6490                                                                compositeBindingColumn(variable).setTransform(transform);
6491                                                        }
6492                                                        Process process = modelFactory.createProcess(callStmt);
6493                                                        variable.addProcess(process);
6494                                                        analyzeFunctionArgumentsDataFlowRelation(compositeBindingColumn(variable), functionCall, i,
6495                                                                        process);
6496                                                }
6497                                        }
6498                                }
6499                        } else {
6500                                Function function = (Function) createFunction(functionCall);
6501                                String procedureParent = getProcedureParentName(callStmt);
6502                                if (procedureParent != null) {
6503                                        Procedure caller = getEnclosingProcedure(callStmt);
6504                                        if (caller != null) {
6505                                                CallRelationship callRelation = modelFactory.createCallRelation();
6506                                                callRelation.setCallObject(callStmt);
6507                                                callRelation.setTarget(new ProcedureRelationshipElement(caller));
6508                                                callRelation.addSource(new FunctionRelationshipElement(function));
6509                                                if (isBuiltInFunctionName(functionCall.getFunctionName())
6510                                                                || isKeyword(functionCall.getFunctionName())) {
6511                                                        callRelation.setBuiltIn(true);
6512                                                }
6513                                        }
6514                                }
6515                        }
6516                }
6517                else if (callStmt.getRoutineName() != null) {
6518                        TObjectName function = callStmt.getRoutineName();
6519                        RoutineAliasKey functionKey = nameAliasKey(function, null);
6520                        Procedure callee = modelManager.getProcedure(functionKey);
6521                        if (callee == null && analyzeProcedureDefinition(functionKey)) {
6522                                callee = modelManager.getProcedure(functionKey);
6523                        }
6524                        if (gudusoft.gsqlparser.dlineage.routine.RoutineShadow.enabled()) shadowCallee("analyzeCallStmt.name", function, callee, new Integer[] {null}, gudusoft.gsqlparser.catalog.runtime.CatalogObjectKind.PROCEDURE);
6525                        if (callee != null) {
6526                                String procedureParent = getProcedureParentName(callStmt);
6527                                if (procedureParent != null) {
6528                                        Procedure caller = getEnclosingProcedure(callStmt);
6529                                        if (caller != null) {
6530                                                CallRelationship callRelation = modelFactory.createCallRelation();
6531                                                callRelation.setCallObject(callStmt);
6532                                                callRelation.setTarget(new ProcedureRelationshipElement(caller));
6533                                                callRelation.addSource(new ProcedureRelationshipElement(callee));
6534                                                if (isBuiltInFunctionName(function)
6535                                                                || isKeyword(function)) {
6536                                                        callRelation.setBuiltIn(true);
6537                                                }
6538                                        }
6539                                }
6540                                if (callee.getArguments() != null) {
6541                                        for (int i = 0; i < callee.getArguments().size(); i++) {
6542                                                Argument argument = callee.getArguments().get(i);
6543                                                Variable variable = resolveFormalVariable(callee, argument);
6544                                                if (variable != null) {
6545                                                        if (argument.getMode() == EParameterMode.out || argument.getMode() == EParameterMode.output) {
6546                                                                Transform transform = new Transform();
6547                                                                transform.setType(Transform.FUNCTION);
6548                                                                transform.setCode(callStmt);
6549                                                                compositeBindingColumn(variable).setTransform(transform);
6550                                                        }
6551                                                        Process process = modelFactory.createProcess(callStmt);
6552                                                        variable.addProcess(process);
6553                                                        analyzeFunctionArgumentsDataFlowRelation(compositeBindingColumn(variable), callStmt, i,
6554                                                                        process);
6555                                                }
6556                                        }
6557                                }
6558                        }
6559                }
6560        }
6561
6562        /**
6563         * DB2's CALL node is {@link TDb2CallStmt} (a sibling of {@link TCallStatement},
6564         * not a subclass), so the generic call dispatch never reaches it. Resolve its
6565         * caller/callee and emit the call relationship exactly as the
6566         * {@link #analyzeCallStmt} getRoutineName path does, using the DB2 accessors
6567         * getProcedureName()/getParameters().
6568         */
6569        private void analyzeDb2CallStmt(TDb2CallStmt callStmt) {
6570                if (callStmt.getProcedureName() == null) {
6571                        return;
6572                }
6573                TObjectName function = callStmt.getProcedureName();
6574                // DB2 supports procedure overloading by arity, so overloadable procedures
6575                // register under a "name(argCount)" key (see RoutineAliases.argumentCount).
6576                // Resolve by name+argCount first to bind the correct overload, then fall
6577                // back to the plain name for the single-definition / non-overloaded case.
6578                RoutineAliasKey nameKey = nameAliasKey(function, null);
6579                String count = callStmt.getParameters() == null ? null
6580                                : RoutineAliases.countIfOverloads(callStmt.getParameters().size(), ModelBindingManager.getGlobalVendor());
6581                RoutineAliasKey countKey = nameKey == null || count == null ? nameKey : nameKey.withSignature(count);
6582                Procedure callee = modelManager.getProcedure(countKey);
6583                if (callee == null && countKey != nameKey) {
6584                        callee = modelManager.getProcedure(nameKey);
6585                }
6586                if (callee == null && analyzeProcedureDefinition(countKey)) {
6587                        callee = modelManager.getProcedure(countKey);
6588                }
6589                if (callee == null && analyzeProcedureDefinition(nameKey)) {
6590                        callee = modelManager.getProcedure(nameKey);
6591                }
6592                if (gudusoft.gsqlparser.dlineage.routine.RoutineShadow.enabled()) shadowCallee("analyzeDb2CallStmt", function, callee, new Integer[] {gudusoft.gsqlparser.dlineage.routine.RoutineDeclaration.callsCounted(ModelBindingManager.getGlobalVendor()) ? Integer.valueOf(callStmt.getParameters() == null ? 0 : callStmt.getParameters().size()) : null, null}, gudusoft.gsqlparser.catalog.runtime.CatalogObjectKind.PROCEDURE);
6593                if (callee != null) {
6594                        String procedureParent = getProcedureParentName(callStmt);
6595                        if (procedureParent != null) {
6596                                Procedure caller = getEnclosingProcedure(callStmt);
6597                                if (caller != null) {
6598                                        CallRelationship callRelation = modelFactory.createCallRelation();
6599                                        callRelation.setCallObject(callStmt);
6600                                        callRelation.setTarget(new ProcedureRelationshipElement(caller));
6601                                        callRelation.addSource(new ProcedureRelationshipElement(callee));
6602                                        if (isBuiltInFunctionName(function) || isKeyword(function)) {
6603                                                callRelation.setBuiltIn(true);
6604                                        }
6605                                }
6606                        }
6607                        if (callee.getArguments() != null) {
6608                                for (int i = 0; i < callee.getArguments().size(); i++) {
6609                                        Argument argument = callee.getArguments().get(i);
6610                                        Variable variable = resolveFormalVariable(callee, argument);
6611                                        if (variable != null) {
6612                                                if (argument.getMode() == EParameterMode.out || argument.getMode() == EParameterMode.output) {
6613                                                        Transform transform = new Transform();
6614                                                        transform.setType(Transform.FUNCTION);
6615                                                        transform.setCode(callStmt);
6616                                                        compositeBindingColumn(variable).setTransform(transform);
6617                                                }
6618                                                Process process = modelFactory.createProcess(callStmt);
6619                                                variable.addProcess(process);
6620                                                analyzeFunctionArgumentsDataFlowRelation(compositeBindingColumn(variable), callStmt, i,
6621                                                                process);
6622                                        }
6623                                }
6624                        }
6625                }
6626        }
6627
6628        private boolean analyzeCustomFunctionCall(TFunctionCall functionCall) {
6629                Procedure callee = modelManager.getProcedure(callAliasKey(functionCall));
6630                if(callee == null && analyzeProcedureDefinition(callAliasKey(functionCall))) {
6631                        callee = modelManager.getProcedure(callAliasKey(functionCall));
6632                }
6633                if (gudusoft.gsqlparser.dlineage.routine.RoutineShadow.enabled()) shadowCallee("analyzeCustomFunctionCall", functionCall.getFunctionName(), callee, new Integer[] {gudusoft.gsqlparser.dlineage.routine.RoutineShadow.countAliasOf(functionCall)}, gudusoft.gsqlparser.catalog.runtime.CatalogObjectKind.FUNCTION);
6634                if (callee != null) {
6635                        String procedureParent = getProcedureParentName(stmtStack.peek());
6636                        if (procedureParent != null) {
6637                                Procedure caller = getEnclosingProcedure(stmtStack.peek());
6638                                if (caller != null) {
6639                                        CallRelationship callRelation = modelFactory.createCallRelation();
6640                                        callRelation.setCallObject(functionCall);
6641                                        callRelation.setTarget(new ProcedureRelationshipElement(caller));
6642                                        callRelation.addSource(new ProcedureRelationshipElement(callee));
6643                                        if(isBuiltInFunctionName(functionCall.getFunctionName()) || isKeyword(functionCall.getFunctionName())){
6644                                                callRelation.setBuiltIn(true);
6645                                        }
6646                                }
6647                        }
6648                        if (callee.getArguments() != null) {
6649                                for (int i = 0; i < callee.getArguments().size(); i++) {
6650                                        Argument argument = callee.getArguments().get(i);
6651                                        Variable variable = resolveFormalVariable(callee, argument);
6652                                        if(variable!=null) {
6653                                                if (argument.getMode() == EParameterMode.out || argument.getMode() == EParameterMode.output) {
6654                                                        Transform transform = new Transform();
6655                                                        transform.setType(Transform.FUNCTION);
6656                                                        transform.setCode(functionCall);
6657                                                        compositeBindingColumn(variable).setTransform(transform);
6658                                                }
6659                                                Process process = modelFactory.createProcess(functionCall);
6660                                                variable.addProcess(process);
6661                                                analyzeFunctionArgumentsDataFlowRelation(compositeBindingColumn(variable), functionCall, i, process);
6662                                        }
6663                                }
6664                        }
6665                        return true;
6666                }
6667                return false;
6668        }
6669
6670        /** Resolve raw Oracle names within their declared scope, before legacy display-name aliases. */
6671        private Procedure resolveOracleRoutineCall(TBasicStmt site, TObjectName name, TFunctionCall call) {
6672                List<String> parts = SQLUtil.parseNames(name.toString());
6673                TStoredProcedureSqlStatement enclosing = oracleRoutineParent(site);
6674                OraclePackage currentPackage = ModelBindingManager.getGlobalOraclePackage();
6675                Procedure found = null;
6676                int bestScope = Integer.MIN_VALUE;
6677                boolean ambiguous = false;
6678                Set<Procedure> seen = Collections.newSetFromMap(new IdentityHashMap<Procedure, Boolean>());
6679                for (TStoredProcedureSqlStatement declaration : modelManager.getProcedures()) {
6680                        Object model = modelManager.getModel(declaration);
6681                        if (!(model instanceof Procedure) || getProcedureName(declaration) == null) continue;
6682                        Procedure candidate = (Procedure) model;
6683                        if (!seen.add(candidate)) continue;
6684                        OraclePackage owner = candidate.getParentPackage();
6685                        String declaredName = getProcedureName(declaration).toString();
6686                        int scope = 0;
6687                        TStoredProcedureSqlStatement parent = declaration.getParentStmt() == null ? null
6688                                        : oracleRoutineParent(declaration.getParentStmt());
6689                        if (parent != null && !(parent instanceof TPlsqlCreatePackage)) {
6690                                // A nested routine shadows a package member only inside its declaring scope.
6691                                TStoredProcedureSqlStatement visible = enclosing;
6692                                int depth = 0;
6693                                while (visible != null && visible != parent) {
6694                                        visible = visible.getParentStmt() == null ? null : oracleRoutineParent(visible.getParentStmt());
6695                                        depth++;
6696                                }
6697                                if (visible == null || parts.size() != 1) continue;
6698                                scope = Integer.MAX_VALUE - depth;
6699                        } else if (owner != null) {
6700                                if (parts.size() == 1) {
6701                                        if (owner != currentPackage) continue;
6702                                        scope = 1;
6703                                } else {
6704                                        TObjectName packageName = getProcedureName((TStoredProcedureSqlStatement) owner.getProcedureObject());
6705                                        String qualifier = owner == currentPackage && parts.size() == 2
6706                                                        ? packageName.getObjectString() : packageName.toString();
6707                                        declaredName = qualifier + "." + declaredName;
6708                                }
6709                        } else if (parts.size() == 1 && SQLUtil.parseNames(declaredName).size() > 1) {
6710                                // Preserve the legacy unique standalone-name fallback when the script
6711                                // omits its schema. Exact and lexical-scope matches take precedence.
6712                                declaredName = getProcedureName(declaration).getObjectString();
6713                                scope = -1;
6714                        }
6715                        if (!sameOracleRoutineName(name.toString(), declaredName)
6716                                        || !acceptsOracleArguments(declaration, call)) continue;
6717                        if (scope > bestScope) {
6718                                found = candidate;
6719                                bestScope = scope;
6720                                ambiguous = false;
6721                        } else if (scope == bestScope && found != candidate) {
6722                                ambiguous = true;
6723                        }
6724                }
6725                if (found != null) return ambiguous ? null : found;
6726                // Standalone definitions can appear later in the script. Use their raw AST names,
6727                // not the display-normalized map key, and keep the existing lazy body analysis.
6728                TStoredProcedureSqlStatement definition = uniqueOracleDefinition(procedureDDLAliases.values(), parts, name, call);
6729                if (definition != null) {
6730                        // The definition is standalone even when its caller is a package member.
6731                        OraclePackage savedPackage = ModelBindingManager.getGlobalOraclePackage();
6732                        TStoredProcedureSqlStatement savedProcedure = ModelBindingManager.getGlobalProcedure();
6733                        try {
6734                                ModelBindingManager.removeGlobalOraclePackage();
6735                                analyzeCustomSqlStmt(definition);
6736                        } finally {
6737                                ModelBindingManager.setGlobalOraclePackage(savedPackage);
6738                                ModelBindingManager.setGlobalProcedure(savedProcedure);
6739                        }
6740                        Object model = modelManager.getModel(definition);
6741                        if (model instanceof Procedure) return (Procedure) model;
6742                }
6743                return null;
6744        }
6745
6746        /** The one standalone definition that matches an Oracle call, or null (none, or two different ones). */
6747        private TStoredProcedureSqlStatement uniqueOracleDefinition(Collection<TCustomSqlStatement> definitions,
6748                        List<String> parts, TObjectName name, TFunctionCall call) {
6749                TStoredProcedureSqlStatement definition = null;
6750                for (TCustomSqlStatement statement : definitions) {
6751                        if (!(statement instanceof TStoredProcedureSqlStatement)) continue;
6752                        TStoredProcedureSqlStatement candidate = (TStoredProcedureSqlStatement) statement;
6753                        if (candidate instanceof TPlsqlCreatePackage || getProcedureName(candidate) == null) continue;
6754                        String candidateName = parts.size() == 1 ? getProcedureName(candidate).getObjectString()
6755                                        : getProcedureName(candidate).toString();
6756                        if (sameOracleRoutineName(name.toString(), candidateName)
6757                                        && acceptsOracleArguments(candidate, call)) {
6758                                if (definition != null && definition != candidate) return null;
6759                                definition = candidate;
6760                        }
6761                }
6762                return definition;
6763        }
6764
6765        private boolean sameOracleRoutineName(String left, String right) {
6766                List<String> a = SQLUtil.parseNames(left);
6767                List<String> b = SQLUtil.parseNames(right);
6768                if (a.size() != b.size()) return false;
6769                for (int i = 0; i < a.size(); i++) {
6770                        if (!DlineageUtil.sameName(ESQLDataObjectType.dotProcedure, a.get(i), b.get(i))) return false;
6771                }
6772                return true;
6773        }
6774
6775        private boolean acceptsOracleArguments(TStoredProcedureSqlStatement declaration, TFunctionCall call) {
6776                int count = call == null || call.getArgs() == null ? 0 : call.getArgs().size();
6777                TParameterDeclarationList parameters = declaration.getParameterDeclarations();
6778                if (parameters == null) return count == 0;
6779                if (count > parameters.size()) return false;
6780                int matched = 0;
6781                for (int i = 0; i < parameters.size(); i++) {
6782                        TParameterDeclaration parameter = parameters.getParameterDeclarationItem(i);
6783                        if (oracleArgumentIndex(call, parameter.getParameterName(), i) >= 0) matched++;
6784                        else if (parameter.getDefaultValue() == null) return false;
6785                }
6786                return matched == count;
6787        }
6788
6789        private int oracleArgumentIndex(TFunctionCall call, TObjectName formalName, int position) {
6790                if (call == null || call.getArgs() == null) return -1;
6791                for (int i = 0; i < call.getArgs().size(); i++) {
6792                        TExpression actual = call.getArgs().getExpression(i);
6793                        if (actual.getExpressionType() == EExpressionType.ref_arrow_t) {
6794                                if (formalName != null && actual.getLeftOperand() != null
6795                                                && DlineageUtil.sameName(ESQLDataObjectType.dotColumn,
6796                                                formalName.toString(), actual.getLeftOperand().toString())) return i;
6797                        } else if (i == position) return i;
6798                }
6799                return -1;
6800        }
6801
6802        private RelationshipElement<?> oracleCallCaller(TBasicStmt site) {
6803                if (option.getVendor() == EDbVendor.dbvoracle) {
6804                        TStoredProcedureSqlStatement parent = oracleRoutineParent(site);
6805                        Procedure model = modelManager.getRoutineModel(parent);
6806                        if (model != null) return new ProcedureRelationshipElement(model);
6807                        if (parent == null) {
6808                                // An anonymous block is a real process, not a fabricated stored procedure.
6809                                return new ProcessRelationshipElement(modelFactory.createProcess(DlineageUtil.getTopStmt(site)));
6810                        }
6811                }
6812                Procedure caller = getEnclosingProcedure(site);
6813                return caller == null ? null : new ProcedureRelationshipElement(caller);
6814        }
6815
6816        private TStoredProcedureSqlStatement oracleRoutineParent(TCustomSqlStatement site) {
6817                TStoredProcedureSqlStatement parent = getProcedureParent(site);
6818                while (parent != null && getProcedureName(parent) == null) {
6819                        TCustomSqlStatement outer = parent.getParentStmt();
6820                        parent = outer == null ? null : getProcedureParent(outer);
6821                }
6822                return parent;
6823        }
6824
6825        private void analyzeOracleBasicStmt(TBasicStmt stmt) {
6826                if (stmt.getExpr() == null) return;
6827                TFunctionCall call = stmt.getExpr().getFunctionCall();
6828                TObjectName name = call == null ? stmt.getExpr().getObjectOperand() : call.getFunctionName();
6829                if (call == null && (option.getVendor() != EDbVendor.dbvoracle
6830                                || stmt.getExpr().getExpressionType() != EExpressionType.simple_object_name_t
6831                                || name == null)) return;
6832                if (call != null) recordDbmsSqlSiteIfParseCall(stmt, call);
6833                TParseTreeNode callObject = call == null ? name : call;
6834                Procedure callee;
6835                if (option.getVendor() == EDbVendor.dbvoracle) {
6836                        callee = resolveOracleRoutineCall(stmt, name, call);
6837                        if (gudusoft.gsqlparser.dlineage.routine.RoutineShadow.enabled()) shadowCallee("resolveOracleRoutineCall", name, callee, new Integer[] {gudusoft.gsqlparser.dlineage.routine.RoutineShadow.countAliasOf(call)}, gudusoft.gsqlparser.catalog.runtime.CatalogObjectKind.ROUTINE);
6838                } else {
6839                        RoutineAliasKey callKey = callAliasKey(call);
6840                        callee = modelManager.getProcedure(callKey);
6841                        if (callee == null && analyzeProcedureDefinition(callKey)) {
6842                                callee = modelManager.getProcedure(callKey);
6843                        }
6844                        if (gudusoft.gsqlparser.dlineage.routine.RoutineShadow.enabled()) shadowCallee("analyzeOracleBasicStmt", call.getFunctionName(), callee, new Integer[] {gudusoft.gsqlparser.dlineage.routine.RoutineShadow.countAliasOf(call)}, gudusoft.gsqlparser.catalog.runtime.CatalogObjectKind.ROUTINE);
6845                }
6846                RelationshipElement<?> source;
6847                if (callee != null) {
6848                        source = new ProcedureRelationshipElement(callee);
6849                } else {
6850                        Object function = call == null ? modelFactory.createFunction(name) : createFunction(call);
6851                        if (!(function instanceof Function)) return;
6852                        source = new FunctionRelationshipElement((Function) function);
6853                }
6854                RelationshipElement<?> caller = oracleCallCaller(stmt);
6855                if (caller != null) {
6856                        CallRelationship relation = modelFactory.createCallRelation();
6857                        relation.setCallObject(callObject);
6858                        relation.setTarget(caller);
6859                        relation.addSource(source);
6860                        if (functionChecker.isOraclePredefinedPackageFunction(name.toString())
6861                                        || isBuiltInFunctionName(name) || isKeyword(name)) relation.setBuiltIn(true);
6862                }
6863                if (callee != null && call != null) {
6864                        OraclePackage owner = callee.getParentPackage();
6865                        if (option.getVendor() == EDbVendor.dbvoracle && owner != null
6866                                        && !accessedStatements.contains(callee.getProcedureObject())) {
6867                                List<Pair<TBasicStmt, Procedure>> pending = pendingOracleCallArguments.get(owner);
6868                                if (pending == null) {
6869                                        pending = new ArrayList<Pair<TBasicStmt, Procedure>>();
6870                                        pendingOracleCallArguments.put(owner, pending);
6871                                }
6872                                pending.add(new Pair<TBasicStmt, Procedure>(stmt, callee));
6873                        } else {
6874                                analyzeOracleCallArguments(callee, call);
6875                        }
6876                }
6877        }
6878
6879        private void analyzeOracleCallArguments(Procedure callee, TFunctionCall call) {
6880                if (callee.getArguments() == null) return;
6881                for (int i = 0; i < callee.getArguments().size(); i++) {
6882                        Argument argument = callee.getArguments().get(i);
6883                        int actualIndex = option.getVendor() == EDbVendor.dbvoracle
6884                                        ? oracleArgumentIndex(call, argument.getParameterObject().getParameterName(), i) : i;
6885                        if (actualIndex < 0) continue;
6886                        Variable variable = resolveFormalVariable(callee, argument);
6887                        if (variable == null) continue;
6888                        if (argument.getMode() == EParameterMode.out || argument.getMode() == EParameterMode.output) {
6889                                Transform transform = new Transform();
6890                                transform.setType(Transform.FUNCTION);
6891                                transform.setCode(call);
6892                                compositeBindingColumn(variable).setTransform(transform);
6893                        }
6894                        Process process = modelFactory.createProcess(call);
6895                        variable.addProcess(process);
6896                        analyzeFunctionArgumentsDataFlowRelation(compositeBindingColumn(variable), call, actualIndex, process);
6897                }
6898        }
6899
6900        private boolean isOracleBasicCall(TFunctionCall call) {
6901                return option.getVendor() == EDbVendor.dbvoracle && !stmtStack.isEmpty()
6902                                && stmtStack.peek() instanceof TBasicStmt
6903                                && ((TBasicStmt) stmtStack.peek()).getExpr().getFunctionCall() == call;
6904        }
6905
6906        private void analyzeMssqlIfElseStmt(TMssqlIfElse ifStmt) {
6907                if (ifStmt.getCondition() != null) {
6908                        columnsInExpr visitor = new columnsInExpr();
6909                        ifStmt.getCondition().inOrderTraverse(visitor);
6910                        List<TParseTreeNode> functions = visitor.getFunctions();
6911
6912                        if (functions != null && !functions.isEmpty()) {
6913                                for (int i = 0; i < functions.size(); i++) {
6914                                        if (!(functions.get(i) instanceof TFunctionCall))
6915                                                continue;
6916                                        TFunctionCall functionCall = (TFunctionCall) functions.get(i);
6917                                        Procedure callee = modelManager.getProcedure(callAliasKey(functionCall));
6918                                        if(callee == null && analyzeProcedureDefinition(callAliasKey(functionCall))) {
6919                                                callee = modelManager.getProcedure(callAliasKey(functionCall));
6920                                        }
6921                                        if (gudusoft.gsqlparser.dlineage.routine.RoutineShadow.enabled()) shadowCallee("analyzeMssqlIfElseStmt", functionCall.getFunctionName(), callee, new Integer[] {gudusoft.gsqlparser.dlineage.routine.RoutineShadow.countAliasOf(functionCall)}, gudusoft.gsqlparser.catalog.runtime.CatalogObjectKind.FUNCTION);
6922                                        if (callee != null) {
6923                                                String procedureParent = getProcedureParentName(ifStmt);
6924                                                if (procedureParent != null) {
6925                                                        Procedure caller = getEnclosingProcedure(ifStmt);
6926                                                        if (caller != null) {
6927                                                                CallRelationship callRelation = modelFactory.createCallRelation();
6928                                                                callRelation.setCallObject(functionCall);
6929                                                                callRelation.setTarget(new ProcedureRelationshipElement(caller));
6930                                                                callRelation.addSource(new ProcedureRelationshipElement(callee));
6931                                                                if (isBuiltInFunctionName(functionCall.getFunctionName()) || isKeyword(functionCall.getFunctionName())) {
6932                                                                        callRelation.setBuiltIn(true);
6933                                                                }
6934                                                        }
6935                                                }
6936                                                if (callee.getArguments() != null) {
6937                                                        for (int j = 0; j < callee.getArguments().size(); j++) {
6938                                                                Argument argument = callee.getArguments().get(j);
6939                                                                Variable variable = resolveFormalVariable(callee, argument);
6940                                                                if(variable!=null) {
6941                                                                        if (argument.getMode() == EParameterMode.out || argument.getMode() == EParameterMode.output) {
6942                                                                                Transform transform = new Transform();
6943                                                                                transform.setType(Transform.FUNCTION);
6944                                                                                transform.setCode(functionCall);
6945                                                                                compositeBindingColumn(variable).setTransform(transform);
6946                                                                        }
6947                                                                        Process process = modelFactory.createProcess(functionCall);
6948                                                                        variable.addProcess(process);
6949                                                                        analyzeFunctionArgumentsDataFlowRelation(compositeBindingColumn(variable), functionCall, j, process);
6950                                                                }
6951                                                        }
6952                                                }
6953                                        } else {
6954                                                Object functionModel = createFunction(functionCall);
6955                                                if (!(functionModel instanceof Function)) {
6956                                                        continue;
6957                                                }
6958                                                Function function = (Function) functionModel;
6959                                                String procedureParent = getProcedureParentName(ifStmt);
6960                                                if (procedureParent != null) {
6961                                                        Procedure caller = getEnclosingProcedure(ifStmt);
6962                                                        if (caller != null) {
6963                                                                CallRelationship callRelation = modelFactory.createCallRelation();
6964                                                                callRelation.setCallObject(functionCall);
6965                                                                callRelation.setTarget(new ProcedureRelationshipElement(caller));
6966                                                                callRelation.addSource(new FunctionRelationshipElement(function));
6967                                                                if (isBuiltInFunctionName(functionCall.getFunctionName()) || isKeyword(functionCall.getFunctionName())) {
6968                                                                        callRelation.setBuiltIn(true);
6969                                                                }
6970                                                        }
6971                                                }
6972                                        }
6973                                }
6974                        }
6975                }
6976
6977                if(ifStmt.getStmt()!=null) {
6978                        analyzeCustomSqlStmt(ifStmt.getStmt());
6979                }
6980        
6981                if (ifStmt.getElseStmt() != null) {
6982                        analyzeCustomSqlStmt(ifStmt.getElseStmt());
6983                }
6984        }
6985        
6986        private void analyzeIfStmt(TIfStmt ifStmt) {
6987                if (ifStmt.getCondition() != null) {
6988                        columnsInExpr visitor = new columnsInExpr();
6989                        ifStmt.getCondition().inOrderTraverse(visitor);
6990                        List<TParseTreeNode> functions = visitor.getFunctions();
6991
6992                        if (functions != null && !functions.isEmpty()) {
6993                                for (int i = 0; i < functions.size(); i++) {
6994                                        if (!(functions.get(i) instanceof TFunctionCall))
6995                                                continue;
6996                                        TFunctionCall functionCall = (TFunctionCall) functions.get(i);
6997                                        Procedure callee = modelManager.getProcedure(callAliasKey(functionCall));
6998                                        if(callee == null && analyzeProcedureDefinition(callAliasKey(functionCall))) {
6999                                                callee = modelManager.getProcedure(callAliasKey(functionCall));
7000                                        }
7001                                        if (gudusoft.gsqlparser.dlineage.routine.RoutineShadow.enabled()) shadowCallee("analyzeIfStmt", functionCall.getFunctionName(), callee, new Integer[] {gudusoft.gsqlparser.dlineage.routine.RoutineShadow.countAliasOf(functionCall)}, gudusoft.gsqlparser.catalog.runtime.CatalogObjectKind.FUNCTION);
7002                                        if (callee != null) {
7003                                                String procedureParent = getProcedureParentName(ifStmt);
7004                                                if (procedureParent != null) {
7005                                                        Procedure caller = getEnclosingProcedure(ifStmt);
7006                                                        if (caller != null) {
7007                                                                CallRelationship callRelation = modelFactory.createCallRelation();
7008                                                                callRelation.setCallObject(functionCall);
7009                                                                callRelation.setTarget(new ProcedureRelationshipElement(caller));
7010                                                                callRelation.addSource(new ProcedureRelationshipElement(callee));
7011                                                                if (isBuiltInFunctionName(functionCall.getFunctionName()) || isKeyword(functionCall.getFunctionName())) {
7012                                                                        callRelation.setBuiltIn(true);
7013                                                                }
7014                                                        }
7015                                                }
7016                                                if (callee.getArguments() != null) {
7017                                                        for (int j = 0; j < callee.getArguments().size(); j++) {
7018                                                                Argument argument = callee.getArguments().get(j);
7019                                                                Variable variable = resolveFormalVariable(callee, argument);
7020                                                                if(variable!=null) {
7021                                                                        if (argument.getMode() == EParameterMode.out || argument.getMode() == EParameterMode.output) {
7022                                                                                Transform transform = new Transform();
7023                                                                                transform.setType(Transform.FUNCTION);
7024                                                                                transform.setCode(functionCall);
7025                                                                                compositeBindingColumn(variable).setTransform(transform);
7026                                                                        }
7027                                                                        Process process = modelFactory.createProcess(functionCall);
7028                                                                        variable.addProcess(process);
7029                                                                        analyzeFunctionArgumentsDataFlowRelation(compositeBindingColumn(variable), functionCall, j, process);
7030                                                                }
7031                                                        }
7032                                                }
7033                                        } else {
7034                                                Object functionModel = createFunction(functionCall);
7035                                                if (!(functionModel instanceof Function)) {
7036                                                        continue;
7037                                                }
7038                                                Function function = (Function) functionModel;
7039                                                String procedureParent = getProcedureParentName(ifStmt);
7040                                                if (procedureParent != null) {
7041                                                        Procedure caller = getEnclosingProcedure(ifStmt);
7042                                                        if (caller != null) {
7043                                                                CallRelationship callRelation = modelFactory.createCallRelation();
7044                                                                callRelation.setCallObject(functionCall);
7045                                                                callRelation.setTarget(new ProcedureRelationshipElement(caller));
7046                                                                callRelation.addSource(new FunctionRelationshipElement(function));
7047                                                                if (isBuiltInFunctionName(functionCall.getFunctionName()) || isKeyword(functionCall.getFunctionName())) {
7048                                                                        callRelation.setBuiltIn(true);
7049                                                                }
7050                                                        }
7051                                                }
7052                                        }
7053                                }
7054                        }
7055                }
7056
7057                if (ifStmt.getThenStatements() != null) {
7058                        for (int i = 0; i < ifStmt.getThenStatements().size(); ++i) {
7059                                analyzeCustomSqlStmt(ifStmt.getThenStatements().get(i));
7060                                ResultSet returnResult = modelFactory.createResultSet(ifStmt.getThenStatements().get(i), false);
7061                                if(returnResult!=null){
7062                                        for(ResultColumn resultColumn: returnResult.getColumns()){
7063                                                analyzeFilterCondition(resultColumn, ifStmt.getCondition(), null, null, EffectType.function);
7064                                        }
7065                                }
7066                        }
7067                }
7068
7069                if (ifStmt.getElseifStatements() != null) {
7070                        for (int i = 0; i < ifStmt.getElseifStatements().size(); ++i) {
7071                                analyzeCustomSqlStmt(ifStmt.getElseifStatements().get(i));
7072                        }
7073                }
7074
7075                if (ifStmt.getElseStatements() != null) {
7076                        for (int i = 0; i < ifStmt.getElseStatements().size(); ++i) {
7077                                analyzeCustomSqlStmt(ifStmt.getElseStatements().get(i));
7078                        }
7079                }
7080        }
7081
7082        private void analyzeElsIfStmt(TElsifStmt elsIfStmt) {
7083                if (elsIfStmt.getCondition() != null) {
7084                        columnsInExpr visitor = new columnsInExpr();
7085                        elsIfStmt.getCondition().inOrderTraverse(visitor);
7086                        List<TParseTreeNode> functions = visitor.getFunctions();
7087
7088                        if (functions != null && !functions.isEmpty()) {
7089                                for (int i = 0; i < functions.size(); i++) {
7090                                        if (!(functions.get(i) instanceof TFunctionCall))
7091                                                continue;
7092                                        TFunctionCall functionCall = (TFunctionCall) functions.get(i);
7093                                        Procedure callee = modelManager.getProcedure(callAliasKey(functionCall));
7094                                        if(callee == null && analyzeProcedureDefinition(callAliasKey(functionCall))) {
7095                                                callee = modelManager.getProcedure(callAliasKey(functionCall));
7096                                        }
7097                                        if (gudusoft.gsqlparser.dlineage.routine.RoutineShadow.enabled()) shadowCallee("analyzeElsIfStmt", functionCall.getFunctionName(), callee, new Integer[] {gudusoft.gsqlparser.dlineage.routine.RoutineShadow.countAliasOf(functionCall)}, gudusoft.gsqlparser.catalog.runtime.CatalogObjectKind.FUNCTION);
7098                                        if (callee != null) {
7099                                                String procedureParent = getProcedureParentName(elsIfStmt);
7100                                                if (procedureParent != null) {
7101                                                        Procedure caller = getEnclosingProcedure(elsIfStmt);
7102                                                        if (caller != null) {
7103                                                                CallRelationship callRelation = modelFactory.createCallRelation();
7104                                                                callRelation.setCallObject(functionCall);
7105                                                                callRelation.setTarget(new ProcedureRelationshipElement(caller));
7106                                                                callRelation.addSource(new ProcedureRelationshipElement(callee));
7107                                                                if(isBuiltInFunctionName(functionCall.getFunctionName()) || isKeyword(functionCall.getFunctionName())){
7108                                                                        callRelation.setBuiltIn(true);
7109                                                                }
7110                                                        }
7111                                                }
7112                                                if (callee.getArguments() != null) {
7113                                                        for (int j = 0; j < callee.getArguments().size(); j++) {
7114                                                                Argument argument = callee.getArguments().get(j);
7115                                                                Variable variable = resolveFormalVariable(callee, argument);
7116                                                                if(variable!=null) {
7117                                                                        if (argument.getMode() == EParameterMode.out || argument.getMode() == EParameterMode.output) {
7118                                                                                Transform transform = new Transform();
7119                                                                                transform.setType(Transform.FUNCTION);
7120                                                                                transform.setCode(functionCall);
7121                                                                                compositeBindingColumn(variable).setTransform(transform);
7122                                                                        }
7123                                                                        Process process = modelFactory.createProcess(functionCall);
7124                                                                        variable.addProcess(process);
7125                                                                        analyzeFunctionArgumentsDataFlowRelation(compositeBindingColumn(variable), functionCall, j, process);
7126                                                                }
7127                                                        }
7128                                                }
7129                                        } else {
7130                                                Function function = (Function)createFunction(functionCall);
7131                                                String procedureParent = getProcedureParentName(elsIfStmt);
7132                                                if (procedureParent != null) {
7133                                                        Procedure caller = getEnclosingProcedure(elsIfStmt);
7134                                                        if (caller != null) {
7135                                                                CallRelationship callRelation = modelFactory.createCallRelation();
7136                                                                callRelation.setCallObject(functionCall);
7137                                                                callRelation.setTarget(new ProcedureRelationshipElement(caller));
7138                                                                callRelation.addSource(new FunctionRelationshipElement(function));
7139                                                                if(isBuiltInFunctionName(functionCall.getFunctionName()) || isKeyword(functionCall.getFunctionName())){
7140                                                                        callRelation.setBuiltIn(true);
7141                                                                }
7142                                                        }
7143                                                }
7144                                        }
7145                                }
7146                        }
7147                }
7148
7149                if (elsIfStmt.getThenStatements() != null) {
7150                        for (int i = 0; i < elsIfStmt.getThenStatements().size(); ++i) {
7151                                ResultSet returnResult = modelFactory.createResultSet(elsIfStmt.getThenStatements().get(i), false);
7152                                if (returnResult != null) {
7153                                        for (ResultColumn resultColumn : returnResult.getColumns()) {
7154                                                analyzeFilterCondition(resultColumn, elsIfStmt.getCondition(), null, null, EffectType.function);
7155                                        }
7156                                }
7157                                analyzeCustomSqlStmt(elsIfStmt.getThenStatements().get(i));
7158                        }
7159                }
7160        }
7161
7162        private void analyzeCloneTableStmt(TCreateTableSqlStatement stmt) {
7163                if (stmt.getCloneSourceTable() != null) {
7164                        Table sourceTable = modelFactory.createTableByName(stmt.getCloneSourceTable());
7165                        Table cloneTable = modelFactory.createTableByName(stmt.getTableName());
7166                        // Clone creates the target. Parse fact, set regardless of source
7167                        // resolution (the gated setFromDDL below stays as-is). See
7168                        // dlineage-authoritative-endpoint-classification.md.
7169                        cloneTable.setCreatedInSql(true);
7170                        cloneTable.setEndpointIntroduction(EndpointIntroduction.CLONE_TABLE);
7171                        Process process = modelFactory.createProcess(stmt);
7172                        cloneTable.addProcess(process);
7173
7174                        if (sourceTable.isDetermined()) {
7175                                for (int k = 0; k < sourceTable.getColumns().size(); k++) {
7176                                        TableColumn sourceColumn = sourceTable.getColumns().get(k);
7177                                        TObjectName objectName = new TObjectName();
7178                                        objectName.setString(sourceColumn.getName());
7179                                        TableColumn tableColumn = modelFactory.createTableColumn(cloneTable, objectName, true);
7180                                        DataFlowRelationship dataflowRelation = modelFactory.createDataFlowRelation();
7181                                        dataflowRelation.setEffectType(EffectType.clone_table);
7182                                        dataflowRelation
7183                                                        .addSource(new TableColumnRelationshipElement(sourceColumn));
7184                                        dataflowRelation.setTarget(new TableColumnRelationshipElement(tableColumn));
7185                                        dataflowRelation.setProcess(process);
7186                                }
7187                                cloneTable.setDetermined(true);
7188                                cloneTable.setFromDDL(true);
7189                        } else {
7190                                TObjectName sourceName = DlineageUtil.syntheticStarName();
7191                                TableColumn sourceTableColumn = modelFactory.createTableColumn(sourceTable, sourceName, false);
7192                                TObjectName targetName = DlineageUtil.syntheticStarName();
7193                                TableColumn targetTableColumn = modelFactory.createTableColumn(cloneTable, targetName, false);
7194                                if(sourceTableColumn!=null && targetTableColumn!=null) {
7195                                        DataFlowRelationship dataflowRelation = modelFactory.createDataFlowRelation();
7196                                        dataflowRelation.setEffectType(EffectType.clone_table);
7197                                        dataflowRelation
7198                                                        .addSource(new TableColumnRelationshipElement(sourceTableColumn));
7199                                        dataflowRelation.setTarget(new TableColumnRelationshipElement(targetTableColumn));
7200                                        dataflowRelation.setProcess(process);
7201                                }
7202                        }
7203                }
7204        }
7205
7206        private void analyzeCloneDatabaseStmt(TCreateDatabaseSqlStatement stmt) {
7207                if (stmt.getCloneSourceDb() != null) {
7208                        Database sourceDatabase = modelFactory.createDatabase(stmt.getCloneSourceDb());
7209                        Database cloneDatabase = modelFactory.createDatabase(stmt.getDatabaseName());
7210                        Process process = modelFactory.createProcess(stmt);
7211                        cloneDatabase.addProcess(process);
7212                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
7213                        relation.setEffectType(EffectType.clone_database);
7214                        relation.setTarget(new RelationRowsRelationshipElement<TableRelationRows>(cloneDatabase.getRelationRows()));
7215                        relation.addSource(
7216                                        new RelationRowsRelationshipElement<TableRelationRows>(sourceDatabase.getRelationRows()));
7217                        relation.setProcess(process);
7218                }
7219        }
7220
7221        private void analyzeCloneSchemaStmt(TCreateSchemaSqlStatement stmt) {
7222                if (stmt.getCloneSourceSchema() != null) {
7223                        Schema sourceSchema = modelFactory.createSchema(stmt.getCloneSourceSchema());
7224                        Schema cloneSchema = modelFactory.createSchema(stmt.getSchemaName());
7225                        Process process = modelFactory.createProcess(stmt);
7226                        cloneSchema.addProcess(process);
7227                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
7228                        relation.setEffectType(EffectType.clone_schema);
7229                        relation.setTarget(new RelationRowsRelationshipElement<TableRelationRows>(cloneSchema.getRelationRows()));
7230                        relation.addSource(new RelationRowsRelationshipElement<TableRelationRows>(sourceSchema.getRelationRows()));
7231                        relation.setProcess(process);
7232                }
7233        }
7234
7235        /**
7236         * Record one dynamic-SQL site. Span is the statement extent (1-based, end-exclusive), tagged with
7237         * the per-statement file hash, mirroring the {@link ErrorInfo} position convention. When the
7238         * {@code reportDynamicSqlSitesAsErrors} option is on, non-RESOLVED sites are also mirrored into an
7239         * {@link ErrorInfo} so legacy {@code getErrorMessages()} consumers can see them.
7240         */
7241        private void recordDynamicSqlSite(TCustomSqlStatement stmt, DynamicSqlSite.Kind kind,
7242                        DynamicSqlSite.Status status, String reason) {
7243                recordDynamicSqlSite(stmt, kind, status, reason, 0, 0);
7244        }
7245
7246        private void recordDynamicSqlSite(TCustomSqlStatement stmt, DynamicSqlSite.Kind kind,
7247                        DynamicSqlSite.Status status, String reason, int resolvedCount, int partialCount) {
7248                DynamicSqlTrustMode trustMode = option == null ? DynamicSqlTrustMode.LEGACY
7249                                : option.getDynamicSqlTrustMode();
7250                // This compatibility overload has no fragment provenance. Never turn
7251                // that absence into an exact zero; callers with evidence use the full
7252                // overload below.
7253                recordDynamicSqlSite(stmt, kind, status, reason, resolvedCount, partialCount,
7254                                0, -1, false, trustMode);
7255        }
7256
7257        private void recordDynamicSqlSite(TCustomSqlStatement stmt, DynamicSqlSite.Kind kind,
7258                        DynamicSqlSite.Status status, String reason, int resolvedCount, int partialCount,
7259                        int unprovenCount, int unresolvedHoleCount,
7260                        boolean holeCountExact, DynamicSqlTrustMode trustMode) {
7261                recordDynamicSqlSite(stmt, kind, status, reason, resolvedCount, partialCount,
7262                                unprovenCount, unresolvedHoleCount, holeCountExact, trustMode,
7263                                -1, -1, null);
7264        }
7265
7266        private void recordDynamicSqlSite(TCustomSqlStatement stmt, DynamicSqlSite.Kind kind,
7267                        DynamicSqlSite.Status status, String reason, int resolvedCount, int partialCount,
7268                        int unprovenCount, int unresolvedHoleCount,
7269                        boolean holeCountExact, DynamicSqlTrustMode trustMode,
7270                        int relationshipsBefore, int nestedSitesBefore) {
7271                recordDynamicSqlSite(stmt, kind, status, reason, resolvedCount, partialCount,
7272                                unprovenCount, unresolvedHoleCount, holeCountExact, trustMode,
7273                                relationshipsBefore, nestedSitesBefore, null);
7274        }
7275
7276        private void recordDynamicSqlSite(TCustomSqlStatement stmt, DynamicSqlSite.Kind kind,
7277                        DynamicSqlSite.Status status, String reason, int resolvedCount, int partialCount,
7278                        int unprovenCount, int unresolvedHoleCount,
7279                        boolean holeCountExact, DynamicSqlTrustMode trustMode,
7280                        int relationshipsBefore, int nestedSitesBefore,
7281                        List<Long> explicitlyProvenRelationshipIds) {
7282                recordDynamicSqlSite(stmt, kind, status, reason, resolvedCount, partialCount,
7283                                unprovenCount, unresolvedHoleCount, holeCountExact, trustMode,
7284                                relationshipsBefore, nestedSitesBefore,
7285                                explicitlyProvenRelationshipIds, null);
7286        }
7287
7288        private void recordDynamicSqlSite(TCustomSqlStatement stmt, DynamicSqlSite.Kind kind,
7289                        DynamicSqlSite.Status status, String reason, int resolvedCount, int partialCount,
7290                        int unprovenCount, int unresolvedHoleCount,
7291                        boolean holeCountExact, DynamicSqlTrustMode trustMode,
7292                        int relationshipsBefore, int nestedSitesBefore,
7293                        List<Long> explicitlyProvenRelationshipIds, String diagnostic) {
7294                recordDynamicSqlSite(stmt, kind, status, reason, resolvedCount, partialCount,
7295                                unprovenCount, unresolvedHoleCount, holeCountExact, trustMode,
7296                                relationshipsBefore, nestedSitesBefore,
7297                                explicitlyProvenRelationshipIds, diagnostic,
7298                                Collections.<UnresolvedFragment>emptyList(), null);
7299        }
7300
7301        /**
7302         * An opaque site the evaluator could not turn into analyzable text: UNRESOLVED with
7303         * the argument-shape reason, carrying whatever hole evidence the evaluator gathered
7304         * on the way (the variable that stopped the fold, its assignment, its span).
7305         */
7306        private void recordUnresolvedDynamicSqlSite(TCustomSqlStatement stmt, TMssqlExecute executeStmt,
7307                        DynamicSqlSite.Kind kind) {
7308                gudusoft.gsqlparser.dlineage.dynamicsql.DynamicSqlLineageResolver.MaterializedSite evaluated =
7309                                evaluatedDynamicSqlFor(stmt);
7310                DynamicHoleEvidence holeEvidence = dynamicHoleEvidence(evaluated);
7311                String provenNull = provenNullReasonOf(stmt);
7312                // every binding that reached the site, not only the one whose variant stands for it
7313                gudusoft.gsqlparser.dlineage.dynamicsql.BindingSource bindings = gudusoft.gsqlparser.dlineage.dynamicsql.BindingSource.NONE;
7314                boolean certain = evaluated == null || evaluated.executionCertain;
7315                for (gudusoft.gsqlparser.dlineage.dynamicsql.DynamicSqlLineageResolver.MaterializedSite candidate : selectVariants(stmt)) {
7316                        bindings = bindings.getKind() == gudusoft.gsqlparser.dlineage.dynamicsql.BindingSource.Kind.NONE
7317                                        ? candidate.bindingSource : bindings.alsoAt(candidate.bindingSource);
7318                        certain &= candidate.executionCertain;
7319                }
7320                recordDynamicSqlSite(stmt, kind, DynamicSqlSite.Status.UNRESOLVED,
7321                                provenNull != null ? "the dynamic SQL is NULL and runs nothing: " + provenNull
7322                                                : evaluated != null && evaluated.sqlText == null
7323                                                                && evaluated.bindingSource.getKind() != gudusoft.gsqlparser.dlineage.dynamicsql.BindingSource.Kind.NONE
7324                                                                ? "the evaluation with the bound parameters gives no SQL text"
7325                                                                                + (evaluated.reason == null ? "" : ": " + evaluated.reason)
7326                                                                : dynamicReasonOf(executeStmt),
7327                                0, 0, 0, holeEvidence.count, holeEvidence.exact, option.getDynamicSqlTrustMode(),
7328                                -1, -1, null, evaluated == null ? null
7329                                                : gudusoft.gsqlparser.dlineage.dynamicsql.DynamicSqlLineageResolver.joinNotes(
7330                                                                evaluated.executionNote, bindings.getDescription()),
7331                                holeEvidence.fragments,
7332                                holeEvidence.materializedText, certain);
7333        }
7334
7335        private void recordDynamicSqlSite(TCustomSqlStatement stmt, DynamicSqlSite.Kind kind,
7336                        DynamicSqlSite.Status status, String reason, int resolvedCount, int partialCount,
7337                        int unprovenCount, int unresolvedHoleCount,
7338                        boolean holeCountExact, DynamicSqlTrustMode trustMode,
7339                        int relationshipsBefore, int nestedSitesBefore,
7340                        List<Long> explicitlyProvenRelationshipIds, String diagnostic,
7341                        List<UnresolvedFragment> unresolvedFragments, String materializedText) {
7342                recordDynamicSqlSite(stmt, kind, status, reason, resolvedCount, partialCount, unprovenCount,
7343                                unresolvedHoleCount, holeCountExact, trustMode, relationshipsBefore, nestedSitesBefore,
7344                                explicitlyProvenRelationshipIds, diagnostic, unresolvedFragments, materializedText, true);
7345        }
7346
7347        private void recordDynamicSqlSite(TCustomSqlStatement stmt, DynamicSqlSite.Kind kind,
7348                        DynamicSqlSite.Status status, String reason, int resolvedCount, int partialCount,
7349                        int unprovenCount, int unresolvedHoleCount,
7350                        boolean holeCountExact, DynamicSqlTrustMode trustMode,
7351                        int relationshipsBefore, int nestedSitesBefore,
7352                        List<Long> explicitlyProvenRelationshipIds, String diagnostic,
7353                        List<UnresolvedFragment> unresolvedFragments, String materializedText,
7354                        boolean executionCertain) {
7355                if (stmt == null || stmt.getStartToken() == null || stmt.getEndToken() == null) {
7356                        return;
7357                }
7358                diagnostic = withDynamicEvaluationNotes(diagnostic);
7359                List<Long> observedRelationshipIds = dynamicRelationshipIdsSince(
7360                                relationshipsBefore, nestedSitesBefore);
7361                List<Long> provenRelationshipIds = Collections.emptyList();
7362                if (explicitlyProvenRelationshipIds != null) {
7363                        LinkedHashSet<Long> observed = new LinkedHashSet<Long>(observedRelationshipIds);
7364                        provenRelationshipIds = new ArrayList<Long>();
7365                        for (Long id : explicitlyProvenRelationshipIds) {
7366                                if (id != null && observed.contains(id)
7367                                                && !provenRelationshipIds.contains(id)) {
7368                                        provenRelationshipIds.add(id);
7369                                }
7370                        }
7371                } else if (status == DynamicSqlSite.Status.RESOLVED
7372                                && unprovenCount == 0
7373                                && holeCountExact && unresolvedHoleCount == 0) {
7374                        provenRelationshipIds = provenDynamicRelationshipIdsSince(
7375                                        relationshipsBefore, nestedSitesBefore);
7376                }
7377                Pair3<Long, Long, String> start = new Pair3<Long, Long, String>(stmt.getStartToken().lineNo,
7378                                stmt.getStartToken().columnNo, ModelBindingManager.getGlobalHash());
7379                Pair3<Long, Long, String> end;
7380                String[] segments = stmt.getEndToken().getAstext().split("\n", -1);
7381                if (segments.length == 1) {
7382                        end = new Pair3<Long, Long, String>(stmt.getEndToken().lineNo,
7383                                        stmt.getEndToken().columnNo + stmt.getEndToken().getAstext().length(),
7384                                        ModelBindingManager.getGlobalHash());
7385                } else {
7386                        end = new Pair3<Long, Long, String>(stmt.getEndToken().lineNo + segments.length - 1,
7387                                        (long) segments[segments.length - 1].length() + 1, ModelBindingManager.getGlobalHash());
7388                }
7389                dynamicSqlSites.add(new DynamicSqlSite(kind, status, reason, start, end,
7390                                resolvedCount, partialCount, unprovenCount,
7391                                unresolvedHoleCount, holeCountExact, trustMode,
7392                                observedRelationshipIds, provenRelationshipIds, diagnostic,
7393                                unresolvedFragments, materializedText, executionCertain));
7394                dynamicSiteIndexes.put(stmt, dynamicSqlSites.size() - 1);
7395
7396                if (status != DynamicSqlSite.Status.RESOLVED && option != null
7397                                && option.isReportDynamicSqlSitesAsErrors()) {
7398                        ErrorInfo errorInfo = new ErrorInfo();
7399                        errorInfo.setErrorType(ErrorInfo.DYNAMIC_SQL_UNRESOLVED);
7400                        errorInfo.setErrorMessage(reason != null ? reason : "dynamic SQL not resolved");
7401                        errorInfo.setStartPosition(start);
7402                        errorInfo.setEndPosition(end);
7403                        errorInfo.fillInfo(this);
7404                        errorInfos.add(errorInfo);
7405                }
7406        }
7407
7408        /**
7409         * Relationship IDs created by one dynamic site, excluding relationship
7410         * deltas already attributed to nested dynamic sites. The IDs match the
7411         * non-simple XML model emitted by this analyzer instance.
7412         */
7413        private List<Long> dynamicRelationshipIdsSince(int beforeCount, int nestedSitesBefore) {
7414                if (beforeCount < 0) {
7415                        return Collections.emptyList();
7416                }
7417                Set<Long> nested = new HashSet<Long>();
7418                if (nestedSitesBefore >= 0) {
7419                        for (int i = Math.min(nestedSitesBefore, dynamicSqlSites.size());
7420                                        i < dynamicSqlSites.size(); i++) {
7421                                nested.addAll(dynamicSqlSites.get(i).getObservedRelationshipIds());
7422                        }
7423                }
7424                Relationship[] relationships = modelManager.getRelations();
7425                List<Long> ids = new ArrayList<Long>();
7426                for (int i = Math.min(beforeCount, relationships.length);
7427                                i < relationships.length; i++) {
7428                        Relationship relationship = relationships[i];
7429                        if (relationship instanceof AbstractRelationship) {
7430                                Long id = Long.valueOf(((AbstractRelationship) relationship).getId());
7431                                if (!nested.contains(id)) {
7432                                        ids.add(id);
7433                                }
7434                        }
7435                }
7436                return ids;
7437        }
7438
7439        /**
7440         * Per-edge proof filter for a complete materialization. Placeholder-tainted
7441         * relations and every downstream relation that consumes their target stay
7442         * observation-only; independent relations from the same site remain proven.
7443         */
7444        private List<Long> provenDynamicRelationshipIdsSince(
7445                        int beforeCount, int nestedSitesBefore) {
7446                List<Long> observedIds = dynamicRelationshipIdsSince(beforeCount, nestedSitesBefore);
7447                if (observedIds.isEmpty()) {
7448                        return observedIds;
7449                }
7450                Set<Long> observed = new HashSet<Long>(observedIds);
7451                Set<Long> nestedUnproven = new HashSet<Long>();
7452                if (nestedSitesBefore >= 0) {
7453                        for (int i = Math.min(nestedSitesBefore, dynamicSqlSites.size());
7454                                        i < dynamicSqlSites.size(); i++) {
7455                                DynamicSqlSite nestedSite = dynamicSqlSites.get(i);
7456                                nestedUnproven.addAll(nestedSite.getObservedRelationshipIds());
7457                                nestedUnproven.removeAll(nestedSite.getProvenRelationshipIds());
7458                        }
7459                }
7460
7461                Set<Object> taintedElements = Collections.newSetFromMap(
7462                                new IdentityHashMap<Object, Boolean>());
7463                Set<Relationship> taintedRelationships = Collections.newSetFromMap(
7464                                new IdentityHashMap<Relationship, Boolean>());
7465                Relationship[] relationships = modelManager.getRelations();
7466                Map<Long, Relationship> relationshipsById = new HashMap<Long, Relationship>();
7467                for (Relationship relationship : relationships) {
7468                        if (!(relationship instanceof AbstractRelationship)) continue;
7469                        Long id = Long.valueOf(((AbstractRelationship) relationship).getId());
7470                        relationshipsById.put(id, relationship);
7471                        if (nestedUnproven.contains(id)) {
7472                                addDynamicTargetElement(taintedElements, relationship);
7473                        }
7474                }
7475
7476                boolean changed;
7477                do {
7478                        changed = false;
7479                        for (Relationship relationship : relationships) {
7480                                if (!(relationship instanceof AbstractRelationship)
7481                                                || !observed.contains(Long.valueOf(
7482                                                                ((AbstractRelationship) relationship).getId()))
7483                                                || taintedRelationships.contains(relationship)) {
7484                                        continue;
7485                                }
7486                                if (hasUnresolvedDynamicEndpoint(relationship)
7487                                                || hasDynamicSourceElement(taintedElements, relationship)) {
7488                                        taintedRelationships.add(relationship);
7489                                        changed |= addDynamicTargetElement(taintedElements, relationship);
7490                                }
7491                        }
7492                } while (changed);
7493
7494                List<Long> proven = new ArrayList<Long>();
7495                for (Long id : observedIds) {
7496                        Relationship relationship = relationshipsById.get(id);
7497                        if (relationship != null && !taintedRelationships.contains(relationship)) {
7498                                proven.add(id);
7499                        }
7500                }
7501                return proven;
7502        }
7503
7504        private boolean hasDynamicSourceElement(Set<Object> elements, Relationship relationship) {
7505                if (relationship.getSources() == null) return false;
7506                for (Object value : relationship.getSources()) {
7507                        if (value instanceof RelationshipElement
7508                                        && elements.contains(((RelationshipElement<?>) value).getElement())) {
7509                                return true;
7510                        }
7511                }
7512                return false;
7513        }
7514
7515        private boolean addDynamicTargetElement(Set<Object> elements, Relationship relationship) {
7516                if (relationship.getTarget() == null
7517                                || relationship.getTarget().getElement() == null) {
7518                        return false;
7519                }
7520                return elements.add(relationship.getTarget().getElement());
7521        }
7522
7523        private boolean hasUnresolvedDynamicEndpoint(Relationship relationship) {
7524                if (hasPlaceholderSourceTable(relationship, false)) {
7525                        return true;
7526                }
7527                if (relationship.getSources() != null) {
7528                        for (Object value : relationship.getSources()) {
7529                                if (value instanceof RelationRowsRelationshipElement) {
7530                                        Object rows = ((RelationRowsRelationshipElement<?>) value).getElement();
7531                                        if (rows instanceof TableRelationRows
7532                                                        && isRuntimeObjectPlaceholder(
7533                                                                        ((TableRelationRows) rows).getHolder())) {
7534                                                return true;
7535                                        }
7536                                }
7537                        }
7538                }
7539                Object target = relationship.getTarget() == null
7540                                ? null : relationship.getTarget().getElement();
7541                if (target instanceof TableColumn) {
7542                        return isRuntimeObjectPlaceholder(((TableColumn) target).getTable());
7543                }
7544                return target instanceof TableRelationRows
7545                                && isRuntimeObjectPlaceholder(((TableRelationRows) target).getHolder());
7546        }
7547
7548        private boolean isRuntimeObjectPlaceholder(Table table) {
7549                if (table == null || table.isPseudo()) return true;
7550                if (table.isVariable()) return false;
7551                String name = table.getName();
7552                return name != null && name.trim().startsWith("@");
7553        }
7554
7555        /** Identity snapshot used only by SHADOW observability; it never drives removal. */
7556        private Set<Relationship> snapshotDynamicRelationships() {
7557                Set<Relationship> snapshot = Collections.newSetFromMap(
7558                                new IdentityHashMap<Relationship, Boolean>());
7559                Collections.addAll(snapshot, modelManager.getRelations());
7560                return snapshot;
7561        }
7562
7563        /** Exact number of currently published relationships not present in {@code before}. */
7564        private int countNewDynamicRelationships(Set<Relationship> before) {
7565                if (before == null) {
7566                        return 0;
7567                }
7568                int count = 0;
7569                for (Relationship relation : modelManager.getRelations()) {
7570                        if (!before.contains(relation)) {
7571                                count++;
7572                        }
7573                }
7574                return count;
7575        }
7576
7577        /**
7578         * One unresolved hole of a dynamic fold, located by CHARACTER SPAN in the analyzed text
7579         * rather than by its rendered spelling. Locating by text would over-mark: a hole rendering
7580         * as {@code SRC} also occurs inside the unrelated literal {@code SRC_COPY} in the same batch.
7581         *
7582         * <p>A hole here is always a fragment whose VALUE is unknown. An {@code INEXACT_TRANSFORM}
7583         * fragment is deliberately NOT one: there the evaluator did compute the value from literals
7584         * ({@code REPLACE(tmpl, '{SRC}', 'dbo.SalesOrders')}) and only its byte-exactness against
7585         * vendor runtime semantics is unproven. Treating it as a hole would brand every fully
7586         * template-resolved name a runtime template — the opposite of this feature's purpose.
7587         */
7588        private static final class FoldHole {
7589                /** Character span [start, end) in the analyzed text; start == end for a splice point. */
7590                final int start;
7591                final int end;
7592                FoldHole(int start, int end) {
7593                        this.start = start;
7594                        this.end = end;
7595                }
7596
7597                /** True when this hole can affect any character of [from, to) — splice points included. */
7598                boolean touches(int from, int to) {
7599                        if (start == end) {
7600                                return from <= start && start <= to;
7601                        }
7602                        return start < to && end > from;
7603                }
7604        }
7605
7606        /** Conservative fold-hole spelling: the unresolved {@code @variable} remnant a partial fold leaves behind. */
7607        private static final Pattern DYNAMIC_FOLD_PLACEHOLDER = Pattern.compile("@\\w+");
7608
7609        /**
7610         * Depth of dynamic-fold inner analysis currently on the stack. Statements analyzed at
7611         * depth 0 are STATIC statements of the unit; their table references veto dynamic-template
7612         * marks on the same name (see {@link #applyStaticTemplateVetoes}), which is what makes the
7613         * shared-node contract independent of statement ORDER — a static reference after the fold
7614         * withdraws the mark exactly as one before the fold prevents it.
7615         */
7616        private int dynamicFoldDepth = 0;
7617
7618        /** True once any endpoint of this analysis carries a dynamic-template mark. */
7619        private boolean anyDynamicTemplateMarked = false;
7620
7621        /**
7622         * Name-registry keys of every table referenced by a STATIC statement after the first
7623         * template mark. A fold never marks (and a later collection unmarks) a name in this set.
7624         */
7625        private final Set<Object> staticTemplateVetoKeys = new HashSet<Object>();
7626
7627        /** Statements already veto-walked (identity), so repeated re-analysis stays O(1). */
7628        private final Set<TCustomSqlStatement> staticVetoWalkedStmts =
7629                        Collections.newSetFromMap(new IdentityHashMap<TCustomSqlStatement, Boolean>());
7630
7631        /**
7632         * Collects {@code stmt}'s static table references into the veto set and withdraws any
7633         * template mark already sitting on a node they share. Runs only at fold depth 0 and only
7634         * once a mark exists, so the common no-dynamic-SQL unit pays nothing.
7635         */
7636        private void applyStaticTemplateVetoes(TCustomSqlStatement stmt) {
7637                // Walk each statement object once: the veto keys accumulate, so a repeat walk of a
7638                // re-analyzed procedure body adds nothing but O(AST) cost per call.
7639                if (!staticVetoWalkedStmts.add(stmt)) {
7640                        return;
7641                }
7642                TemplateTableCollector collector = new TemplateTableCollector();
7643                try {
7644                        if (stmt instanceof TSelectSqlStatement) {
7645                                collector.preVisit((TSelectSqlStatement) stmt);
7646                        }
7647                        stmt.acceptChildren(collector);
7648                } catch (RuntimeException ex) {
7649                        // This is an enrichment-only walk over an error-recovered AST. Keep every
7650                        // static veto collected before the malformed slot, and never let a missing
7651                        // annotation discard the statement's real lineage.
7652                        logger.warn("Static template-veto walk was incomplete; keeping recovered lineage", ex);
7653                }
7654                for (TTable table : collector.tables) {
7655                        if (table.getSubquery() != null || table.getCTE() != null
7656                                        || table.getTableName() == null) {
7657                                continue;
7658                        }
7659                        vetoStaticTemplateName(table.getTableName().toString());
7660                }
7661                for (TObjectName intoTarget : collector.intoTargets) {
7662                        vetoStaticTemplateName(intoTarget.toString());
7663                }
7664        }
7665
7666        /**
7667         * Records one static reference under the SAME name-registry keys the model-reuse lookups
7668         * use ({@code ModelFactory.createTableByName}), so the veto matches exactly the reuse that
7669         * creates a shared node.
7670         */
7671        private void vetoStaticTemplateName(String rawName) {
7672                if (SQLUtil.isEmpty(rawName)) {
7673                        return;
7674                }
7675                Object[] keys = { DlineageUtil.getTableIdentityKey(rawName),
7676                                DlineageUtil.getTableIdentityKeyWithDefaultSchema(rawName) };
7677                for (Object key : keys) {
7678                        if (key == null || (key instanceof String && SQLUtil.isEmpty((String) key))) {
7679                                continue;
7680                        }
7681                        staticTemplateVetoKeys.add(key);
7682                        Table bound = modelManager.getTableByName(key);
7683                        if (bound != null && bound.isDynamicTemplate()) {
7684                                bound.clearDynamicTemplate();
7685                        }
7686                }
7687        }
7688
7689        /**
7690         * A delimited identifier whose ENTIRE content is an {@code @variable} remnant —
7691         * {@code [@S]} / {@code "@S"}, the shape a {@code '... [' + @S + '] ...'} splice folds to.
7692         * Deliberately anchored to the whole content: {@code [weird@name]} does NOT match, because
7693         * a delimited name that merely CONTAINS an {@code @} is an ordinary legal identifier.
7694         */
7695        private static final Pattern DYNAMIC_FOLD_DELIMITED_PLACEHOLDER =
7696                        Pattern.compile("\\[(@\\w+)\\]|\"(@\\w+)\"");
7697
7698        /**
7699         * Identity snapshot of the endpoints that already existed before a dynamic fold was
7700         * analyzed, so {@link #markDynamicTemplateEndpoints} can restrict itself to the nodes
7701         * THIS fold introduced. Returns {@code null} when the fold cannot carry a hole, which is
7702         * the signal to skip marking entirely (and pay nothing on the fully-resolved path).
7703         */
7704        /**
7705         * The holes of the text an EXECUTE of PostgreSQL's format() analyzes (Mantis 4880): the parser puts PLACEHOLDER
7706         * where each %s of the template stands for a runtime argument. Rebuilt from the template, so a PLACEHOLDER the
7707         * template itself spells is no hole; none when the rebuilt text is not the analyzed one.
7708         */
7709        private static List<FoldHole> formatHoles(TExecuteSqlStatement stmt, String analyzedText) {
7710                TExpression call = stmt.getStmtString();
7711                if (call == null || call.getExpressionType() != EExpressionType.function_t || call.getFunctionCall() == null
7712                                || !call.toString().startsWith("format") || call.getFunctionCall().getArgs() == null
7713                                || call.getFunctionCall().getArgs().size() == 0) {
7714                        return Collections.emptyList();
7715                }
7716                String template = SQLUtil.trimColumnStringQuote(call.getFunctionCall().getArgs().getExpression(0).toString());
7717                List<FoldHole> holes = new ArrayList<FoldHole>();
7718                StringBuilder text = new StringBuilder();
7719                int percents = 0;
7720                for (int i = 0; i < template.length(); i++) {
7721                        if (template.startsWith("%s", i)) {
7722                                // the parser replaces every %s; only one after an odd run of % is a specifier (%% is a literal %)
7723                                if ((percents + 1) % 2 == 1) {
7724                                        holes.add(new FoldHole(text.length(), text.length() + "PLACEHOLDER".length()));
7725                                }
7726                                text.append("PLACEHOLDER");
7727                                i++;
7728                                percents = 0;
7729                        } else {
7730                                percents = template.charAt(i) == '%' ? percents + 1 : 0;
7731                                text.append(template.charAt(i));
7732                        }
7733                }
7734                return text.toString().equals(analyzedText) ? holes : Collections.<FoldHole>emptyList();
7735        }
7736
7737        private Set<Table> snapshotTemplateCandidateTables(boolean foldMayCarryHoles) {
7738                if (!foldMayCarryHoles) {
7739                        return null;
7740                }
7741                Set<Table> snapshot = Collections.newSetFromMap(new IdentityHashMap<Table, Boolean>());
7742                snapshot.addAll(modelManager.getTablesByName());
7743                return snapshot;
7744        }
7745
7746        /**
7747         * Exact hole spans for {@code analyzedText}, from the evaluator's fragment provenance.
7748         * Empty unless the site describes THIS text — spans taken from a different materialization
7749         * would point at arbitrary offsets of the string actually analyzed.
7750         */
7751        /** True when the site carries fragments that provably render its own text. */
7752        private static boolean hasCompleteFragmentProvenance(
7753                        gudusoft.gsqlparser.dlineage.dynamicsql.DynamicSqlLineageResolver.MaterializedSite site) {
7754                if (site == null || site.sqlText == null || site.fragments.isEmpty()) {
7755                        return false;
7756                }
7757                StringBuilder rendered = new StringBuilder(site.sqlText.length());
7758                for (gudusoft.gsqlparser.dlineage.dynamicsql.SqlFragment f : site.fragments) {
7759                        rendered.append(f.text);
7760                }
7761                return rendered.toString().equals(site.sqlText);
7762        }
7763
7764        /** True when any fragment of the site stands in for a value nobody knows. */
7765        private static boolean hasValueUnknownHole(
7766                        gudusoft.gsqlparser.dlineage.dynamicsql.DynamicSqlLineageResolver.MaterializedSite site) {
7767                if (site == null) {
7768                        return false;
7769                }
7770                for (gudusoft.gsqlparser.dlineage.dynamicsql.SqlFragment f : site.fragments) {
7771                        if (!f.isLiteral() && f.valueUnknown) {
7772                                return true;
7773                        }
7774                }
7775                return false;
7776        }
7777
7778        /**
7779         * Value-unknown hole spans of a site whose text IS the analyzed text. A hole whose value
7780         * the evaluator computed from literals ({@code SqlFragment.valueUnknown} false) is an
7781         * unproven RENDERING of a known name, not a runtime template, and yields no span.
7782         */
7783        private List<FoldHole> exactFoldHoles(
7784                        gudusoft.gsqlparser.dlineage.dynamicsql.DynamicSqlLineageResolver.MaterializedSite site,
7785                        String analyzedText) {
7786                List<FoldHole> holes = new ArrayList<FoldHole>();
7787                if (site == null || site.sqlText == null || analyzedText == null
7788                                || !site.sqlText.equals(analyzedText)) {
7789                        return holes;
7790                }
7791                List<int[]> spans = gudusoft.gsqlparser.dlineage.dynamicsql.DynamicSqlEdgeProof
7792                                .holeSpansOf(site);
7793                List<gudusoft.gsqlparser.dlineage.dynamicsql.SqlFragment> fragments =
7794                                gudusoft.gsqlparser.dlineage.dynamicsql.DynamicSqlEdgeProof.holesOf(site);
7795                for (int i = 0; i < spans.size() && i < fragments.size(); i++) {
7796                        if (!fragments.get(i).valueUnknown) {
7797                                continue;
7798                        }
7799                        int[] span = spans.get(i);
7800                        holes.add(new FoldHole(span[0], span[1]));
7801                }
7802                return holes;
7803        }
7804
7805        /**
7806         * Maps the site's hole positions into a DIFFERENT rendering of the same fold — the legacy
7807         * token fold renders a hole as the variable/function name ({@code [pre_UPPER]}) where the
7808         * evaluator renders {@code [pre_@X]} — by aligning the LITERAL fragments, which both
7809         * renderings share, against the analyzed text. The literals are matched twice, greedily
7810         * from the left and greedily from the right; only when both passes agree is the alignment
7811         * forced, and the gaps between matched literals are then provably the holes. Returns
7812         * {@code null} when alignment is ambiguous or impossible — the caller falls back to the
7813         * conservative tier rather than guess.
7814         */
7815        private List<FoldHole> alignedFoldHoles(
7816                        gudusoft.gsqlparser.dlineage.dynamicsql.DynamicSqlLineageResolver.MaterializedSite site,
7817                        String analyzedText) {
7818                if (site == null || analyzedText == null) {
7819                        return null;
7820                }
7821                List<gudusoft.gsqlparser.dlineage.dynamicsql.SqlFragment> fragments = site.fragments;
7822                // A value-KNOWN hole's text IS its value (that is what valueUnknown=false means), so
7823                // it anchors the alignment exactly like a literal. Without this, a known hole
7824                // adjacent to an unknown one would smear "templated" across every segment the
7825                // combined gap touches — [' + UPPER(@D) + '].[dbo].[x] must report db, not
7826                // db|schema|name. If the legacy rendering of the known hole differs, the anchor
7827                // match fails and the alignment is refused rather than guessed.
7828                List<String> literals = new ArrayList<String>();
7829                for (gudusoft.gsqlparser.dlineage.dynamicsql.SqlFragment f : fragments) {
7830                        if (isAlignmentAnchor(f) && !f.text.isEmpty()) {
7831                                literals.add(f.text);
7832                        }
7833                }
7834                int n = literals.size();
7835                int[] posForward = new int[n];
7836                int cursor = 0;
7837                for (int i = 0; i < n; i++) {
7838                        int at = analyzedText.indexOf(literals.get(i), cursor);
7839                        if (at < 0) {
7840                                return null;
7841                        }
7842                        posForward[i] = at;
7843                        cursor = at + literals.get(i).length();
7844                }
7845                int[] posBackward = new int[n];
7846                cursor = analyzedText.length();
7847                for (int i = n - 1; i >= 0; i--) {
7848                        int at = analyzedText.lastIndexOf(literals.get(i), cursor - literals.get(i).length());
7849                        if (at < 0) {
7850                                return null;
7851                        }
7852                        posBackward[i] = at;
7853                        cursor = at;
7854                }
7855                for (int i = 0; i < n; i++) {
7856                        if (posForward[i] != posBackward[i]) {
7857                                return null;
7858                        }
7859                }
7860                // Walk the fragments, attributing each inter-literal gap to the hole fragments
7861                // between the anchors. A non-empty gap with no hole, or leftover text at either
7862                // end, means the analyzed text is not this fold — refuse.
7863                List<FoldHole> holes = new ArrayList<FoldHole>();
7864                int pos = 0;
7865                int literalIndex = 0;
7866                boolean pendingHole = false;
7867                for (gudusoft.gsqlparser.dlineage.dynamicsql.SqlFragment f : fragments) {
7868                        if (isAlignmentAnchor(f)) {
7869                                if (f.text.isEmpty()) {
7870                                        continue;
7871                                }
7872                                int at = posForward[literalIndex++];
7873                                if (at < pos) {
7874                                        return null;
7875                                }
7876                                if (at > pos && !pendingHole) {
7877                                        return null;
7878                                }
7879                                if (pendingHole) {
7880                                        holes.add(new FoldHole(pos, at));
7881                                }
7882                                pendingHole = false;
7883                                pos = at + f.text.length();
7884                        } else {
7885                                pendingHole = true;
7886                        }
7887                }
7888                if (pos < analyzedText.length() && !pendingHole) {
7889                        return null;
7890                }
7891                if (pendingHole) {
7892                        holes.add(new FoldHole(pos, analyzedText.length()));
7893                }
7894                return holes;
7895        }
7896
7897        /** Literal fragments AND value-known holes anchor the alignment; only value-unknown holes gap. */
7898        private static boolean isAlignmentAnchor(gudusoft.gsqlparser.dlineage.dynamicsql.SqlFragment f) {
7899                return f.isLiteral() || !f.valueUnknown;
7900        }
7901
7902        /**
7903         * Fallback for a legacy token fold that carries no fragment provenance: the unresolved
7904         * {@code @variable} remnants left in the text. Restricted to what is PROVABLE — a T-SQL
7905         * family dialect (where an undelimited leading {@code @} cannot begin a legal object name,
7906         * the same reasoning {@link #isPlaceholderNamedTable} relies on), and only occurrences
7907         * outside identifier delimiters and string literals. A bracketed {@code [weird@name]} is a
7908         * legal literal identifier even inside a folded batch, so it is deliberately not a hole
7909         * here; such a name is only marked when exact provenance says so.
7910         */
7911        private List<FoldHole> conservativeFoldHoles(String analyzedText) {
7912                List<FoldHole> holes = new ArrayList<FoldHole>();
7913                if (analyzedText == null || !Table.isTsqlFamilyVendor(option.getVendor())) {
7914                        return holes;
7915                }
7916                Matcher matcher = DYNAMIC_FOLD_PLACEHOLDER.matcher(analyzedText);
7917                while (matcher.find()) {
7918                        if (!isInsideDelimiterOrLiteral(analyzedText, matcher.start())) {
7919                                holes.add(new FoldHole(matcher.start(), matcher.end()));
7920                        }
7921                }
7922                Matcher delimited = DYNAMIC_FOLD_DELIMITED_PLACEHOLDER.matcher(analyzedText);
7923                while (delimited.find()) {
7924                        // The delimiters themselves are literal fold text; only the content is the hole,
7925                        // so a '#'/'@' that survives outside them still classifies the endpoint.
7926                        if (!isInsideDelimiterOrLiteral(analyzedText, delimited.start())) {
7927                                holes.add(new FoldHole(delimited.start() + 1, delimited.end() - 1));
7928                        }
7929                }
7930                return holes;
7931        }
7932
7933        /** True when {@code index} sits inside a delimited identifier or a string literal. */
7934        private static boolean isInsideDelimiterOrLiteral(String text, int index) {
7935                boolean inBracket = false, inDoubleQuote = false, inSingleQuote = false;
7936                for (int i = 0; i < index && i < text.length(); i++) {
7937                        char c = text.charAt(i);
7938                        if (inBracket) {
7939                                if (c == ']') {
7940                                        inBracket = false;
7941                                }
7942                        } else if (inDoubleQuote) {
7943                                if (c == '"') {
7944                                        inDoubleQuote = false;
7945                                }
7946                        } else if (inSingleQuote) {
7947                                if (c == '\'') {
7948                                        inSingleQuote = false;
7949                                }
7950                        } else if (c == '[') {
7951                                inBracket = true;
7952                        } else if (c == '"') {
7953                                inDoubleQuote = true;
7954                        } else if (c == '\'') {
7955                                inSingleQuote = true;
7956                        }
7957                }
7958                return inBracket || inDoubleQuote || inSingleQuote;
7959        }
7960
7961        /**
7962         * Land the placeholder verdict on the MODEL NODES a partially-folded dynamic site just
7963         * produced (docs/tmp/dynamic-template-endpoint-provenance.md): every endpoint whose
7964         * identifier OVERLAPS an unresolved hole is marked {@link EndpointKind#DYNAMIC_TEMPLATE}
7965         * and told WHICH name segments are templated, so a consumer never has to sniff {@code @}
7966         * out of a name — which it cannot do soundly, because the fold keeps the original
7967         * delimiters and {@code [weird@name]} is a legal literal identifier.
7968         *
7969         * <p>Overlap is decided on CHARACTER SPANS of this parse's own tokens against the hole
7970         * spans (exact tier) or the provable {@code @\w+} remnants (conservative tier) — never on
7971         * rendered-text coincidence, which marks unrelated literals that happen to contain a
7972         * hole's spelling.
7973         *
7974         * <p>Never marks outside a fold context ({@code before == null} short-circuits), and never
7975         * marks a plain endpoint that already existed before this fold — a static statement's
7976         * {@code dbo.[weird@name]} stays exactly what it was. <b>Known limitation:</b> when a
7977         * static statement and a folded statement name the SAME object, the model carries one
7978         * shared node; it is left unmarked, because marking it would make the static reference
7979         * wrong. Per-reference template provenance would be needed to state both facts.
7980         *
7981         * <p>Oracle {@code EXECUTE IMMEDIATE} / DBMS_SQL legacy folds splice a BARE identifier
7982         * ({@code '... FROM ' || p_tab}), indistinguishable from a real table name and carrying no
7983         * fragment provenance, so they are deliberately NOT marked rather than marked on a guess.
7984         */
7985        private void markDynamicTemplateEndpoints(Set<Table> before,
7986                        gudusoft.gsqlparser.dlineage.dynamicsql.DynamicSqlLineageResolver.MaterializedSite site,
7987                        TGSqlParser analyzedParser) {
7988                if (before == null || analyzedParser == null || analyzedParser.sqlstatements == null) {
7989                        return;
7990                }
7991                markDynamicTemplateEndpoints(before, analyzedParser, dynamicFoldHoles(site, analyzedParser.sqltext));
7992        }
7993
7994        /**
7995         * {@link #markDynamicTemplateEndpoints(Set, gudusoft.gsqlparser.dlineage.dynamicsql.DynamicSqlLineageResolver.MaterializedSite, TGSqlParser)}
7996         * for holes already known: the exact tier, when the analysis itself put each hole into the text
7997         * ({@link #formatHoles}, Mantis 4880).
7998         */
7999        private void markDynamicTemplateEndpoints(Set<Table> before, TGSqlParser analyzedParser, List<FoldHole> holes) {
8000                if (before == null || analyzedParser == null || analyzedParser.sqlstatements == null || holes.isEmpty()) {
8001                        return;
8002                }
8003                String analyzedText = analyzedParser.sqltext;
8004                TemplateTableCollector collector = new TemplateTableCollector();
8005                for (int i = 0; i < analyzedParser.sqlstatements.size(); i++) {
8006                        TCustomSqlStatement stmt = analyzedParser.sqlstatements.get(i);
8007                        if (stmt == null) {
8008                                continue;
8009                        }
8010                        try {
8011                                // acceptChildren does not preVisit the statement itself, so a top-level
8012                                // SELECT ... INTO <target> would lose its write endpoint.
8013                                if (stmt instanceof TSelectSqlStatement) {
8014                                        collector.preVisit((TSelectSqlStatement) stmt);
8015                                }
8016                                stmt.acceptChildren(collector);
8017                        } catch (RuntimeException ex) {
8018                                // A clean inner parse can still contain recovery shells. The collector is
8019                                // stateless, so keep endpoints reached before the bad slot and continue with
8020                                // later statements. A warning is intentional: marks for this site may be
8021                                // incomplete, but enrichment must never erase otherwise valid lineage.
8022                                logger.warn("Dynamic template endpoint walk was incomplete; keeping recovered lineage", ex);
8023                        }
8024                }
8025                // a name the fold also spells away from every hole is a real object, never a template (Codex review of
8026                // Mantis 4880: format('INSERT INTO %s SELECT c FROM PLACEHOLDER', t))
8027                for (TTable table : collector.tables) {
8028                        if (table.getSubquery() == null && table.getCTE() == null && table.getTableName() != null
8029                                        && !touchesHole(table.getTableName(), analyzedText, holes)) {
8030                                vetoStaticTemplateName(table.getTableName().toString());
8031                        }
8032                }
8033                for (TTable table : collector.tables) {
8034                        if (table.getSubquery() != null || table.getCTE() != null) {
8035                                continue;
8036                        }
8037                        markDynamicTemplateEndpoint(before, table.getTableName(),
8038                                        templateTableModelOf(table), analyzedText, holes);
8039                }
8040                for (TObjectName intoTarget : collector.intoTargets) {
8041                        // SELECT ... INTO targets are bound by NAME (ModelFactory.createTableByName),
8042                        // not against the parsed node, so they resolve through the name registry.
8043                        Table model = modelManager.getTableByName(
8044                                        DlineageUtil.getTableIdentityKey(intoTarget.toString()));
8045                        markDynamicTemplateEndpoint(before, intoTarget, model, analyzedText, holes);
8046                }
8047                for (TMssqlBulkInsert bulkInsert : collector.bulkInserts) {
8048                        // BULK INSERT (Mantis 4962): the target is bound by name like an INTO target. Its file
8049                        // was made a template path before the statement was analyzed (noteTemplatedLoadFiles).
8050                        TObjectName target = bulkInsert.getTableName();
8051                        if (target != null) {
8052                                markDynamicTemplateEndpoint(before, target,
8053                                                modelManager.getTableByName(DlineageUtil.getTableIdentityKey(target.toString())),
8054                                                analyzedText, holes);
8055                        }
8056                }
8057        }
8058
8059        /**
8060         * The holes of a dynamic fold's analyzed text: from the site's exact provenance when it has it (none when every
8061         * part of the string is a known value), else from the conservative remnant scan of the legacy fold.
8062         */
8063        private List<FoldHole> dynamicFoldHoles(
8064                        gudusoft.gsqlparser.dlineage.dynamicsql.DynamicSqlLineageResolver.MaterializedSite site, String analyzedText) {
8065                if (hasCompleteFragmentProvenance(site)) {
8066                        // Exact provenance is AUTHORITATIVE in both directions: zero value-unknown
8067                        // holes means every part of the string is a known value, so nothing here is
8068                        // runtime-templated and the conservative heuristic must NOT run — it would
8069                        // mark a legitimate literal-derived [@S].
8070                        if (!hasValueUnknownHole(site)) {
8071                                return Collections.emptyList();
8072                        }
8073                        if (site.sqlText.equals(analyzedText)) {
8074                                return exactFoldHoles(site, analyzedText);
8075                        }
8076                        // The analyzed text is the legacy token fold's rendering of the same
8077                        // fold; map the holes across via literal alignment, falling back to the
8078                        // conservative remnant scan when the alignment cannot be proven.
8079                        List<FoldHole> holes = alignedFoldHoles(site, analyzedText);
8080                        return holes != null ? holes : conservativeFoldHoles(analyzedText);
8081                }
8082                return conservativeFoldHoles(analyzedText);
8083        }
8084
8085        /**
8086         * Notes, while a dynamic fold is analyzed, the file literals of its BULK INSERT statements that a hole reaches
8087         * inside the quotes: such a file is a runtime template, a path node of its own that no static reference of the
8088         * same text shares (Codex review of Mantis 4962 step 3), so it must be known before the statement is analyzed.
8089         * Returns the literals it noted, for {@link #forgetTemplatedLoadFiles}.
8090         */
8091        private List<TConstant> noteTemplatedLoadFiles(
8092                        gudusoft.gsqlparser.dlineage.dynamicsql.DynamicSqlLineageResolver.MaterializedSite site,
8093                        TGSqlParser analyzedParser) {
8094                List<TConstant> added = new ArrayList<TConstant>();
8095                if (analyzedParser == null || analyzedParser.sqlstatements == null || analyzedParser.sqltext == null) {
8096                        return added;
8097                }
8098                TemplateTableCollector collector = new TemplateTableCollector();
8099                for (int i = 0; i < analyzedParser.sqlstatements.size(); i++) {
8100                        TCustomSqlStatement stmt = analyzedParser.sqlstatements.get(i);
8101                        if (stmt != null) {
8102                                try {
8103                                        // TMssqlBulkInsert.acceptChildren visits the statement itself too
8104                                        stmt.acceptChildren(collector);
8105                                } catch (RuntimeException ex) {
8106                                        logger.warn("Dynamic template endpoint walk was incomplete; keeping recovered lineage", ex);
8107                                }
8108                        }
8109                }
8110                if (collector.bulkInserts.isEmpty()) {
8111                        return added;
8112                }
8113                String analyzedText = analyzedParser.sqltext;
8114                List<FoldHole> holes = dynamicFoldHoles(site, analyzedText);
8115                for (TMssqlBulkInsert bulkInsert : collector.bulkInserts) {
8116                        TConstant file = bulkInsert.getDatafile();
8117                        if (file != null && literalContentTouched(file, analyzedText, holes) && !templatedLoadFiles.containsKey(file)) {
8118                                templatedLoadFiles.put(file, file);
8119                                added.add(file);
8120                        }
8121                }
8122                return added;
8123        }
8124
8125        private void forgetTemplatedLoadFiles(List<TConstant> added) {
8126                for (TConstant file : added) {
8127                        templatedLoadFiles.remove(file);
8128                }
8129        }
8130
8131        /** The file literals of the dynamic folds being analyzed that a hole reaches, by identity. */
8132        private final Map<TConstant, TConstant> templatedLoadFiles = new IdentityHashMap<TConstant, TConstant>();
8133
8134        /** Whether a hole reaches any character inside the quotes of the string literal {@code file}. */
8135        private static boolean literalContentTouched(TConstant file, String analyzedText, List<FoldHole> holes) {
8136                if (holes.isEmpty() || file.getStartToken() == null || file.getEndToken() == null || file.toString() == null) {
8137                        return false;
8138                }
8139                String literal = file.toString().trim();
8140                int from = (int) file.getStartToken().offset;
8141                int to = (int) (file.getEndToken().offset + file.getEndToken().toString().length());
8142                if (literal.length() > 1 && (literal.charAt(0) == 'N' || literal.charAt(0) == 'n') && literal.charAt(1) == '\'') {
8143                        from++;
8144                }
8145                // the characters inside the quotes
8146                int contentFrom = from + 1;
8147                int contentTo = to - 1;
8148                if (from < 0 || contentTo < contentFrom || to > analyzedText.length()) {
8149                        return false;
8150                }
8151                for (FoldHole hole : holes) {
8152                        if (hole.touches(contentFrom, contentTo)) {
8153                                return true;
8154                        }
8155                }
8156                return false;
8157        }
8158
8159        /** Collects the endpoint-bearing name nodes of an analyzed dynamic-fold parse. */
8160        private static final class TemplateTableCollector extends TParseTreeVisitor {
8161                final List<TTable> tables = new ArrayList<TTable>();
8162                final List<TObjectName> intoTargets = new ArrayList<TObjectName>();
8163                final List<TMssqlBulkInsert> bulkInserts = new ArrayList<TMssqlBulkInsert>();
8164
8165                @Override
8166                public void preVisit(TTable table) {
8167                        if (table != null) {
8168                                tables.add(table);
8169                        }
8170                }
8171
8172                @Override
8173                public void preVisit(TMssqlBulkInsert bulkInsert) {
8174                        if (bulkInsert != null) {
8175                                bulkInserts.add(bulkInsert);
8176                        }
8177                }
8178
8179                @Override
8180                public void preVisit(TSelectSqlStatement select) {
8181                        if (select == null) {
8182                                return;
8183                        }
8184                        if (select.getIntoTableClause() != null
8185                                        && select.getIntoTableClause().getTableName() != null) {
8186                                intoTargets.add(select.getIntoTableClause().getTableName());
8187                        }
8188                        // T-SQL SELECT ... INTO <target> arrives as an INTO clause carrying an
8189                        // expression list, the same shape analyzeSelectIntoClause() reads.
8190                        TIntoClause intoClause = select.getIntoClause();
8191                        if (intoClause == null || intoClause.getExprList() == null) {
8192                                return;
8193                        }
8194                        for (int i = 0; i < intoClause.getExprList().size(); i++) {
8195                                TExpression expr = intoClause.getExprList().getExpression(i);
8196                                if (expr != null && expr.getObjectOperand() != null) {
8197                                        intoTargets.add(expr.getObjectOperand());
8198                                }
8199                        }
8200                }
8201        }
8202
8203        /** Per-endpoint half of {@link #markDynamicTemplateEndpoints}: span overlap + part labelling. */
8204        private void markDynamicTemplateEndpoint(Set<Table> before, TObjectName tableName,
8205                        Table model, String analyzedText, List<FoldHole> holes) {
8206                if (tableName == null || model == null || tableName.getStartToken() == null
8207                                || tableName.getEndToken() == null) {
8208                        return;
8209                }
8210                int from = (int) tableName.getStartToken().offset;
8211                int to = (int) (tableName.getEndToken().offset
8212                                + tableName.getEndToken().toString().length());
8213                if (from < 0 || to <= from || to > analyzedText.length()) {
8214                        return;
8215                }
8216                List<int[]> segments = nameSegmentSpans(analyzedText, from, to);
8217                if (segments.isEmpty()) {
8218                        return;
8219                }
8220                int last = segments.size() - 1;
8221                StringBuilder parts = new StringBuilder();
8222                boolean identityTemplated = false;
8223                for (int i = 0; i < segments.size(); i++) {
8224                        int[] segment = segments.get(i);
8225                        boolean touched = false;
8226                        for (FoldHole hole : holes) {
8227                                if (!hole.touches(segment[0], segment[1])) {
8228                                        continue;
8229                                }
8230                                touched = true;
8231                                // The hole reaching the FIRST character of the object-name segment means the
8232                                // '#'/'@' prefix that would classify this endpoint as temp / table-variable is
8233                                // itself unknown runtime text ('INSERT INTO ' + @tab folds to a table named
8234                                // @tab, which really names a CATALOG object).
8235                                if (i == last
8236                                                && hole.touches(firstNameChar(analyzedText, segment),
8237                                                                firstNameChar(analyzedText, segment) + 1)) {
8238                                        identityTemplated = true;
8239                                }
8240                        }
8241                        if (!touched) {
8242                                continue;
8243                        }
8244                        String part = templatedPartLabel(i, last);
8245                        if (part == null) {
8246                                continue;
8247                        }
8248                        if (parts.length() > 0) {
8249                                parts.append('|');
8250                        }
8251                        parts.append(part);
8252                }
8253                if (parts.length() == 0) {
8254                        return;
8255                }
8256                if (model.hasSubquery() || model.isPseudo() || model.isVariable()) {
8257                        return;
8258                }
8259                // A plain endpoint that predates this fold is shared with a static reference; marking
8260                // it would make that reference wrong. An endpoint already known to be a template may
8261                // accumulate more segments from a second fold.
8262                if (before.contains(model) && !model.isDynamicTemplate()) {
8263                        return;
8264                }
8265                // The same contract, order-independent: a name a STATIC statement referenced at any
8266                // earlier point of the unit is never marked (see staticTemplateVetoKeys).
8267                if (staticTemplateVetoKeys.contains(DlineageUtil.getTableIdentityKey(tableName.toString()))
8268                                || staticTemplateVetoKeys.contains(
8269                                                DlineageUtil.getTableIdentityKeyWithDefaultSchema(tableName.toString()))) {
8270                        return;
8271                }
8272                model.markDynamicTemplate(parts.toString(), identityTemplated);
8273                anyDynamicTemplateMarked = true;
8274        }
8275
8276        /** Whether a hole reaches any character of {@code name} in {@code analyzedText}; true when its span is unknown. */
8277        private static boolean touchesHole(TObjectName name, String analyzedText, List<FoldHole> holes) {
8278                if (name.getStartToken() == null || name.getEndToken() == null) {
8279                        return true;
8280                }
8281                int from = (int) name.getStartToken().offset;
8282                int to = (int) (name.getEndToken().offset + name.getEndToken().toString().length());
8283                if (from < 0 || to <= from || to > analyzedText.length()) {
8284                        return true;
8285                }
8286                for (FoldHole hole : holes) {
8287                        if (hole.touches(from, to)) {
8288                                return true;
8289                        }
8290                }
8291                return false;
8292        }
8293
8294        /** Offset of a segment's first NAME character, skipping an opening identifier delimiter. */
8295        private static int firstNameChar(String text, int[] segment) {
8296                int i = segment[0];
8297                if (i < text.length()) {
8298                        char c = text.charAt(i);
8299                        if (c == '[' || c == '"' || c == '`') {
8300                                return i + 1;
8301                        }
8302                }
8303                return i;
8304        }
8305
8306        /**
8307         * Splits the source span of a qualified name into one span per identifier segment, on the
8308         * dots that sit OUTSIDE identifier delimiters (so {@code [a.b].c} is two segments).
8309         */
8310        private static List<int[]> nameSegmentSpans(String text, int from, int to) {
8311                List<int[]> segments = new ArrayList<int[]>();
8312                int segmentStart = from;
8313                boolean inBracket = false, inDoubleQuote = false, inBacktick = false;
8314                for (int i = from; i < to; i++) {
8315                        char c = text.charAt(i);
8316                        if (inBracket) {
8317                                if (c == ']') {
8318                                        inBracket = false;
8319                                }
8320                        } else if (inDoubleQuote) {
8321                                if (c == '"') {
8322                                        inDoubleQuote = false;
8323                                }
8324                        } else if (inBacktick) {
8325                                if (c == '`') {
8326                                        inBacktick = false;
8327                                }
8328                        } else if (c == '[') {
8329                                inBracket = true;
8330                        } else if (c == '"') {
8331                                inDoubleQuote = true;
8332                        } else if (c == '`') {
8333                                inBacktick = true;
8334                        } else if (c == '.') {
8335                                segments.add(new int[] { segmentStart, i });
8336                                segmentStart = i + 1;
8337                        }
8338                }
8339                segments.add(new int[] { segmentStart, to });
8340                return segments;
8341        }
8342
8343        /**
8344         * Outermost-first segment labels for a qualified name, counted from the RIGHT so a
8345         * 2-, 3- or 4-part name labels consistently. Anything deeper than server is unlabelled.
8346         */
8347        private String templatedPartLabel(int index, int lastIndex) {
8348                switch (lastIndex - index) {
8349                case 0:
8350                        return "name";
8351                case 1:
8352                        return "schema";
8353                case 2:
8354                        return "db";
8355                case 3:
8356                        return "server";
8357                default:
8358                        return null;
8359                }
8360        }
8361
8362        /** The dlineage endpoint model bound to a parsed table, across the binding maps. */
8363        private Table templateTableModelOf(TTable table) {
8364                Object model = modelManager.getModel(table);
8365                if (!(model instanceof Table)) {
8366                        model = modelManager.getCreateModel(table);
8367                }
8368                if (!(model instanceof Table)) {
8369                        model = modelManager.getInsertModel(table);
8370                }
8371                if (!(model instanceof Table)) {
8372                        model = modelManager.getUpdateModel(table);
8373                }
8374                if (!(model instanceof Table)) {
8375                        model = modelManager.getMergeModel(table);
8376                }
8377                return model instanceof Table ? (Table) model : null;
8378        }
8379
8380        /**
8381         * What a site can say about its holes. {@code exact} is true only when the evaluator
8382         * saw the site's argument: then {@code count == fragments.size()}, and
8383         * {@code materializedText} is the argument as far as it folded (holes rendered as
8384         * their placeholder text). Otherwise {@code count} is {@code -1} and the list is
8385         * empty - "not known", never an invented zero.
8386         */
8387        private static final class DynamicHoleEvidence {
8388                final int count;
8389                final boolean exact;
8390                final List<UnresolvedFragment> fragments;
8391                final String materializedText;
8392
8393                DynamicHoleEvidence(int count, boolean exact, List<UnresolvedFragment> fragments,
8394                                String materializedText) {
8395                        this.count = count;
8396                        this.exact = exact;
8397                        this.fragments = fragments;
8398                        this.materializedText = materializedText;
8399                }
8400        }
8401
8402        private static final DynamicHoleEvidence NO_HOLE_EVIDENCE = new DynamicHoleEvidence(-1, false,
8403                        Collections.<UnresolvedFragment>emptyList(), null);
8404
8405        private DynamicHoleEvidence dynamicHoleEvidence(
8406                        gudusoft.gsqlparser.dlineage.dynamicsql.DynamicSqlLineageResolver.MaterializedSite site) {
8407                if (site == null) {
8408                        return NO_HOLE_EVIDENCE;
8409                }
8410                if (!site.provenanceIncomplete) {
8411                        return new DynamicHoleEvidence(0, true, Collections.<UnresolvedFragment>emptyList(),
8412                                        site.sqlText);
8413                }
8414                if (site.fragments.isEmpty()) {
8415                        // The value did not reduce and the producer left no provenance (a channel
8416                        // without fragments, or a value that never touched a variable).
8417                        return new DynamicHoleEvidence(-1, false, Collections.<UnresolvedFragment>emptyList(),
8418                                        site.sqlText);
8419                }
8420                List<UnresolvedFragment> holes = new ArrayList<UnresolvedFragment>();
8421                for (gudusoft.gsqlparser.dlineage.dynamicsql.SqlFragment fragment : site.fragments) {
8422                        UnresolvedFragment hole = UnresolvedFragment.of(fragment);
8423                        if (hole != null) {
8424                                holes.add(hole);
8425                        }
8426                }
8427                // Only a reduced argument has a materialization; rendering the provenance of an
8428                // unreduced one would fabricate text (the arguments of an unfoldable call glued
8429                // together). Its holes are still reported.
8430                String text = site.sqlText;
8431                if (site.sqlText == null && holes.isEmpty()) {
8432                        // The value did not reduce, yet nothing in its provenance is a hole: the
8433                        // producer that stopped the fold left no fragment (an undecidable CASE, a
8434                        // resource bound, a malformed call). "Not known" - never an exact zero.
8435                        return new DynamicHoleEvidence(-1, false, Collections.<UnresolvedFragment>emptyList(), text);
8436                }
8437                return new DynamicHoleEvidence(holes.size(), true, holes, text);
8438        }
8439
8440        /**
8441         * True when the materialized text is nothing but value-unknown holes - a bare
8442         * {@code @sql} whose value the evaluator could not fold. Parsing that as SQL proves
8443         * nothing about the runtime statement, so such a site is reported as its
8444         * argument shape (with the hole evidence) instead of as a parse failure of text
8445         * that was never SQL.
8446         */
8447        private static boolean isWholeTextHole(
8448                        gudusoft.gsqlparser.dlineage.dynamicsql.DynamicSqlLineageResolver.MaterializedSite site) {
8449                if (site == null || site.sqlText == null || site.fragments.isEmpty()) {
8450                        return false;
8451                }
8452                for (gudusoft.gsqlparser.dlineage.dynamicsql.SqlFragment fragment : site.fragments) {
8453                        if (fragment.isLiteral() || !fragment.valueUnknown) {
8454                                return false;
8455                        }
8456                }
8457                return true;
8458        }
8459
8460        private String dynamicTrustReason(String prefix, int unprovenCount,
8461                        int unresolvedHoleCount, boolean holeCountExact) {
8462                String holes = holeCountExact ? unresolvedHoleCount + " unresolved fragment(s)"
8463                                : "unresolved fragment count unavailable from the producer";
8464                return prefix + "; " + holes
8465                                + (unprovenCount >= 0 ? "; " + unprovenCount + " unproven candidate relationship(s)" : "");
8466        }
8467
8468        /**
8469         * Classify the lineage edges a folded dynamic site produced. {@code relsBefore} is the relation
8470         * snapshot taken immediately before the folded SQL was analyzed; the tail of the current relation set
8471         * is this site's delta (relationHolder is insertion-ordered). counts[0]=resolved (edge into a real
8472         * target column), counts[1]=partial (edge into a T-SQL variable / placeholder target). Edges whose
8473         * target is an intermediate result-set are ignored — only final-target edges are counted.
8474         */
8475        /**
8476         * String-building functions whose names show up as fabricated source "tables" when a partial
8477         * fold leaves a runtime-built object name in the re-parsed text (e.g.
8478         * {@code EXEC('... FROM ' + QUOTENAME(@tab))}).
8479         */
8480        private static final java.util.Set<String> DYNAMIC_NAME_BUILDER_FUNCTIONS = new HashSet<String>(
8481                        Arrays.asList("QUOTENAME", "REPLACE", "CONCAT", "FORMAT", "UPPER", "LOWER", "TRIM", "LTRIM", "RTRIM"));
8482
8483        /**
8484         * Returns {@code {resolvedCount, partialCount, placeholderSeen, resultSetEdgeCount}} for the
8485         * lineage edges added after {@code beforeCount}.
8486         *
8487         * <ul>
8488         *   <li>{@code resolvedCount} — edges into a real base-table target column with no placeholder
8489         *       contamination anywhere in the new edge set.</li>
8490         *   <li>{@code partialCount} — edges into a variable / placeholder target, plus (when the edge
8491         *       set is contaminated) every edge that would otherwise have counted as resolved, so the
8492         *       public counts never claim usable lineage a placeholder actually feeds.</li>
8493         *   <li>{@code placeholderSeen} — 1 when any new edge draws from a fabricated placeholder table:
8494         *       a raw name starting with '@' (delimited {@code [@x]} identifiers are legal object names
8495         *       and are NOT placeholders), or — during a partial fold only — a variable-backed source
8496         *       table (Oracle {@code ' ... FROM ' || p_tab}) or a table named after a string-building
8497         *       function ({@code QUOTENAME} etc.).</li>
8498         *   <li>{@code resultSetEdgeCount} — clean edges into a result-set column (a folded dynamic
8499         *       {@code SELECT}'s projection). These contribute to the site-resolution decision but stay
8500         *       out of {@code resolvedCount}, which is documented as base-table targets only.</li>
8501         * </ul>
8502         */
8503        private int[] classifyDynamicSiteLineage(int beforeCount, boolean partialFold) {
8504                int resolved = 0;
8505                int partial = 0;
8506                int resultSetEdges = 0;
8507                boolean placeholderSeen = false;
8508                Relationship[] after = modelManager.getRelations();
8509                for (int i = beforeCount; i < after.length; i++) {
8510                        Relationship r = after[i];
8511                        if (r == null) {
8512                                continue;
8513                        }
8514                        if (hasPlaceholderSourceTable(r, partialFold)) {
8515                                placeholderSeen = true;
8516                        }
8517                        if (r.getTarget() instanceof ResultColumnRelationshipElement) {
8518                                resultSetEdges++;
8519                        } else if (r.getTarget() instanceof TableColumnRelationshipElement) {
8520                                TableColumn targetColumn = ((TableColumnRelationshipElement) r.getTarget()).getElement();
8521                                Table targetTable = targetColumn == null ? null : targetColumn.getTable();
8522                                if (isPlaceholderNamedTable(targetTable, partialFold)) {
8523                                        // Runtime-built INSERT destination (e.g. 'INSERT INTO '+QUOTENAME(@tab)+...)
8524                                        // — the target object is unknown, so the whole site is contaminated.
8525                                        placeholderSeen = true;
8526                                        partial++;
8527                                } else if (isUnresolvedTargetTable(targetTable)) {
8528                                        partial++;
8529                                } else {
8530                                        resolved++;
8531                                }
8532                        }
8533                }
8534                if (placeholderSeen) {
8535                        // Contaminated edges must not be surfaced as usable lineage
8536                        // (producedLineage() reads resolvedCount).
8537                        partial += resolved;
8538                        resolved = 0;
8539                        resultSetEdges = 0;
8540                }
8541                return new int[] { resolved, partial, placeholderSeen ? 1 : 0, resultSetEdges };
8542        }
8543
8544        /**
8545         * Oracle DBMS_SQL resolution (plan Phase 5 slice 4; supersedes the Phase 1
8546         * visibility-only recording): a {@code DBMS_SQL.PARSE(c, <sql>, ...)} call is a
8547         * dynamic-SQL execution site. The SQL-text argument is folded through the shared
8548         * literal-bindable whitelist — inline literals, literal-composed locals, and
8549         * parameters bound at vetted literal call sites (same-file or cross-file via the
8550         * routine catalog) — and each folded text is analyzed through the ordinary inner-
8551         * SQL path. Anything that does not fold keeps an honest UNRESOLVED diagnostic;
8552         * PARSE_ERROR is reserved for compile-time literal text, mirroring the EXECUTE
8553         * IMMEDIATE status contract. Recording keys off PARSE, the call that carries the
8554         * SQL text, so a PARSE/EXECUTE pair maps to one site.
8555         */
8556        private void recordDbmsSqlSiteIfParseCall(TBasicStmt stmt, TFunctionCall functionCall) {
8557                if (functionCall == null || functionCall.getFunctionName() == null
8558                                || option.getVendor() != EDbVendor.dbvoracle) {
8559                        return;
8560                }
8561                String calledName = functionCall.getFunctionName().toString();
8562                if (!SQLUtil.compareIdentifier(EDbVendor.dbvoracle, ESQLDataObjectType.dotProcedure,
8563                                calledName, "DBMS_SQL.PARSE")
8564                                && !SQLUtil.compareIdentifier(EDbVendor.dbvoracle, ESQLDataObjectType.dotProcedure,
8565                                                calledName, "SYS.DBMS_SQL.PARSE")) {
8566                        return;
8567                }
8568                if (!option.isAnalyzeDynamicSql()) {
8569                        recordDynamicSqlSite(stmt, DynamicSqlSite.Kind.DBMS_SQL,
8570                                        DynamicSqlSite.Status.UNRESOLVED,
8571                                        "dynamic SQL inner analysis disabled");
8572                        return;
8573                }
8574                boolean literalSql = false;
8575                DynamicSqlTrustMode trustMode = option.getDynamicSqlTrustMode();
8576                TExpression sqlArg = null;
8577                if (functionCall.getArgs() != null && functionCall.getArgs().size() >= 2) {
8578                        // Positional form: PARSE(c, <sql>, <lang>). Named association may reorder the
8579                        // arguments (statement => '...'), so resolve the named form first.
8580                        boolean namedForm = false;
8581                        for (int i = 0; i < functionCall.getArgs().size(); i++) {
8582                                TExpression arg = functionCall.getArgs().getExpression(i);
8583                                if (arg != null && arg.getExpressionType() == EExpressionType.ref_arrow_t) {
8584                                        namedForm = true;
8585                                        if (arg.getLeftOperand() != null
8586                                                        && SQLUtil.sameName(EDbVendor.dbvoracle, ESQLDataObjectType.dotColumn,
8587                                                                        arg.getLeftOperand().toString(), "statement")) {
8588                                                sqlArg = arg.getRightOperand();
8589                                                break;
8590                                        }
8591                                }
8592                        }
8593                        if (sqlArg == null && !namedForm) {
8594                                sqlArg = functionCall.getArgs().getExpression(1);
8595                        }
8596                        literalSql = sqlArg != null && sqlArg.getExpressionType() == EExpressionType.simple_constant_t;
8597                }
8598                if (sqlArg != null) {
8599                        TCustomSqlStatement routine = null;
8600                        for (int i = stmtStack.size() - 1; i >= 0; i--) {
8601                                if (stmtStack.get(i) instanceof TStoredProcedureSqlStatement) {
8602                                        routine = stmtStack.get(i);
8603                                        break;
8604                                }
8605                        }
8606                        List<String> foldedTexts;
8607                        try {
8608                                foldedTexts = gudusoft.gsqlparser.dlineage.dynamicsql.DynamicSqlLineageResolver
8609                                                .foldDbmsSqlTexts(routine, sqlArg, routineCatalog, 4);
8610                        } catch (RuntimeException ex) {
8611                                foldedTexts = new ArrayList<String>();
8612                        }
8613                        if (!foldedTexts.isEmpty()) {
8614                                Set<Relationship> shadowBefore = trustMode == DynamicSqlTrustMode.SHADOW
8615                                                ? snapshotDynamicRelationships() : null;
8616                                int dynamicSitesBefore = dynamicSqlSites.size();
8617                                int relsBeforeDynamic = modelManager.getRelations().length;
8618                                int errorsBeforeDynamic = errorInfos.size();
8619                                boolean analyzedAny = false;
8620                                boolean variantFailed = false;
8621                                List<Long> provenVariantRelationshipIds = new ArrayList<Long>();
8622                                for (String foldedText : foldedTexts) {
8623                                        int relationshipsBeforeVariant = modelManager.getRelations().length;
8624                                        int dynamicSitesBeforeVariant = dynamicSqlSites.size();
8625                                        int errorsBeforeVariant = errorInfos.size();
8626                                        TGSqlParser sqlparser = createSqlParser(option.getVendor());
8627                                        sqlparser.sqltext = foldedText;
8628                                        int result;
8629                                        try {
8630                                                result = parseWithCommentBindings(sqlparser);
8631                                        } catch (RuntimeException ex) {
8632                                                variantFailed = true;
8633                                                continue;
8634                                        }
8635                                        if (result != 0 || sqlparser.sqlstatements == null) {
8636                                                variantFailed = true;
8637                                                continue;
8638                                        }
8639                                        try {
8640                                                dynamicFoldDepth++;
8641                                                try {
8642                                                        for (int i = 0; i < sqlparser.sqlstatements.size(); i++) {
8643                                                                analyzeCustomSqlStmt(sqlparser.sqlstatements.get(i));
8644                                                        }
8645                                                } finally {
8646                                                        dynamicFoldDepth--;
8647                                                }
8648                                                analyzedAny = true;
8649                                                if (errorInfos.size() == errorsBeforeVariant
8650                                                                && !hasIncompleteDynamicSqlSitesSince(dynamicSitesBeforeVariant)) {
8651                                                        provenVariantRelationshipIds.addAll(provenDynamicRelationshipIdsSince(
8652                                                                        relationshipsBeforeVariant, dynamicSitesBeforeVariant));
8653                                                }
8654                                        } catch (RuntimeException ex) {
8655                                                // An analyzer crash on folded text must neither surface as an
8656                                                // AnalyzeError blessed into output nor masquerade as resolved.
8657                                                variantFailed = true;
8658                                        }
8659                                }
8660                                // Inner analysis handlers may swallow their own crashes into
8661                                // errorInfos (never rethrowing to the catch above). Errors raised
8662                                // while analyzing SYNTHESIZED text carry misleading fragment-
8663                                // relative coordinates - roll them back and treat the variant as
8664                                // failed so the site status stays honest.
8665                                if (errorInfos.size() > errorsBeforeDynamic) {
8666                                        while (errorInfos.size() > errorsBeforeDynamic) {
8667                                                errorInfos.remove(errorInfos.size() - 1);
8668                                        }
8669                                        variantFailed = true;
8670                                }
8671                                int[] dynCounts = classifyDynamicSiteLineage(relsBeforeDynamic, false);
8672                                boolean nestedIncomplete = trustMode != DynamicSqlTrustMode.LEGACY
8673                                                && hasIncompleteDynamicSqlSitesSince(dynamicSitesBefore);
8674                                boolean aggregateIncomplete = variantFailed || nestedIncomplete;
8675                                int shadowUnproven = trustMode == DynamicSqlTrustMode.SHADOW && aggregateIncomplete
8676                                                ? Math.max(0, countNewDynamicRelationships(shadowBefore)
8677                                                                - provenVariantRelationshipIds.size()) : 0;
8678                                if (analyzedAny && !aggregateIncomplete) {
8679                                        recordDynamicSqlSite(stmt, DynamicSqlSite.Kind.DBMS_SQL,
8680                                                        DynamicSqlSite.Status.RESOLVED, null, dynCounts[0], dynCounts[1],
8681                                                        0, 0, true, trustMode,
8682                                                        relsBeforeDynamic, dynamicSitesBefore,
8683                                                        provenVariantRelationshipIds);
8684                                        return;
8685                                }
8686                                if (analyzedAny) {
8687                                        // Some accepted variants could not be analyzed - the aggregate
8688                                        // picture is incomplete. SHADOW retains the historical candidates
8689                                        // and labels the aggregate as lacking per-edge proof.
8690                                        DynamicSqlSite.Status status = trustMode == DynamicSqlTrustMode.SHADOW
8691                                                        && (shadowUnproven > 0 || !provenVariantRelationshipIds.isEmpty())
8692                                                        ? DynamicSqlSite.Status.PARTIAL
8693                                                        : DynamicSqlSite.Status.UNRESOLVED;
8694                                        String reason = nestedIncomplete
8695                                                        ? "nested dynamic SQL in a folded DBMS_SQL variant is incomplete"
8696                                                        : "some folded DBMS_SQL variants failed to parse or analyze; lineage may be incomplete";
8697                                        recordDynamicSqlSite(stmt, DynamicSqlSite.Kind.DBMS_SQL, status,
8698                                                        trustMode == DynamicSqlTrustMode.SHADOW
8699                                    ? dynamicTrustReason(reason + "; SHADOW retained historical candidates",
8700                                                                                        shadowUnproven, nestedIncomplete ? -1 : 0,
8701                                                                                        !nestedIncomplete)
8702                                                                        : reason,
8703                                                        dynCounts[0], dynCounts[1], shadowUnproven,
8704                                                        nestedIncomplete ? -1 : 0, !nestedIncomplete, trustMode,
8705                                                        relsBeforeDynamic, dynamicSitesBefore,
8706                                                        provenVariantRelationshipIds);
8707                                        return;
8708                                }
8709                                if (variantFailed && literalSql) {
8710                                        recordDynamicSqlSite(stmt, DynamicSqlSite.Kind.DBMS_SQL,
8711                                                        DynamicSqlSite.Status.PARSE_ERROR,
8712                                                        "DBMS_SQL.PARSE literal SQL failed to parse", 0, 0,
8713                                                        0, 0, true, trustMode,
8714                                                        relsBeforeDynamic, dynamicSitesBefore);
8715                                        return;
8716                                }
8717                                if (variantFailed) {
8718                                        recordDynamicSqlSite(stmt, DynamicSqlSite.Kind.DBMS_SQL,
8719                                                        DynamicSqlSite.Status.UNRESOLVED,
8720                                                        "folded DBMS_SQL text failed to parse", 0, 0,
8721                                                        0, 0, true, trustMode,
8722                                                        relsBeforeDynamic, dynamicSitesBefore);
8723                                        return;
8724                                }
8725                        }
8726                }
8727                String unresolvedReason = literalSql ? "DBMS_SQL.PARSE with literal SQL; inner SQL not analyzed"
8728                                : "DBMS_SQL.PARSE with runtime SQL text; inner SQL not analyzed";
8729                if (trustMode == DynamicSqlTrustMode.LEGACY) {
8730                        recordDynamicSqlSite(stmt, DynamicSqlSite.Kind.DBMS_SQL,
8731                                        DynamicSqlSite.Status.UNRESOLVED, unresolvedReason);
8732                } else {
8733                        boolean runtimeText = !literalSql;
8734                        if (runtimeText && trustMode == DynamicSqlTrustMode.SHADOW) {
8735                                unresolvedReason = dynamicTrustReason("SHADOW observed no materialized DBMS_SQL candidate",
8736                                                0, -1, false);
8737                        }
8738                        recordDynamicSqlSite(stmt, DynamicSqlSite.Kind.DBMS_SQL,
8739                                        DynamicSqlSite.Status.UNRESOLVED, unresolvedReason, 0, 0,
8740                                        0,
8741                                        runtimeText ? -1 : 0, !runtimeText, trustMode);
8742                }
8743        }
8744
8745        private boolean hasIncompleteDynamicSqlSitesSince(int start) {
8746                return hasIncompleteDynamicSqlSitesSince(dynamicSqlSites, start);
8747        }
8748
8749        private static boolean hasIncompleteDynamicSqlSitesSince(List<DynamicSqlSite> sites, int start) {
8750                for (int i = Math.max(0, start); i < sites.size(); i++) {
8751                        DynamicSqlSite site = sites.get(i);
8752                        if (site.getStatus() != DynamicSqlSite.Status.RESOLVED) {
8753                                return true;
8754                        }
8755                }
8756                return false;
8757        }
8758
8759        /** True when any source of this edge is a fabricated placeholder table (see caller javadoc). */
8760        private boolean hasPlaceholderSourceTable(Relationship r, boolean partialFold) {
8761                if (r.getSources() == null) {
8762                        return false;
8763                }
8764                for (Object source : r.getSources()) {
8765                        if (!(source instanceof TableColumnRelationshipElement)) {
8766                                continue;
8767                        }
8768                        TableColumn sourceColumn = ((TableColumnRelationshipElement) source).getElement();
8769                        Table sourceTable = sourceColumn == null ? null : sourceColumn.getTable();
8770                        if (sourceTable == null || sourceTable.isPseudo()) {
8771                                continue;
8772                        }
8773                        if (sourceTable.isVariable()) {
8774                                // A variable-backed source inside a PARTIAL fold is the folded remnant of a
8775                                // runtime-built OBJECT NAME only when it stands in table position — its
8776                                // "columns" then carry real column names distinct from the variable's own
8777                                // name ('... FROM ' || p_tab reparsed as table p_tab with column s1). A
8778                                // scalar variable used as a value/predicate operand surfaces as a
8779                                // single-column variable whose column IS the variable name — legitimate,
8780                                // not contamination.
8781                                if (partialFold && sourceColumn != null && sourceColumn.getName() != null
8782                                                && sourceTable.getName() != null
8783                                                && !DlineageUtil.sameName(ESQLDataObjectType.dotColumn,
8784                                                                sourceColumn.getName(), sourceTable.getName())) {
8785                                        return true;
8786                                }
8787                                continue;
8788                        }
8789                        if (isPlaceholderNamedTable(sourceTable, partialFold)) {
8790                                return true;
8791                        }
8792                        // a file path a dynamic fold assembled at runtime ('... FROM ''' + @file + '''', Mantis 4962)
8793                        if (sourceTable.isPath() && sourceTable.isDynamicTemplate()) {
8794                                return true;
8795                        }
8796                }
8797                return false;
8798        }
8799
8800        /**
8801         * A table whose NAME betrays a runtime-built object: a raw name starting with '@' (fold
8802         * artifact; delimited {@code [@x]} / {@code "@x"} are legal object names and do not match), or —
8803         * during a partial fold — a name equal to a string-building function left behind by the folder
8804         * ({@code QUOTENAME(...)} etc.). Applied to both edge sources and INSERT targets.
8805         */
8806        private boolean isPlaceholderNamedTable(Table table, boolean partialFold) {
8807                if (table == null) {
8808                        return false;
8809                }
8810                String name = table.getName() == null ? "" : table.getName().trim();
8811                if (name.startsWith("@")) {
8812                        return true;
8813                }
8814                // Locale-stable fold: default-locale toUpperCase turns "ltrim" into "LTRİM" under tr_TR.
8815                return partialFold && DYNAMIC_NAME_BUILDER_FUNCTIONS.contains(name.toUpperCase(Locale.ENGLISH));
8816        }
8817
8818        /**
8819         * A folded dynamic site's target is "unresolved" when it is a T-SQL variable / placeholder rather
8820         * than a real object. When GSP only partially folds (e.g. {@code EXEC sp_executesql @sql} →
8821         * {@code INSERT INTO @p SELECT ...}), the re-parsed target is a table literally named {@code @p};
8822         * its {@code isVariable()} flag is not set, so the reliable signal is a name starting with '@'.
8823         */
8824        private boolean isUnresolvedTargetTable(Table table) {
8825                if (table == null) {
8826                        return true;
8827                }
8828                if (table.isVariable() || table.isPseudo()) {
8829                        return true;
8830                }
8831                String name = table.getName();
8832                if (name != null) {
8833                        // Raw-name check only: delimited [@x] / "@x" are legal permanent-object names,
8834                        // while the fold artifact keeps a bare leading '@'.
8835                        if (name.trim().startsWith("@")) {
8836                                return true;
8837                        }
8838                }
8839                return false;
8840        }
8841
8842        /** True when a module name resolves to sp_executesql, ignoring schema qualifier and brackets/quotes. */
8843        private boolean isSpExecutesql(TObjectName moduleName) {
8844                if (moduleName == null) {
8845                        return false;
8846                }
8847                String s = moduleName.toString();
8848                int dot = s.lastIndexOf('.');
8849                if (dot >= 0) {
8850                        s = s.substring(dot + 1);
8851                }
8852                s = s.replace("[", "").replace("]", "").replace("\"", "").replace("`", "").trim();
8853                return "sp_executesql".equalsIgnoreCase(s);
8854        }
8855
8856        /**
8857         * True when a module name is a <em>local</em> invocation of the system
8858         * procedure sp_execute_external_script whose input query therefore executes in
8859         * the caller's current database context.
8860         *
8861         * <p>Uses the parser's structured object/schema segmentation plus the
8862         * identifier-equality facade ({@link SQLUtil#sameName}) so vendor delimiter
8863         * rules are honored: a naive {@code lastIndexOf('.')} or delimiter strip would
8864         * mistake a delimited identifier that merely contains a dot
8865         * ({@code [dbo.sp_execute_external_script]}) or one with escaped delimiters
8866         * ({@code [sp_execute_external_]]script]}, {@code "sp_execute_external_""script"})
8867         * for the system procedure.
8868         *
8869         * <p>Only the unqualified form or a bare {@code sys} schema is accepted, and any
8870         * server or database qualifier is rejected. A {@code dbo.} (or other user
8871         * schema) qualifier denotes a user procedure, and a server/database qualifier
8872         * (e.g. a four-part {@code Srv.Db.sys.sp_execute_external_script} linked-server
8873         * call) means the input query resolves in a remote context this pass does not
8874         * model. In both cases the argument is left unanalyzed rather than analyzed
8875         * against the wrong objects.
8876         */
8877        private boolean isSpExecuteExternalScript(TObjectName moduleName) {
8878                if (moduleName == null || moduleName.getObjectToken() == null) {
8879                        return false;
8880                }
8881                EDbVendor vendor = option.getVendor();
8882                if (!SQLUtil.sameName(vendor, gudusoft.gsqlparser.sqlenv.ESQLDataObjectType.dotProcedure,
8883                                moduleName.getObjectToken().toString(), "sp_execute_external_script")) {
8884                        return false;
8885                }
8886                // A server or database qualifier changes (or makes remote) the context in
8887                // which the input query resolves; only handle the local, unqualified call.
8888                if (moduleName.getServerToken() != null || moduleName.getDatabaseToken() != null) {
8889                        return false;
8890                }
8891                TSourceToken schemaTok = moduleName.getSchemaToken();
8892                if (schemaTok == null) {
8893                        return true;
8894                }
8895                return SQLUtil.sameName(vendor, gudusoft.gsqlparser.sqlenv.ESQLDataObjectType.dotSchema,
8896                                schemaTok.toString(), "sys");
8897        }
8898
8899        /**
8900         * Recover lineage for the query passed to sp_execute_external_script via its
8901         * input dataset (MantisBT #4606). The input value is a SQL string literal whose
8902         * SELECT reads real tables; analyze it as a normal statement so its
8903         * source-table -&gt; result-set lineage is emitted.
8904         *
8905         * <p>The input dataset is the {@code @input_data_1} parameter (named form) or,
8906         * for a positional call, the third argument — the documented signature is
8907         * {@code (@language, @script, @input_data_1, ...)}. Only that one parameter
8908         * carries a query; its siblings {@code @input_data_1_name},
8909         * {@code @input_data_1_order_by_columns} and
8910         * {@code @input_data_1_partition_by_columns} are identifiers / column lists,
8911         * NOT queries — analyzing them would attach lineage to the wrong objects, so
8912         * named matching is exact and positional matching is restricted to the third
8913         * slot.
8914         *
8915         * <p>Only a plain string literal is materialized. A variable-valued argument is
8916         * left unanalyzed: the parse-time symbol table is not available in this pass,
8917         * so its value cannot be statically resolved here (the same unresolved-value
8918         * limitation as the general dynamic-SQL case).
8919         *
8920         * <p>The script body is opaque (arbitrary Python/R can reorder, drop, or
8921         * synthesize columns), so by default the input query's result set is NOT
8922         * connected to the {@code WITH RESULT SETS} output columns — that mapping is
8923         * unprovable. Only when {@link Option#isAssumeExternalScriptPassthrough()} is
8924         * explicitly enabled does {@link #buildExternalScriptPassthrough} add that
8925         * connection, tagged {@link EffectType#external_script_passthrough} to keep it
8926         * distinguishable from proven lineage.
8927         */
8928        private void analyzeExternalScriptInputData(TMssqlExecute executeStmt) {
8929                TExecParameterList params = executeStmt.getParameters();
8930                if (params == null) {
8931                        return;
8932                }
8933                // T-SQL allows a positional prefix followed by named arguments; once an
8934                // argument is named, every later one must be named too. So a positional
8935                // argument is only valid while no NAMED argument has appeared before it.
8936                // The documented signature is (@language, @script, @input_data_1, ...), so
8937                // the third positional argument (index 2) is @input_data_1.
8938                boolean namedBefore = false;
8939                for (int i = 0; i < params.size(); i++) {
8940                        TExecParameter param = params.getExecParameter(i);
8941                        TObjectName pname = param.getParameterName();
8942                        boolean isInputData1;
8943                        if (pname != null) {
8944                                String nm = pname.toString();
8945                                if (nm.startsWith("@")) {
8946                                        nm = nm.substring(1);
8947                                }
8948                                isInputData1 = "input_data_1".equalsIgnoreCase(nm.trim()); // non-identifier-compare: T-SQL system-proc parameter name
8949                                namedBefore = true;
8950                        } else {
8951                                isInputData1 = !namedBefore && i == 2;
8952                        }
8953                        if (!isInputData1 || param.getParameterValue() == null) {
8954                                continue;
8955                        }
8956                        String sql = materializeTSqlStringLiteral(param.getParameterValue().toString());
8957                        if (sql != null && sql.trim().length() > 0) {
8958                                ResultSet inputResultSet = analyzeExternalScriptInputQuery(sql);
8959                                // Opt-in only: bridge the opaque script by assuming the input dataset
8960                                // is passed through to the WITH RESULT SETS output columns positionally
8961                                // (EffectType.external_script_passthrough). Off by default because the
8962                                // script may reorder/drop/add columns, so these edges are assumed, not
8963                                // proven.
8964                                if (option.isAssumeExternalScriptPassthrough() && inputResultSet != null) {
8965                                        buildExternalScriptPassthrough(executeStmt, inputResultSet);
8966                                }
8967                        }
8968                        // Only @input_data_1 carries a query; nothing else to look at.
8969                        return;
8970                }
8971        }
8972
8973        /**
8974         * Parse and analyze an sp_execute_external_script input query, returning the
8975         * ResultSet model of its (last) top-level SELECT, or null. Behaves like
8976         * {@link #executeDynamicSql(String)} but hands back the result set so the
8977         * optional passthrough model can wire it to the WITH RESULT SETS output.
8978         */
8979        private ResultSet analyzeExternalScriptInputQuery(String sql) {
8980                TGSqlParser sqlparser = createSqlParser(option.getVendor());
8981                sqlparser.sqltext = sql;
8982                if (parseWithCommentBindings(sqlparser) != 0) {
8983                        return null;
8984                }
8985                ResultSet resultSet = null;
8986                // Dynamic inner analysis: its references are NOT static statements of the unit
8987                // and must not veto dynamic-template marks (see dynamicFoldDepth).
8988                dynamicFoldDepth++;
8989                try {
8990                        for (int i = 0; i < sqlparser.sqlstatements.size(); i++) {
8991                                TCustomSqlStatement stmt = sqlparser.sqlstatements.get(i);
8992                                analyzeCustomSqlStmt(stmt);
8993                                if (stmt instanceof TSelectSqlStatement) {
8994                                        Object model = modelManager.getModel(stmt);
8995                                        if (model instanceof ResultSet) {
8996                                                resultSet = (ResultSet) model;
8997                                        }
8998                                }
8999                        }
9000                } finally {
9001                        dynamicFoldDepth--;
9002                }
9003                return resultSet;
9004        }
9005
9006        /**
9007         * Under the opt-in passthrough assumption, wire the external-script input
9008         * dataset to its declared {@code WITH RESULT SETS} output columns positionally,
9009         * emitting {@link EffectType#external_script_passthrough} edges and binding the
9010         * synthesized output ResultSet to the EXEC statement so an
9011         * {@code INSERT ... EXEC} consumer can inherit it (see the INSERT..EXEC path in
9012         * {@link #analyzeInsertStmt}).
9013         */
9014        private void buildExternalScriptPassthrough(TMssqlExecute executeStmt, ResultSet inputResultSet) {
9015                TColumnDefinitionList outputColumns = firstInlineResultSetColumns(executeStmt);
9016                if (outputColumns == null || outputColumns.size() == 0) {
9017                        // No declared output columns (e.g. WITH RESULT SETS NONE / UNDEFINED):
9018                        // there is nothing to pass the input through to.
9019                        return;
9020                }
9021                List<ResultColumn> inputColumns = inputResultSet.getColumns();
9022                // A star projection ("SELECT *") has unknown width, so positional
9023                // correspondence to the declared output cannot be established -- decline
9024                // rather than mis-map a single star column onto the first output column.
9025                for (ResultColumn inputColumn : inputColumns) {
9026                        if (inputColumn != null && inputColumn.isStarColumn()) {
9027                                return;
9028                        }
9029                }
9030                ResultSet outputResultSet = modelFactory.createResultSet(executeStmt, false);
9031                Process process = modelFactory.createProcess(executeStmt);
9032                // Materialize the FULL declared output schema (so every WITH RESULT SETS
9033                // column is available to a downstream INSERT..EXEC), preserving cardinality
9034                // by position via the raw ResultColumn constructor (the name-keyed factory
9035                // overload would collapse duplicate declared names). Passthrough edges are
9036                // added only for positions that have a matching input column.
9037                for (int i = 0; i < outputColumns.size(); i++) {
9038                        TObjectName outputName = outputColumns.getColumn(i).getColumnName();
9039                        if (outputName == null) {
9040                                continue;
9041                        }
9042                        ResultColumn outputColumn = new ResultColumn(outputResultSet, outputName);
9043                        if (i < inputColumns.size() && inputColumns.get(i) != null) {
9044                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
9045                                relation.setEffectType(EffectType.external_script_passthrough);
9046                                relation.setTarget(new ResultColumnRelationshipElement(outputColumn));
9047                                relation.addSource(new ResultColumnRelationshipElement(inputColumns.get(i)));
9048                                relation.setProcess(process);
9049                        }
9050                }
9051        }
9052
9053        /** The column list of the first inline {@code WITH RESULT SETS ((...))} definition of an EXEC, or null. */
9054        private gudusoft.gsqlparser.nodes.TColumnDefinitionList firstInlineResultSetColumns(TMssqlExecute executeStmt) {
9055                gudusoft.gsqlparser.nodes.mssql.TExecuteOption execOption = executeStmt.getExecuteOption();
9056                if (!(execOption instanceof gudusoft.gsqlparser.nodes.mssql.TResultSetsExecuteOption)) {
9057                        return null;
9058                }
9059                for (gudusoft.gsqlparser.nodes.mssql.TResultSetDefinition definition
9060                                : ((gudusoft.gsqlparser.nodes.mssql.TResultSetsExecuteOption) execOption).getDefinitions()) {
9061                        if (definition instanceof gudusoft.gsqlparser.nodes.mssql.TInlineResultSetDefinition) {
9062                                return ((gudusoft.gsqlparser.nodes.mssql.TInlineResultSetDefinition) definition)
9063                                                .getColumnDefinitionList();
9064                        }
9065                }
9066                return null;
9067        }
9068
9069        /**
9070         * Result sets that an {@code INSERT ... EXEC <proc>} can consume: the result
9071         * sets of the procedure's trailing SELECT(s), or — when the procedure has no
9072         * such SELECT — the {@code WITH RESULT SETS} output(s) synthesized for its
9073         * internal sp_execute_external_script call. The latter exist only when the
9074         * passthrough assumption is enabled ({@link Option#isAssumeExternalScriptPassthrough()}),
9075         * so with the option off this reduces to the previous SELECT-only behavior.
9076         */
9077        private List<ResultSet> collectCalleeResultSets(TStoredProcedureSqlStatement procedureStmt) {
9078                List<ResultSet> resultSets = new ArrayList<ResultSet>();
9079                List<TSelectSqlStatement> selects = getLastSelectStmt(procedureStmt);
9080                if (selects != null) {
9081                        for (TSelectSqlStatement select : selects) {
9082                                Object model = modelManager.getModel(select);
9083                                if (model instanceof ResultSet) {
9084                                        resultSets.add((ResultSet) model);
9085                                }
9086                        }
9087                }
9088                // A procedure may return an ordinary SELECT result set AND an external-script
9089                // result set; INSERT..EXEC consumes every returned (compatible) result set, so
9090                // include the passthrough output(s) as well, not only when no SELECT exists.
9091                // externalScriptOutputResultSets is empty unless the passthrough option is on,
9092                // so with the option off this reduces to the previous SELECT-only behavior.
9093                resultSets.addAll(externalScriptOutputResultSets(procedureStmt));
9094                return resultSets;
9095        }
9096
9097        /**
9098         * Result sets an {@code INSERT ... EXEC <target>} consumes. When the target is a
9099         * user stored procedure, this is {@link #collectCalleeResultSets}. When it is a
9100         * DIRECT call to sp_execute_external_script (no wrapping procedure), the
9101         * synthesized WITH RESULT SETS output is bound to the EXEC statement itself, so
9102         * it is fetched directly. Empty unless the passthrough assumption is enabled.
9103         */
9104        private List<ResultSet> calleeResultSetsForInsertExec(TMssqlExecute executeStmt, Procedure procedure) {
9105                if (procedure != null && procedure.getProcedureObject() instanceof TStoredProcedureSqlStatement) {
9106                        return collectCalleeResultSets((TStoredProcedureSqlStatement) procedure.getProcedureObject());
9107                }
9108                if (isSpExecuteExternalScript(executeStmt.getModuleName())) {
9109                        Object model = modelManager.getModel(executeStmt);
9110                        if (model instanceof ResultSet) {
9111                                return java.util.Collections.singletonList((ResultSet) model);
9112                        }
9113                }
9114                return new ArrayList<ResultSet>();
9115        }
9116
9117        /**
9118         * WITH RESULT SETS output result sets bound to sp_execute_external_script calls
9119         * inside a procedure body (the passthrough model binds one to each such EXEC).
9120         * Empty unless the passthrough assumption is enabled.
9121         */
9122        private List<ResultSet> externalScriptOutputResultSets(TStoredProcedureSqlStatement procedureStmt) {
9123                final List<ResultSet> resultSets = new ArrayList<ResultSet>();
9124                procedureStmt.acceptChildren(new TParseTreeVisitor() {
9125                        @Override
9126                        public void preVisit(TMssqlExecute execute) {
9127                                if (isSpExecuteExternalScript(execute.getModuleName())) {
9128                                        Object model = modelManager.getModel(execute);
9129                                        if (model instanceof ResultSet) {
9130                                                resultSets.add((ResultSet) model);
9131                                        }
9132                                }
9133                        }
9134                });
9135                return resultSets;
9136        }
9137
9138        /**
9139         * Materialize a T-SQL string literal: strip the {@code N'...'} / {@code '...'}
9140         * wrapper and unescape doubled single quotes ({@code ''} -&gt; {@code '}).
9141         * Unlike {@link TBaseType#getStringInsideLiteral(String)} this does NOT treat
9142         * {@code \'} as an escape — backslash is an ordinary character in T-SQL, so a
9143         * source such as an {@code OPENROWSET} path must survive unchanged. Returns
9144         * null when the argument is not a plain string literal (e.g. a variable).
9145         */
9146        private static String materializeTSqlStringLiteral(String raw) {
9147                if (raw == null) {
9148                        return null;
9149                }
9150                String s = raw.trim();
9151                int start = -1;
9152                if (s.length() >= 2 && s.charAt(0) == '\'') {
9153                        start = 1;
9154                } else if (s.length() >= 3 && (s.charAt(0) == 'N' || s.charAt(0) == 'n') && s.charAt(1) == '\'') {
9155                        start = 2;
9156                }
9157                if (start < 0 || s.charAt(s.length() - 1) != '\'') {
9158                        return null;
9159                }
9160                return s.substring(start, s.length() - 1).replace("''", "'");
9161        }
9162
9163        /** True when this EXEC form would expand SQL text rather than invoke an
9164         * ordinary statically named stored procedure. */
9165        private boolean expandsDynamicSql(TMssqlExecute e) {
9166                return e.getSqlText() != null
9167                                || e.getExecType() == TBaseType.metExecStringCmd
9168                                || isSpExecutesql(e.getModuleName());
9169        }
9170
9171        /** Kind of a {@link TMssqlExecute} dynamic site: sp_executesql vs the EXEC(string) family. */
9172        private DynamicSqlSite.Kind dynamicKindOf(TMssqlExecute e) {
9173                if (e.getExecType() == TBaseType.metExecStringCmd) {
9174                        return DynamicSqlSite.Kind.EXEC_STRING;
9175                }
9176                if (isSpExecutesql(e.getModuleName())) {
9177                        return DynamicSqlSite.Kind.SP_EXECUTESQL;
9178                }
9179                return DynamicSqlSite.Kind.OTHER;
9180        }
9181
9182        /** The dynamic-SQL argument expression of a {@link TMssqlExecute}, or null. */
9183        private TExpression dynamicArgOf(TMssqlExecute e) {
9184                if (e.getExecType() == TBaseType.metExecStringCmd) {
9185                        if (e.getStringValues() != null && e.getStringValues().size() > 0) {
9186                                return e.getStringValues().getExpression(0);
9187                        }
9188                        return null;
9189                }
9190                if (e.getParameters() != null && e.getParameters().size() > 0) {
9191                        TExecParameter p = e.getParameters().getExecParameter(0);
9192                        if (p != null) {
9193                                return p.getParameterValue();
9194                        }
9195                }
9196                return null;
9197        }
9198
9199        /**
9200         * True only when the dynamic-SQL argument is a compile-time string literal. A variable or
9201         * expression argument is data-driven: even when GSP partially folds it (e.g. {@code @sql} →
9202         * {@code INSERT INTO @t SELECT ...} with an unresolved parameter left in), the site is NOT
9203         * fully resolved, so it must not be reported RESOLVED.
9204         */
9205        private boolean isLiteralDynamicArg(TMssqlExecute e) {
9206                TExpression arg = dynamicArgOf(e);
9207                return arg != null && arg.getExpressionType() == EExpressionType.simple_constant_t;
9208        }
9209
9210        /** Refine the UNRESOLVED reason by inspecting the dynamic argument expression. */
9211        private String dynamicReasonOf(TMssqlExecute e) {
9212                TExpression arg = dynamicArgOf(e);
9213                if (arg == null) {
9214                        return "dynamic SQL target could not be resolved";
9215                }
9216                if (arg.getExpressionType() == EExpressionType.simple_object_name_t && arg.getObjectOperand() != null
9217                                && arg.getObjectOperand().getDbObjectType() == EDbObjectType.variable) {
9218                        return "argument is a runtime variable";
9219                }
9220                if (arg.getExpressionType() == EExpressionType.simple_constant_t) {
9221                        return "inline literal not folded; no lineage produced";
9222                }
9223                return "argument is a runtime expression or partially-folded variable";
9224        }
9225
9226        private void executeDynamicSql(String sql) {
9227                TGSqlParser sqlparser = createSqlParser(option.getVendor());
9228                sqlparser.sqltext = sql;
9229                int result = parseWithCommentBindings(sqlparser);
9230                if (result == 0) {
9231                        dynamicFoldDepth++;
9232                        try {
9233                                for (int i = 0; i < sqlparser.sqlstatements.size(); i++) {
9234                                        analyzeCustomSqlStmt(sqlparser.sqlstatements.get(i));
9235                                }
9236                        } finally {
9237                                dynamicFoldDepth--;
9238                        }
9239                }
9240        }
9241
9242        /**
9243         * Callback invoked from snowflake.js (the script shim run by
9244         * {@link #extractSnowflakeSQLFromProcedure}) for every SQL text a
9245         * JavaScript procedure body passes to snowflake.execute() /
9246         * snowflake.createStatement(). The script resolves this method by name
9247         * at runtime, so it must stay public and keep this exact signature —
9248         * renaming it to executeDynamicSql broke JavaScript-procedure lineage
9249         * with "analyzer.extractSnowflakeSQL is not a function" (Mantis 4590).
9250         */
9251        public void extractSnowflakeSQL(String sql) {
9252                executeDynamicSql(sql);
9253        }
9254
9255        private void extractSnowflakeSQLFromProcedure(TCreateProcedureStmt procedure) {
9256                Map<String, String> argMap = new LinkedHashMap<String, String>();
9257                if (procedure.getParameterDeclarations() != null) {
9258                        for (int i = 0; i < procedure.getParameterDeclarations().size(); i++) {
9259                                TParameterDeclaration def = procedure.getParameterDeclarations().getParameterDeclarationItem(i);
9260                                argMap.put(def.getParameterName().toString(), def.getDataType().getDataTypeName());
9261                        }
9262                }
9263                StringBuilder buffer = new StringBuilder();
9264                buffer.append("(function(");
9265                String[] args = argMap.keySet().toArray(new String[0]);
9266                for (int i = 0; i < args.length; i++) {
9267                        buffer.append(args[i].toUpperCase(Locale.ROOT));
9268                        if (i < args.length - 1) {
9269                                buffer.append(",");
9270                        }
9271                }
9272                buffer.append("){\n");
9273
9274                int start = -1;
9275                int end = -1;
9276                boolean dollar = false;
9277                boolean quote = false;
9278                if (procedure.getRoutineBody().indexOf("$$") != -1) {
9279                        start = procedure.getRoutineBody().indexOf("$$") + 2;
9280                        end = procedure.getRoutineBody().lastIndexOf("$$") - 1;
9281                        dollar = true;
9282                } else if (procedure.getRoutineBody().indexOf("'") != -1) {
9283                        start = procedure.getRoutineBody().indexOf("'") + 1;
9284                        end = procedure.getRoutineBody().lastIndexOf("'");
9285                        quote = true;
9286                }
9287                String body = procedure.getRoutineBody().substring(start, end);
9288                if (dollar && body.indexOf("`") != -1) {
9289                        Pattern pattern = Pattern.compile("`.+?`", Pattern.CASE_INSENSITIVE | Pattern.DOTALL);
9290                        Matcher matcher = pattern.matcher(body);
9291                        StringBuffer replaceBuffer = new StringBuffer();
9292                        while (matcher.find()) {
9293                                String condition = matcher.group().replace("\r\n", "\n").replace("'", "\\\\'")
9294                                                .replace("\n", "\\\\n'\n+'").replace("`", "'").replace("$", "RDS_CHAR_DOLLAR");
9295                                matcher.appendReplacement(replaceBuffer, condition);
9296                        }
9297                        matcher.appendTail(replaceBuffer);
9298                        body = replaceBuffer.toString().replace("RDS_CHAR_DOLLAR", "$");
9299                }
9300                if (quote && body.indexOf("'") != -1) {
9301                        body = body.replace("''", "'");
9302                }
9303                buffer.append(body);
9304                buffer.append("})(");
9305                for (int i = 0; i < args.length; i++) {
9306                        String type = argMap.get(args[i]);
9307                        if (type.equalsIgnoreCase("VARCHAR")) {
9308                                buffer.append("'pseudo'");
9309                        } else if (type.equalsIgnoreCase("STRING")) {
9310                                buffer.append("'pseudo'");
9311                        } else if (type.equalsIgnoreCase("CHAR")) {
9312                                buffer.append("'pseudo'");
9313                        } else if (type.equalsIgnoreCase("CHARACTER")) {
9314                                buffer.append("'pseudo'");
9315                        } else if (type.equalsIgnoreCase("TEXT")) {
9316                                buffer.append("'pseudo'");
9317                        } else if (type.equalsIgnoreCase("BINARY")) {
9318                                buffer.append("'pseudo'");
9319                        } else if (type.equalsIgnoreCase("VARBINARY")) {
9320                                buffer.append("'pseudo'");
9321                        } else if (type.equalsIgnoreCase("BOOLEAN")) {
9322                                buffer.append(true);
9323                        } else if (type.equalsIgnoreCase("FLOAT")) {
9324                                buffer.append("1.0");
9325                        } else if (type.equalsIgnoreCase("FLOAT4")) {
9326                                buffer.append("1.0");
9327                        } else if (type.equalsIgnoreCase("FLOAT8")) {
9328                                buffer.append("1.0");
9329                        } else if (type.equalsIgnoreCase("DOUBLE")) {
9330                                buffer.append("1.0");
9331                        } else if (type.equalsIgnoreCase("DOUBLE PRECISION")) {
9332                                buffer.append("1.0");
9333                        } else if (type.equalsIgnoreCase("REAL")) {
9334                                buffer.append("1.0");
9335                        } else if (type.equalsIgnoreCase("NUMBER")) {
9336                                buffer.append("1.0");
9337                        } else if (type.equalsIgnoreCase("DECIMAL")) {
9338                                buffer.append("1.0");
9339                        } else if (type.equalsIgnoreCase("NUMERIC")) {
9340                                buffer.append("1.0");
9341                        } else if (type.equalsIgnoreCase("INT")) {
9342                                buffer.append("1");
9343                        } else if (type.equalsIgnoreCase("INTEGER")) {
9344                                buffer.append("1");
9345                        } else if (type.equalsIgnoreCase("BIGINT")) {
9346                                buffer.append("1");
9347                        } else if (type.equalsIgnoreCase("SMALLINT")) {
9348                                buffer.append("1");
9349                        } else if (type.equalsIgnoreCase("DATE")) {
9350                                buffer.append("new Date()");
9351                        } else if (type.equalsIgnoreCase("DATETIME")) {
9352                                buffer.append("new Date()");
9353                        } else if (type.equalsIgnoreCase("TIME")) {
9354                                buffer.append("new Date()");
9355                        } else if (type.equalsIgnoreCase("TIMESTAMP")) {
9356                                buffer.append("new Date()");
9357                        } else if (type.equalsIgnoreCase("TIMESTAMP_LTZ")) {
9358                                buffer.append("new Date()");
9359                        } else if (type.equalsIgnoreCase("TIMESTAMP_NTZ")) {
9360                                buffer.append("new Date()");
9361                        } else if (type.equalsIgnoreCase("TIMESTAMP_TZ")) {
9362                                buffer.append("new Date()");
9363                        } else if (type.equalsIgnoreCase("VARIANT")) {
9364                                buffer.append("{}");
9365                        } else if (type.equalsIgnoreCase("OBJECT")) {
9366                                buffer.append("{}");
9367                        } else if (type.equalsIgnoreCase("ARRAY")) {
9368                                buffer.append("[]");
9369                        } else if (type.equalsIgnoreCase("GEOGRAPHY")) {
9370                                buffer.append("{}");
9371                        }
9372                        if (i < args.length - 1) {
9373                                buffer.append(",");
9374                        }
9375                }
9376                buffer.append(");");
9377
9378                try {
9379                        ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
9380                        ScriptEngine nashorn = scriptEngineManager.getEngineByName("nashorn");
9381                        if (nashorn == null) {
9382                                // The Nashorn engine was removed from the JDK in Java 15, so the
9383                                // JavaScript body cannot be executed to extract the SQL it runs.
9384                                // Try the body as plain SQL; otherwise keep the procedure model
9385                                // (with its declared language) and skip the body (Mantis 4590).
9386                                TGSqlParser sqlparser = createSqlParser(option.getVendor());
9387                                sqlparser.sqltext = body;
9388                                if (parseWithCommentBindings(sqlparser) == 0) {
9389                                        dynamicFoldDepth++;
9390                                        try {
9391                                                for (int i = 0; i < sqlparser.sqlstatements.size(); i++) {
9392                                                        analyzeCustomSqlStmt(sqlparser.sqlstatements.get(i));
9393                                                }
9394                                        } finally {
9395                                                dynamicFoldDepth--;
9396                                        }
9397                                }
9398                                return;
9399                        }
9400                        nashorn.put("analyzer", this);
9401                        nashorn.eval(new InputStreamReader(
9402                                        getClass().getResourceAsStream("/gudusoft/gsqlparser/parser/snowflake/snowflake.js")));
9403                        nashorn.eval(new StringReader(buffer.toString()));
9404                } catch (ScriptException e) {
9405                        TGSqlParser sqlparser = createSqlParser(option.getVendor());
9406                        sqlparser.sqltext = body;
9407                        int result = parseWithCommentBindings(sqlparser);
9408                        if (result == 0) {
9409                                dynamicFoldDepth++;
9410                                try {
9411                                        for (int i = 0; i < sqlparser.sqlstatements.size(); i++) {
9412                                                analyzeCustomSqlStmt(sqlparser.sqlstatements.get(i));
9413                                        }
9414                                } finally {
9415                                        dynamicFoldDepth--;
9416                                }
9417                                return;
9418                        }
9419                        ErrorInfo errorInfo = new ErrorInfo();
9420                        errorInfo.setErrorType(ErrorInfo.ANALYZE_ERROR);
9421                        errorInfo.setErrorMessage("Invoke script error: " + e.getMessage());
9422                        errorInfo.setStartPosition(new Pair3<Long, Long, String>(procedure.getStartToken().lineNo,
9423                                        procedure.getStartToken().columnNo, ModelBindingManager.getGlobalHash()));
9424                        errorInfo.setEndPosition(new Pair3<Long, Long, String>(procedure.getEndToken().lineNo,
9425                                        procedure.getEndToken().columnNo + procedure.getEndToken().getAstext().length(),
9426                                        ModelBindingManager.getGlobalHash()));
9427                        errorInfos.add(errorInfo);
9428                }
9429        }
9430
9431        private void analyzeHiveLoadStmt(THiveLoad stmt) {
9432                if (stmt.getPath() != null && stmt.getTable() != null) {
9433                        Table uriFile = modelFactory.createTableByName(stmt.getPath(), true);
9434                        uriFile.setPath(true);
9435                        uriFile.setCreateTable(true);
9436                        TObjectName fileUri = new TObjectName();
9437                        fileUri.setString("uri=" + stmt.getPath());
9438                        TableColumn fileUriColumn = modelFactory.createFileUri(uriFile, fileUri);
9439
9440                        Table tableModel = modelFactory.createTable(stmt.getTable());
9441                        Process process = modelFactory.createProcess(stmt);
9442                        tableModel.addProcess(process);
9443
9444                        TPartitionExtensionClause p = stmt.getTable().getPartitionExtensionClause();
9445                        if (p.getKeyValues() != null && p.getKeyValues().size() > 0) {
9446                                for (int i = 0; i < p.getKeyValues().size(); i++) {
9447                                        TExpression expression = p.getKeyValues().getExpression(i);
9448                                        if (expression.getLeftOperand().getExpressionType() == EExpressionType.simple_object_name_t) {
9449                                                modelFactory.createTableColumn(tableModel, expression.getLeftOperand().getObjectOperand(),
9450                                                                true);
9451                                        }
9452                                }
9453                        }
9454
9455                        for (int j = 0; j < tableModel.getColumns().size(); j++) {
9456                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
9457                                relation.addSource(new TableColumnRelationshipElement(fileUriColumn));
9458                                relation.setTarget(new TableColumnRelationshipElement(tableModel.getColumns().get(j)));
9459                                relation.setProcess(process);
9460                        }
9461                }
9462        }
9463        
9464        /**
9465         * MySQL {@code LOAD DATA INFILE 'file' INTO TABLE t [(col, @var, ...)] [SET col = expr, ...]} (Mantis 4963). The
9466         * file is a path node, as for BULK INSERT. With a column list its fields reach the columns that hold them
9467         * ({@link TLoadDataStmt#getLoadedColumnList()}); without one they reach the table as a whole, its load column
9468         * {@code *}, because the fields map to the columns by position.
9469         */
9470        private void analyzeLoadDataStmt(TLoadDataStmt stmt) {
9471                TObjectName tableName = stmt.getTableName();
9472                if (tableName == null || stmt.getFileName() == null) {
9473                        return;
9474                }
9475                String literal = stmt.getFileName().toString().trim();
9476                if (stmt.getFileName().tokentype == ETokenType.ttdqstring && literal.length() >= 2) {
9477                        // INFILE "/data/x.csv" names the file '/data/x.csv' names: one path node, by one spelling
9478                        literal = "'" + literal.substring(1, literal.length() - 1).replace("'", "''") + "'";
9479                }
9480                if (SQLUtil.isEmpty(stringLiteralValue(literal))) {
9481                        return;
9482                }
9483                Table intoTable = modelFactory.createTableByName(tableName, false);
9484                if (intoTable == null) {
9485                        return;
9486                }
9487                List<TableColumn> targets = new ArrayList<TableColumn>();
9488                if (stmt.getColumnList() == null) {
9489                        TableColumn star = intoTable.getLoadColumn();
9490                        if (star == null) {
9491                                star = new TableColumn(intoTable, DlineageUtil.syntheticStarName());
9492                                star.setExpandStar(false);
9493                                star.setPseduo(true);
9494                                intoTable.setLoadColumn(star);
9495                        }
9496                        targets.add(star);
9497                } else {
9498                        for (TObjectName column : stmt.getLoadedColumnList()) {
9499                                targets.add(modelFactory.createTableColumn(intoTable, column, true));
9500                        }
9501                }
9502                Process process = modelFactory.createProcess(stmt);
9503                intoTable.addProcess(process);
9504
9505                Table pathModel = modelFactory.createTableByName(literal, true);
9506                // a file is no object of the database a USE names
9507                pathModel.setPath(true, false);
9508                pathModel.setCreateTable(true);
9509                TObjectName fileUri = new TObjectName();
9510                fileUri.setString(literal);
9511                TableColumn fileUriColumn = modelFactory.createFileUri(pathModel, fileUri);
9512
9513                for (TableColumn target : targets) {
9514                        if (target == null) {
9515                                continue;
9516                        }
9517                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
9518                        relation.addSource(new TableColumnRelationshipElement(fileUriColumn));
9519                        relation.setTarget(new TableColumnRelationshipElement(target));
9520                        relation.setProcess(process);
9521                }
9522        }
9523
9524        private void analyzeUnloadStmt(TUnloadStmt unloadStmt) {
9525                if (unloadStmt.getSelectSqlStatement() != null && unloadStmt.getS3() != null) {
9526
9527                        Table uriFile = modelFactory.createTableByName(unloadStmt.getS3(), true);
9528                        uriFile.setPath(true);
9529                        uriFile.setCreateTable(true);
9530                        TObjectName fileUri = new TObjectName();
9531                        fileUri.setString("uri=" + unloadStmt.getS3());
9532                        TableColumn fileUriColumn = modelFactory.createFileUri(uriFile, fileUri);
9533
9534                        Process process = modelFactory.createProcess(unloadStmt);
9535                        uriFile.addProcess(process);
9536
9537                        TCustomSqlStatement stmt = unloadStmt.getSelectSqlStatement();
9538                        analyzeCustomSqlStmt(stmt);
9539                        if (stmt instanceof TSelectSqlStatement) {
9540                                TSelectSqlStatement select = (TSelectSqlStatement) stmt;
9541                                ResultSet resultSetModel = (ResultSet) modelManager.getModel(select);
9542                                if (resultSetModel != null) {
9543                                        for (int j = 0; j < resultSetModel.getColumns().size(); j++) {
9544                                                ResultColumn resultColumn = resultSetModel.getColumns().get(j);
9545                                                if (resultColumn.hasStarLinkColumn()
9546                                                                && resultColumn.starLinkCount() > 0) {
9547                                                        for (int k = 0; k < resultColumn.starLinkCount(); k++) {
9548                                                                ResultColumn expandStarColumn = modelFactory.createResultColumn(resultSetModel,
9549                                                                                resultColumn.getStarLinkColumnName(k), false);
9550                                                                DataFlowRelationship dataflowRelation = modelFactory.createDataFlowRelation();
9551                                                                dataflowRelation.setEffectType(EffectType.unload);
9552                                                                dataflowRelation
9553                                                                                .addSource(new ResultColumnRelationshipElement(expandStarColumn));
9554                                                                dataflowRelation.setTarget(new TableColumnRelationshipElement(fileUriColumn));
9555                                                                dataflowRelation.setProcess(process);
9556                                                        }
9557                                                }
9558                                                if (!resultColumn.hasStarLinkColumn() || resultColumn.isShowStar()) {
9559                                                        DataFlowRelationship dataflowRelation = modelFactory.createDataFlowRelation();
9560                                                        dataflowRelation.setEffectType(EffectType.unload);
9561                                                        dataflowRelation.addSource(new ResultColumnRelationshipElement(resultColumn));
9562                                                        dataflowRelation.setTarget(new TableColumnRelationshipElement(fileUriColumn));
9563                                                        dataflowRelation.setProcess(process);
9564                                                }
9565                                        }
9566                                }
9567                        }
9568                }
9569
9570        }
9571
9572        private void analyzeCopyIntoStmt(TSnowflakeCopyIntoStmt stmt) {
9573                if (stmt.getTableName() != null) {
9574                        if (stmt.getStageLocation() != null) {
9575                                Table intoTable = modelManager
9576                                                .getTableByName(DlineageUtil.getTableIdentityKey(stmt.getTableName().toString()));
9577                                if (intoTable == null) {
9578                                        intoTable = modelFactory.createTableByName(stmt.getTableName(), false);
9579                                }
9580                                // An explicit COPY column list selects only those destination columns,
9581                                // even when a previous DDL statement supplied the complete table schema.
9582                                List<TableColumn> copyColumns = new ArrayList<TableColumn>();
9583                                if (stmt.getSubQuery() == null && stmt.getTableColumnList() != null) {
9584                                        for (int i = 0; i < stmt.getTableColumnList().size(); i++) {
9585                                                TableColumn column = modelFactory.createTableColumn(intoTable,
9586                                                                stmt.getTableColumnList().getObjectName(i), false);
9587                                                if (column != null) copyColumns.add(column);
9588                                        }
9589                                } else {
9590                                        if (intoTable.getColumns().isEmpty()) {
9591                                                TObjectName starColumn = DlineageUtil.syntheticStarName();
9592                                                TableColumn column = modelFactory.createTableColumn(intoTable, starColumn, false);
9593                                                if (column != null) {
9594                                                        column.setExpandStar(false);
9595                                                        column.setPseduo(true);
9596                                                }
9597                                        }
9598                                        copyColumns.addAll(intoTable.getColumns());
9599                                }
9600                                Process process = modelFactory.createProcess(stmt);
9601                                intoTable.addProcess(process);
9602
9603                                TObjectName stageName = stmt.getStageLocation().getStageName();
9604                                if (stageName == null || stmt.getStageLocation().getTableName() != null) {
9605                                        stageName = stmt.getStageLocation().getTableName();
9606                                }
9607                                if (stageName != null) {
9608                                        Table stage = modelManager.getTableByName(DlineageUtil.getTableIdentityKey(stageName.toString()));
9609                                        if (stage == null) {
9610                                                stage = modelFactory.createStage(stageName);
9611                                                stage.setCreateTable(true);
9612                                                stage.setStage(true);
9613                                                String stagePath = stmt.getStageLocation().getPath() == null ? null
9614                                                                : stmt.getStageLocation().getPath().toString();
9615                                                if (stagePath != null) {
9616                                                        stage.setLocation(stagePath);
9617                                                        TObjectName location = new TObjectName();
9618                                                        location.setString(stagePath);
9619                                                        modelFactory.createStageLocation(stage, location);
9620                                                } else {
9621                                                        stage.setLocation("unknownPath");
9622                                                        TObjectName location = new TObjectName();
9623                                                        location.setString("unknownPath");
9624                                                        modelFactory.createStageLocation(stage, location);
9625                                                }
9626                                        }
9627
9628                                        if (stage != null && intoTable != null) {
9629                                                if (intoTable != null && !copyColumns.isEmpty() && !stage.getColumns().isEmpty()) {
9630                                                        for (int i = 0; i < copyColumns.size(); i++) {
9631                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
9632                                                                relation.addSource(new TableColumnRelationshipElement(stage.getColumns().get(0)));
9633                                                                relation.setTarget(new TableColumnRelationshipElement(copyColumns.get(i)));
9634                                                                relation.setProcess(process);
9635                                                        }
9636                                                }
9637                                        }
9638                                } else if (stmt.getStageLocation().getExternalLocation() != null) {
9639                                        Table pathModel = modelFactory.createTableByName(stmt.getStageLocation().getExternalLocation(),
9640                                                        true);
9641                                        pathModel.setPath(true);
9642                                        pathModel.setCreateTable(true);
9643                                        TableColumn fileUriColumn = modelFactory.createFileUri(pathModel,
9644                                                        stmt.getStageLocation().getExternalLocation());
9645                                        if (intoTable != null) {
9646                                                if (intoTable != null && !copyColumns.isEmpty()) {
9647                                                        for (int i = 0; i < copyColumns.size(); i++) {
9648                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
9649                                                                relation.addSource(new TableColumnRelationshipElement(fileUriColumn));
9650                                                                relation.setTarget(new TableColumnRelationshipElement(copyColumns.get(i)));
9651                                                                relation.setProcess(process);
9652                                                        }
9653                                                }
9654                                        }
9655                                }
9656                        }
9657                        else if (stmt.getSubQuery() != null) {
9658                                analyzeSelectStmt(stmt.getSubQuery());
9659                                
9660                                Table intoTable = modelManager
9661                                                .getTableByName(DlineageUtil.getTableIdentityKey(stmt.getTableName().toString()));
9662                                if (intoTable == null) {
9663                                        intoTable = modelFactory.createTableByName(stmt.getTableName(), false);
9664                                        TObjectName starColumn = DlineageUtil.syntheticStarName();
9665                                        TableColumn column = modelFactory.createTableColumn(intoTable, starColumn, false);
9666                                        if (column != null) {
9667                                                column.setExpandStar(true);
9668                                                column.setPseduo(true);
9669                                        }
9670
9671                                        Process process = modelFactory.createProcess(stmt);
9672                                        intoTable.addProcess(process);
9673
9674                                        ResultSet resultSetModel = (ResultSet) modelManager.getModel(stmt.getSubQuery());
9675                                        if (resultSetModel != null && column != null) {
9676                                                for (int i = 0; i < resultSetModel.getColumns().size(); i++) {
9677                                                        ResultColumn resultColumn = resultSetModel.getColumns().get(i);
9678                                                        DataFlowRelationship dataflowRelation = modelFactory.createDataFlowRelation();
9679                                                        dataflowRelation.setEffectType(EffectType.copy);
9680                                                        dataflowRelation.addSource(new ResultColumnRelationshipElement(resultColumn));
9681                                                        dataflowRelation.setTarget(new TableColumnRelationshipElement(column));
9682                                                        dataflowRelation.setProcess(process);
9683                                                }
9684                                        }
9685                                }
9686                        }
9687                }
9688        }
9689
9690        private void analyzeRedshiftCopyStmt(TRedshiftCopy stmt) {
9691                if (stmt.getTableName() != null && stmt.getFromSource() != null) {
9692
9693                        Table intoTable = modelManager
9694                                        .getTableByName(DlineageUtil.getTableIdentityKey(stmt.getTableName().toString()));
9695                        if (intoTable == null) {
9696                                intoTable = modelFactory.createTableByName(stmt.getTableName(), false);
9697                                if (stmt.getColumnList() == null || stmt.getColumnList().size() == 0) {
9698                                        TObjectName starColumn = DlineageUtil.syntheticStarName();
9699                                        TableColumn column = modelFactory.createTableColumn(intoTable, starColumn, false);
9700                                        if (column != null) {
9701                                                column.setExpandStar(false);
9702                                                column.setPseduo(true);
9703                                        }
9704                                } else {
9705                                        for (TObjectName columnName : stmt.getColumnList()) {
9706                                                modelFactory.createTableColumn(intoTable, columnName, true);
9707                                        }
9708                                }
9709                        }
9710                        Process process = modelFactory.createProcess(stmt);
9711                        intoTable.addProcess(process);
9712
9713                        if (stmt.getFromSource() != null) {
9714                                Table pathModel = modelFactory.createTableByName(stmt.getFromSource(), true);
9715                                pathModel.setPath(true);
9716                                pathModel.setCreateTable(true);
9717                                TObjectName fileUri = new TObjectName();
9718                                fileUri.setString(stmt.getFromSource());
9719                                TableColumn fileUriColumn = modelFactory.createFileUri(pathModel, fileUri);
9720                                if (intoTable != null) {
9721                                        if (intoTable != null && !intoTable.getColumns().isEmpty()) {
9722                                                for (int i = 0; i < intoTable.getColumns().size(); i++) {
9723                                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
9724                                                        relation.addSource(new TableColumnRelationshipElement(fileUriColumn));
9725                                                        relation.setTarget(new TableColumnRelationshipElement(intoTable.getColumns().get(i)));
9726                                                        relation.setProcess(process);
9727                                                }
9728                                        }
9729                                }
9730                        }
9731                }
9732        }
9733
9734        /**
9735         * SQL Server {@code BULK INSERT <table> FROM '<file>' [WITH (...)]} (Mantis 4962). The file is a path node and its
9736         * rows reach the table as a whole: one fdd edge from the path's URI to the table's {@code *}, which is never
9737         * expanded, not even when the table's columns are known, because the statement maps file fields to columns by
9738         * position or through a format file that is not read. That {@code *} is the table's load column
9739         * ({@link Table#getLoadColumn()}), no column of the table, so the load changes no other statement's lineage.
9740         * {@code DATA_SOURCE} names the external data source the path is relative to; it is kept on the path node, and
9741         * when this analysis met an external data source of that name its LOCATION is joined to the path for the URI;
9742         * otherwise the URI stays the path as written and an analysis error says the data source was not found.
9743         */
9744        private void analyzeMssqlBulkInsertStmt(TMssqlBulkInsert stmt) {
9745                TObjectName tableName = stmt.getTableName();
9746                TConstant datafile = stmt.getDatafile();
9747                if (tableName == null || datafile == null || datafile.toString() == null) {
9748                        return;
9749                }
9750                String literal = datafile.toString().trim();
9751                if (literal.length() > 1 && (literal.charAt(0) == 'N' || literal.charAt(0) == 'n') && literal.charAt(1) == '\'') {
9752                        literal = literal.substring(1);
9753                }
9754                String file = stringLiteralValue(literal);
9755                if (SQLUtil.isEmpty(file)) {
9756                        return;
9757                }
9758                Table intoTable = modelFactory.createTableByName(tableName, false);
9759                if (intoTable == null) {
9760                        return;
9761                }
9762                TableColumn star = intoTable.getLoadColumn();
9763                if (star == null) {
9764                        star = new TableColumn(intoTable, DlineageUtil.syntheticStarName());
9765                        star.setExpandStar(false);
9766                        star.setPseduo(true);
9767                        intoTable.setLoadColumn(star);
9768                }
9769                Process process = modelFactory.createProcess(stmt);
9770                intoTable.addProcess(process);
9771
9772                String dataSource = stmt.getOptionValue("DATA_SOURCE");
9773                String location = SQLUtil.isEmpty(dataSource) ? null : externalDataSourceLocation(dataSource);
9774                String uri = location == null ? null : "'" + joinLocation(location, file) + "'";
9775                boolean template = templatedLoadFiles.containsKey(datafile);
9776                Table pathModel;
9777                if (SQLUtil.isEmpty(dataSource) && !template) {
9778                        pathModel = modelFactory.createTableByName(literal, true);
9779                } else {
9780                        // the file's identity: the URI its data source resolves it to, else its text under the data source named
9781                        // (or under none); a template's text is never a static file of the same text
9782                        pathModel = modelFactory.createPathModel(literal, Arrays.<Object>asList("BULK INSERT file", template,
9783                                        uri != null ? uri : file, uri != null || SQLUtil.isEmpty(dataSource) ? null : dataSourceKey(dataSource)));
9784                }
9785                // a file is no object of the database a USE names
9786                pathModel.setPath(true, false);
9787                pathModel.setCreateTable(true);
9788                if (template) {
9789                        pathModel.markDynamicTemplate("name", true);
9790                        anyDynamicTemplateMarked = true;
9791                }
9792                if (!SQLUtil.isEmpty(dataSource)) {
9793                        pathModel.setDataSourceName(dataSource);
9794                        if (uri != null) {
9795                                pathModel.setPathUri(uri);
9796                        } else {
9797                                ErrorInfo errorInfo = new ErrorInfo();
9798                                errorInfo.setErrorType(ErrorInfo.ANALYZE_ERROR);
9799                                errorInfo.setErrorMessage("external data source " + dataSource
9800                                                + " not found; the BULK INSERT path " + file + " is relative to its LOCATION");
9801                                errorInfo.setStartPosition(new Pair3<Long, Long, String>(stmt.getStartToken().lineNo,
9802                                                stmt.getStartToken().columnNo, ModelBindingManager.getGlobalHash()));
9803                                errorInfo.setEndPosition(new Pair3<Long, Long, String>(stmt.getEndToken().lineNo,
9804                                                stmt.getEndToken().columnNo + stmt.getEndToken().getAstext().length(),
9805                                                ModelBindingManager.getGlobalHash()));
9806                                errorInfo.fillInfo(this);
9807                                errorInfos.add(errorInfo);
9808                        }
9809                }
9810                TObjectName fileUri = new TObjectName();
9811                fileUri.setString(literal);
9812                TableColumn fileUriColumn = modelFactory.createFileUri(pathModel, fileUri);
9813
9814                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
9815                relation.addSource(new TableColumnRelationshipElement(fileUriColumn));
9816                relation.setTarget(new TableColumnRelationshipElement(star));
9817                relation.setProcess(process);
9818        }
9819
9820        /**
9821         * The identity key an external data source named {@code name} is registered by
9822         * ({@code ModelFactory.createDataSource}), so the defaults of a USE apply alike. {@code name} is the value of the
9823         * {@code DATA_SOURCE} string, one stored name: a dot in it qualifies nothing.
9824         */
9825        private Object dataSourceKey(String name) {
9826                IdentifierProfile profile = IdentifierProfile.forVendor(option.getVendor(), IdentifierProfile.VendorFlags.defaults());
9827                return DlineageUtil.getTableIdentityKey(IdentifierCodec.encodeStored(profile, ESQLDataObjectType.dotTable, name));
9828        }
9829
9830        /**
9831         * The LOCATION of the external data source of this analysis named {@code name} ({@link #dataSourceKey}), the
9832         * value of its string literal, or null when none was created.
9833         */
9834        private String externalDataSourceLocation(String name) {
9835                Object found = modelManager.getTableByName(dataSourceKey(name));
9836                if (!(found instanceof Table) || !((Table) found).isDataSource() || ((Table) found).getLocation() == null) {
9837                        return null;
9838                }
9839                String location = ((Table) found).getLocation().trim();
9840                String value = stringLiteralValue(location.length() > 1 && (location.charAt(0) == 'N' || location.charAt(0) == 'n')
9841                                && location.charAt(1) == '\'' ? location.substring(1) : location);
9842                return value != null ? value : location;
9843        }
9844
9845        /** The value of a T-SQL string literal {@code '...'}, a doubled quote standing for one; null for any other text. */
9846        private static String stringLiteralValue(String literal) {
9847                if (literal == null || literal.length() < 2 || literal.charAt(0) != '\''
9848                                || literal.charAt(literal.length() - 1) != '\'') {
9849                        return null;
9850                }
9851                return literal.substring(1, literal.length() - 1).replace("''", "'");
9852        }
9853
9854        /** {@code location} and {@code path} joined by exactly one '/'. */
9855        private static String joinLocation(String location, String path) {
9856                boolean slashBefore = location.endsWith("/") || location.endsWith("\\");
9857                boolean slashAfter = path.startsWith("/") || path.startsWith("\\");
9858                if (slashBefore && slashAfter) {
9859                        return location + path.substring(1);
9860                }
9861                if (slashBefore || slashAfter) {
9862                        return location + path;
9863                }
9864                return location + "/" + path;
9865        }
9866
9867        private void analyzeCreateIndexExpressionOperand(TExpression expr, Table tableModel){
9868                if(expr == null) return;
9869                Deque<TExpression> stack = new ArrayDeque<>();
9870                stack.push(expr);
9871                while (!stack.isEmpty()) {
9872                        TExpression current = stack.pop();
9873                        if (current == null) continue;
9874                        TObjectName columnObj = current.getObjectOperand();
9875                        if (columnObj != null) {
9876                                TableColumn tableConstraint = modelFactory.createTableColumn(tableModel, columnObj, true);
9877                                tableConstraint.setIndexKey(true);
9878                        } else {
9879                                if (current.getRightOperand() != null) {
9880                                        stack.push(current.getRightOperand());
9881                                }
9882                                if (current.getLeftOperand() != null) {
9883                                        stack.push(current.getLeftOperand());
9884                                }
9885                        }
9886                }
9887        }
9888        private void analyzeCreateIndexStageStmt(TCreateIndexSqlStatement stmt) {
9889        if(stmt.getTableName() == null){
9890            return;
9891        }
9892                Table tableModel = modelFactory.createTableByName(stmt.getTableName());
9893                TOrderByItemList columns = stmt.getColumnNameList();
9894        if(columns!=null) {
9895            for (int i = 0; i < columns.size(); i++) {
9896                TExpression expr = columns.getOrderByItem(i).getSortKey();
9897                analyzeCreateIndexExpressionOperand(expr, tableModel);
9898            }
9899        }
9900        }
9901
9902        private void analyzeCreateSynonymStmt(TCreateSynonymStmt stmt) {
9903                TObjectName sourceTableName = stmt.getForName();
9904                if(sourceTableName == null) {
9905                        return;
9906                }
9907                TCustomSqlStatement createView = viewDDLMap
9908                                .get(DlineageUtil.getTableIdentityKey(sourceTableName.toString()));
9909                if (createView != null) {
9910                        analyzeCustomSqlStmt(createView);
9911                }
9912                Table sourceTableModel = modelFactory.createTableByName(sourceTableName);
9913                Process process = modelFactory.createProcess(stmt);
9914                sourceTableModel.addProcess(process);
9915                if(stmt.getSynonymName()!=null) {
9916                        Table synonymTableModel = modelFactory.createTableByName(stmt.getSynonymName());
9917                        synonymTableModel.setSubType(SubType.synonym);
9918                        if(sourceTableModel.isCreateTable()) {
9919                                synonymTableModel.setCreateTable(true);
9920                                for(TableColumn sourceTableColumn: new ArrayList<>(sourceTableModel.getColumns())) {
9921                                        TObjectName columnName = new TObjectName();
9922                                        columnName.setString(sourceTableColumn.getName());
9923                                        TableColumn synonymTableColumn = new TableColumn(synonymTableModel, columnName);
9924                                        synonymTableModel.addColumn(synonymTableColumn);
9925                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
9926                                        relation.setEffectType(EffectType.create_synonym);
9927                                        relation.setTarget(new TableColumnRelationshipElement(synonymTableColumn));
9928                                        relation.addSource(new TableColumnRelationshipElement(sourceTableColumn));
9929                                        relation.setProcess(process);
9930                                }
9931                        }
9932                        else {
9933                                TObjectName synonymStarColumn = DlineageUtil.syntheticStarName();
9934                                TableColumn synonymTableStarColumn = modelFactory.createTableColumn(synonymTableModel,
9935                                                synonymStarColumn, true);
9936                                TObjectName sourceStarColumn = DlineageUtil.syntheticStarName();
9937                                TableColumn sourceTableStarColumn = modelFactory.createTableColumn(sourceTableModel, sourceStarColumn, true);
9938                                
9939                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
9940                                relation.setEffectType(EffectType.create_synonym);
9941                                relation.setTarget(new TableColumnRelationshipElement(synonymTableStarColumn));
9942                                relation.addSource(new TableColumnRelationshipElement(sourceTableStarColumn));
9943                                relation.setProcess(process);
9944                        }
9945                }
9946        }
9947
9948        /**
9949         * For every Table model that maps to a {@link TSQLSynonyms} in the current
9950         * {@link #sqlenv}, materialize the synonym's base table as a separate Table
9951         * model and emit per-column {@code fdd}/{@code synonym} relations from the
9952         * synonym's columns to the base table's matching columns. Lets lineage flow
9953         * through synonyms defined in the metadata SQLEnv (e.g. loaded via
9954         * {@code TDDLSQLEnv} or sqlflow metadata) the same way it does for
9955         * {@code CREATE SYNONYM} DDL inside the analyzed script.
9956         */
9957        /**
9958         * The identity of a catalog synonym under the analysis's identifier rules, in the synonym role, from the stored
9959         * names the catalog holds; a tier the catalog holds only as its placeholder is left out, as a table model leaves
9960         * it out. Null when the rules give the name no identity.
9961         */
9962        private static QualifiedCanonKey storedSynonymIdentity(IdentifierService service, TSQLCatalog catalog,
9963                        TSQLSchema schema, TSQLSynonyms synonym) {
9964                if (service == null) {
9965                        return null;
9966                }
9967                EDbVendor vendor = service.getProfile().getVendor();
9968                List<String> segments = new ArrayList<String>();
9969                if (TSQLEnv.supportCatalog(vendor) && catalog.getName() != null
9970                                && !TSQLEnv.DEFAULT_DB_NAME.equalsIgnoreCase(catalog.getName())) { // non-identifier-compare: DEFAULT is the placeholder for a missing tier
9971                        segments.add(catalog.getName());
9972                }
9973                if (TSQLEnv.supportSchema(vendor) && schema.getName() != null
9974                                && !TSQLEnv.DEFAULT_SCHEMA_NAME.equalsIgnoreCase(schema.getName())) { // non-identifier-compare: DEFAULT is the placeholder for a missing tier
9975                        segments.add(schema.getName());
9976                }
9977                segments.add(synonym.getName());
9978                try {
9979                        return service.qualifiedCanonKeyFromStored(segments, ESQLDataObjectType.dotSynonyms);
9980                } catch (RuntimeException noIdentity) {
9981                        return null;
9982                }
9983        }
9984
9985        private void materializeSqlEnvSynonyms() {
9986                if (sqlenv == null) {
9987                        return;
9988                }
9989                List<Table> snapshot = new ArrayList<Table>(modelManager.getTablesByName());
9990
9991                // Catalog synonyms by identity under the analysis's rules, from the names the catalog stores, in the
9992                // synonym role (a profile may give synonyms rules of their own). A model matches by its identity in that
9993                // role, never by its rendered name: a stored lower-case Oracle synonym is the quoted name
9994                // "address_synonym", which a folded rendering reads as ADDRESS_SYNONYM.
9995                Map<QualifiedCanonKey, TSQLSynonyms> synonymsByIdentity = new HashMap<>();
9996                IdentifierService synonymService = ModelBindingManager.getGlobalIdentifierService();
9997                List<TSQLCatalog> catalogs = sqlenv.getCatalogList();
9998                if (catalogs != null) {
9999                        for (TSQLCatalog catalog : catalogs) {
10000                                if (catalog == null) {
10001                                        continue;
10002                                }
10003                                List<TSQLSchema> schemas = catalog.getSchemaList();
10004                                if (schemas == null) {
10005                                        continue;
10006                                }
10007                                for (TSQLSchema schema : schemas) {
10008                                        if (schema == null) {
10009                                                continue;
10010                                        }
10011                                        List<TSQLSchemaObject> schemaObjects = schema.getSchemaObjectList();
10012                                        if (schemaObjects == null || schemaObjects.isEmpty()) {
10013                                                continue;
10014                                        }
10015                                        for (TSQLSchemaObject schemaObject : schemaObjects) {
10016                                                if (schemaObject instanceof TSQLSynonyms) {
10017                                                        TSQLSynonyms synonym = (TSQLSynonyms) schemaObject;
10018                                                        QualifiedCanonKey identity = storedSynonymIdentity(synonymService, catalog, schema, synonym);
10019                                                        if (identity != null) {
10020                                                                synonymsByIdentity.put(identity, synonym);
10021                                                        }
10022                                                }
10023                                        }
10024                                }
10025                        }
10026                }
10027
10028                for (Table synonymTableModel : snapshot) {
10029                        if (synonymTableModel == null) {
10030                                continue;
10031                        }
10032                        if (SubType.synonym.equals(synonymTableModel.getSubType())) {
10033                                continue;
10034                        }
10035                        QualifiedCanonKey modelIdentity = synonymTableModel.getCanonicalIdentityAs(ESQLDataObjectType.dotSynonyms);
10036                        TSQLSynonyms synonym = modelIdentity == null ? null : synonymsByIdentity.get(modelIdentity);
10037                        if (synonym == null) {
10038                                continue;
10039                        }
10040
10041                        String baseSqlTableQualifiedName = synonym.getBaseTableQualifiedName();
10042                        if (baseSqlTableQualifiedName == null) {
10043                                continue;
10044                        }
10045
10046                        synonymTableModel.setSubType(SubType.synonym);
10047
10048                        // The base table is named by the catalog, not by the SQL, so no position of it is published: a model made
10049                        // from a synthesized TObjectName would claim the name's own position (line 1), and that path also stamps
10050                        // the position onto a table the SQL did reference.
10051                        Table baseTableModel = modelFactory.createCatalogNamedTable(baseSqlTableQualifiedName);
10052                        if (baseTableModel == null || baseTableModel == synonymTableModel) {
10053                                continue;
10054                        }
10055
10056                        for (TableColumn synonymColumn : new ArrayList<TableColumn>(synonymTableModel.getColumns())) {
10057                                String colName = synonymColumn.getName();
10058                                if (SQLUtil.isEmpty(colName) || synonymColumn.isStarColumn()) {
10059                                        continue;
10060                                }
10061                                TableColumn baseColumn = null;
10062                                for (TableColumn bc : baseTableModel.getColumns()) {
10063                                        if (bc.getName() != null && SQLUtil.sameName(option.getVendor(), ESQLDataObjectType.dotColumn, bc.getName(), colName)) {
10064                                                baseColumn = bc;
10065                                                break;
10066                                        }
10067                                }
10068                                if (baseColumn == null) {
10069                                        TObjectName columnName = new TObjectName();
10070                                        columnName.setString(colName);
10071                                        baseColumn = new TableColumn(baseTableModel, columnName);
10072                                        // a column the catalog relates to the synonym, not a name the SQL wrote: no position
10073                                        baseColumn.setStartPosition(null);
10074                                        baseColumn.setEndPosition(null);
10075                                        baseTableModel.addColumn(baseColumn);
10076                                }
10077                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
10078                                relation.setEffectType(EffectType.synonym);
10079                                relation.setTarget(new TableColumnRelationshipElement(synonymColumn));
10080                                relation.addSource(new TableColumnRelationshipElement(baseColumn));
10081                        }
10082                }
10083        }
10084
10085        private void analyzeRenameStmt(TRenameStmt stmt) {
10086                TObjectName oldTableName = stmt.getOldName();
10087                TObjectName newTableName = stmt.getNewName();
10088                
10089                Table oldNameTableModel = modelFactory.createTableByName(oldTableName);
10090                TObjectName oldStarColumn = DlineageUtil.syntheticStarName();
10091                TableColumn oldTableStarColumn = modelFactory.createTableColumn(oldNameTableModel, oldStarColumn, true);
10092                
10093                Table newNameTableModel = modelFactory.createTableByName(newTableName);
10094                TObjectName newStarColumn = DlineageUtil.syntheticStarName();
10095                TableColumn newTableStarColumn = modelFactory.createTableColumn(newNameTableModel, newStarColumn, true);
10096                
10097                Process process = modelFactory.createProcess(stmt);
10098                newNameTableModel.addProcess(process);
10099                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
10100                relation.setEffectType( EffectType.rename_table);
10101                relation.setTarget(new TableColumnRelationshipElement(newTableStarColumn));
10102                relation.addSource(new TableColumnRelationshipElement(oldTableStarColumn));
10103                relation.setProcess(process);
10104                
10105                if ((oldNameTableModel.isCreateTable() || oldNameTableModel.hasSQLEnv())) {
10106                        oldTableStarColumn.setShowStar(false);
10107                        relation.setShowStarRelation(false);
10108                }
10109
10110                if ((newNameTableModel.isCreateTable() || newNameTableModel.hasSQLEnv())) {
10111                        newTableStarColumn.setShowStar(false);
10112                        relation.setShowStarRelation(false);
10113                }
10114        }
10115        
10116        /**
10117         * {@code ALTER TABLE source SWITCH [PARTITION p] TO target [PARTITION q]} (SQL Server, OpenEdge): the rows move
10118         * from the altered table, or one of its partitions, into the target, so data flows one way, source -> target
10119         * ({@code EXCHANGE PARTITION} swaps, hence its two relations). With every column of both tables known (a CREATE
10120         * TABLE or a SELECT ... INTO in the script, or metadata) each column flows into its namesake, paired by the
10121         * identifier rules. Else the relation is source.* -> target.*: the star expansion resolves it by the side whose
10122         * columns are known (SWITCH requires the same columns on both), and with neither known it stays table level. That
10123         * relation's partition is the source partition when the statement names one, which is what
10124         * {@code exchange_partition} records too: the partition of the altered table.
10125         */
10126        private void analyzeSwitchPartition(TAlterTableStatement stmt, TAlterTableOption option, Table sourceModel,
10127                        TableColumn sourceStarColumn) {
10128                TObjectName targetName = option.getNewTableName();
10129                if (targetName == null) {
10130                        return;
10131                }
10132                Table targetModel = null;
10133                Stack<TParseTreeNode> nodes = targetName.getStartToken().getNodesStartFromThisToken();
10134                for (int j = 0; nodes != null && j < nodes.size(); j++) {
10135                        if (nodes.get(j) instanceof TTable) {
10136                                targetModel = modelFactory.createTable((TTable) nodes.get(j));
10137                                break;
10138                        }
10139                }
10140                if (targetModel == null) {
10141                        targetModel = modelFactory.createTableByName(targetName);
10142                }
10143                targetModel.setStarStmt("switch_partition");
10144
10145                // The star expansion goes by the columns' text and loses a real column named * (a delimited [*]): pair the
10146                // columns here when both tables' columns are known. Like the pairs a star expands to (exchange_partition's
10147                // too), a pair carries no partition; the table-level relation does.
10148                List<TableColumn> sourceColumns = switchColumns(sourceModel);
10149                List<TableColumn> targetColumns = switchColumns(targetModel);
10150                if (sourceColumns != null && targetColumns != null) {
10151                        sourceStarColumn.setShowStar(false);
10152                        Process process = modelFactory.createProcess(stmt);
10153                        targetModel.addProcess(process);
10154                        for (TableColumn sourceColumn : sourceColumns) {
10155                                TableColumn targetColumn = namesake(sourceColumn, targetColumns);
10156                                if (targetColumn == null) {
10157                                        continue;   // the tables' columns disagree: a column the target does not have is not invented
10158                                }
10159                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
10160                                relation.setEffectType(EffectType.switch_partition);
10161                                relation.setTarget(new TableColumnRelationshipElement(targetColumn));
10162                                relation.addSource(new TableColumnRelationshipElement(sourceColumn));
10163                                relation.setProcess(process);
10164                        }
10165                        return;
10166                }
10167
10168                TableColumn targetStarColumn = modelFactory.createTableColumn(targetModel, DlineageUtil.syntheticStarName(),
10169                                true);
10170                Process process = modelFactory.createProcess(stmt);
10171                targetModel.addProcess(process);
10172                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
10173                relation.setEffectType(EffectType.switch_partition);
10174                relation.setTarget(new TableColumnRelationshipElement(targetStarColumn));
10175                relation.addSource(new TableColumnRelationshipElement(sourceStarColumn));
10176                relation.setProcess(process);
10177                TExpression sourcePartition = switchSourcePartition(option, targetName);
10178                if (sourcePartition != null) {
10179                        relation.setPartition(sourcePartition.toString());
10180                }
10181                if (sourceModel.isCreateTable() || sourceModel.hasSQLEnv()) {
10182                        sourceStarColumn.setShowStar(false);
10183                        relation.setShowStarRelation(false);
10184                }
10185                if (targetModel.isCreateTable() || targetModel.hasSQLEnv()) {
10186                        targetStarColumn.setShowStar(false);
10187                        relation.setShowStarRelation(false);
10188                }
10189        }
10190
10191        /**
10192         * The partition of the SWITCH source, or null. The parser keeps the partition expressions in the order they
10193         * appear ({@code SWITCH TO t PARTITION n} stores the TARGET partition first), so the source partition is the one
10194         * written before the target table's name.
10195         */
10196        private static TExpression switchSourcePartition(TAlterTableOption option, TObjectName targetName) {
10197                TSourceToken targetStart = targetName.getStartToken();
10198                for (TExpression partition : new TExpression[] { option.getPartitionExpression1(), option.getPartitionExpression2() }) {
10199                        if (partition != null && partition.getStartToken() != null && targetStart != null
10200                                        && partition.getStartToken().posinlist < targetStart.posinlist) {
10201                                return partition;
10202                        }
10203                }
10204                return null;
10205        }
10206
10207        /**
10208         * The columns of a SWITCH table when the analysis knows every one of them -- declared by a CREATE TABLE in the
10209         * script, given by metadata, or made by a SELECT ... INTO whose columns are all known -- stars and system pseudo
10210         * columns left out; null when it does not: a table only referenced (an INSERT without a column list marks its
10211         * target determined, yet names only the columns it writes), a SELECT ... INTO with a * the analysis could not
10212         * expand (whatever a later INSERT makes of the table), metadata with a star it could not resolve (a table
10213         * determined all the same), a table without columns.
10214         */
10215        private List<TableColumn> switchColumns(Table table) {
10216                boolean selectInto = table.getEndpointIntroduction() == EndpointIntroduction.SELECT_INTO;
10217                if (!table.isDetermined() || !(table.isCreateTable() || table.hasSQLEnv() || selectInto)
10218                                || (selectInto && selectIntoWithUnknownColumns.contains(table)) || metadataHoldsUnresolvedStar(table)) {
10219                        return null;
10220                }
10221                List<TableColumn> columns = new ArrayList<TableColumn>();
10222                for (TableColumn column : table.getColumns()) {
10223                        if (!column.isStarColumn() && !column.isPseduo()) {
10224                                columns.add(column);
10225                        }
10226                }
10227                return columns.isEmpty() ? null : columns;
10228        }
10229
10230        /**
10231         * SELECT ... INTO targets some SELECT ... INTO did not name every column of (a * the analysis could not expand),
10232         * by identity: whether the SELECT named them all is the SELECT's own fact, where the target's being determined can
10233         * change after it -- an INSERT without a column list marks its target determined -- and GSP keeps such a * in the
10234         * target either as a star or as a column named *.
10235         */
10236        private final Set<Table> selectIntoWithUnknownColumns = Collections.newSetFromMap(new IdentityHashMap<Table, Boolean>());
10237
10238        /** Records whether a SELECT ... INTO named every column of its target; once one did not, the table stays so. */
10239        private void recordSelectIntoColumns(Table target, boolean allNamed) {
10240                if (!allNamed) {
10241                        selectIntoWithUnknownColumns.add(target);
10242                }
10243        }
10244
10245        /**
10246         * The SELECT ... INTO statement that first made each table, by identity. A DROP ends a table and the next
10247         * SELECT ... INTO makes a new one, so a second statement making the same table is an alternative to the first,
10248         * as in the two branches of an IF.
10249         */
10250        private final Map<Table, TSelectSqlStatement> selectIntoDefinitions = new IdentityHashMap<Table, TSelectSqlStatement>();
10251
10252        /**
10253         * Records what a SELECT ... INTO tells of its target's columns, and marks the target a table whose columns are
10254         * exactly the ones the SELECT named when that is known, so that a later {@code SELECT *} of it expands to them and
10255         * an INSERT column list pairs with them by position (Mantis 4753). That holds when the SELECT named every column,
10256         * the statement creates the table ({@link #selectIntoFixesColumns}) and it is the table's only SELECT ... INTO.
10257         * Two statements making one table ({@code IF ... SELECT a, b INTO #t ... ELSE SELECT c INTO #t}) leave the
10258         * columns to the branch that ran, while the model holds the columns of both, which no run has.
10259         */
10260        private void recordSelectIntoTarget(TSelectSqlStatement stmt, Table tableModel, TObjectName tableName,
10261                        boolean allNamed, List<TableColumn> written) {
10262                recordSelectIntoColumns(tableModel, allNamed);
10263                if (tableModel.isVariable()) {
10264                        // a variable is assigned, not made, as often as the code does
10265                        if (allNamed && DlineageUtil.isTempTable(tableModel, option.getVendor())) {
10266                                tableModel.setCreateTable(true, false);
10267                        }
10268                        return;
10269                }
10270                if (!selectIntoFixesColumns(tableModel, tableName)) {
10271                        recordPermanentSelectInto(stmt, tableModel, written);
10272                        return;
10273                }
10274                TSelectSqlStatement first = selectIntoDefinitions.get(tableModel);
10275                if (first == null) {
10276                        selectIntoDefinitions.put(tableModel, stmt);
10277                } else if (first != stmt) {
10278                        recordSelectIntoColumns(tableModel, false);
10279                        if (tableModel.isCreateTable()) {
10280                                tableModel.setCreateTable(false, false);
10281                        }
10282                }
10283                if (!selectIntoWithUnknownColumns.contains(tableModel)) {
10284                        tableModel.setCreateTable(true, false);
10285                }
10286        }
10287
10288        /**
10289         * The columns of a permanent table, in order, as the only SELECT ... INTO that made it wrote them, by identity;
10290         * gone once a second SELECT ... INTO makes the table or an ALTER TABLE changes it.
10291         */
10292        private final Map<Table, List<TableColumn>> selectIntoColumnLists = new IdentityHashMap<Table, List<TableColumn>>();
10293
10294        /**
10295         * Records the columns a SELECT ... INTO wrote into a permanent table, {@code written} in select-list order, or
10296         * null when they are not all known. The table is not marked created, as a temporary one is: a permanent table can
10297         * gain columns from code the analysis never reads, and a created table drops a reference to a column it lacks.
10298         */
10299        private void recordPermanentSelectInto(TSelectSqlStatement stmt, Table tableModel, List<TableColumn> written) {
10300                recordSelectIntoAlternative(stmt, tableModel, written);
10301                TSelectSqlStatement first = selectIntoDefinitions.get(tableModel);
10302                if (first == null) {
10303                        selectIntoDefinitions.put(tableModel, stmt);
10304                        if (written != null) {
10305                                selectIntoColumnLists.put(tableModel, new ArrayList<TableColumn>(written));
10306                        }
10307                } else if (first != stmt || written == null) {
10308                        selectIntoColumnLists.remove(tableModel);
10309                }
10310        }
10311
10312        /**
10313         * The columns each SELECT ... INTO that made a permanent table wrote, in select-list order, by statement identity;
10314         * null for one whose columns are not all known. Gone once an ALTER TABLE changes the table (Mantis 4860).
10315         */
10316        private final Map<Table, List<Pair<TSelectSqlStatement, List<TableColumn>>>> selectIntoAlternatives =
10317                        new IdentityHashMap<Table, List<Pair<TSelectSqlStatement, List<TableColumn>>>>();
10318
10319        /** Records the columns {@code stmt} wrote into the permanent table {@code tableModel}, or null when not all are known. */
10320        private void recordSelectIntoAlternative(TSelectSqlStatement stmt, Table tableModel, List<TableColumn> written) {
10321                List<Pair<TSelectSqlStatement, List<TableColumn>>> definitions = selectIntoAlternatives.get(tableModel);
10322                if (definitions == null) {
10323                        definitions = new ArrayList<Pair<TSelectSqlStatement, List<TableColumn>>>();
10324                        selectIntoAlternatives.put(tableModel, definitions);
10325                }
10326                Pair<TSelectSqlStatement, List<TableColumn>> definition = new Pair<TSelectSqlStatement, List<TableColumn>>(stmt,
10327                                written == null ? null : new ArrayList<TableColumn>(written));
10328                for (int i = 0; i < definitions.size(); i++) {
10329                        if (definitions.get(i).first == stmt) {
10330                                definitions.set(i, definition);
10331                                return;
10332                        }
10333                }
10334                definitions.add(definition);
10335        }
10336
10337        /**
10338         * The column lists, in order, of the SELECT ... INTO statements that made the permanent table {@code table}, when
10339         * there are two or more, all complete and as wide (Mantis 4860, owner decision 2026-10-22): which one made the table
10340         * is not known, so a {@code SELECT *} of it reads, at each position, the column each of them wrote there. Null
10341         * otherwise, or while the table has a column none of them wrote, as {@link #starExpansionColumns} has it.
10342         */
10343        private List<List<TableColumn>> starAlternatives(Table table) {
10344                List<Pair<TSelectSqlStatement, List<TableColumn>>> definitions = selectIntoAlternatives.get(table);
10345                if (definitions == null || definitions.size() < 2 || table.isCreateTable() || !table.isDetermined()
10346                                || selectIntoWithUnknownColumns.contains(table)) {
10347                        return null;
10348                }
10349                List<List<TableColumn>> lists = new ArrayList<List<TableColumn>>();
10350                List<TableColumn> written = new ArrayList<TableColumn>();
10351                for (Pair<TSelectSqlStatement, List<TableColumn>> definition : definitions) {
10352                        List<TableColumn> columns = definition.second;
10353                        if (columns == null || columns.isEmpty() || !lists.isEmpty() && columns.size() != lists.get(0).size()) {
10354                                return null;
10355                        }
10356                        lists.add(columns);
10357                        written.addAll(columns);
10358                }
10359                Set<TableColumn> members = Collections.newSetFromMap(new IdentityHashMap<TableColumn, Boolean>());
10360                members.addAll(written);
10361                for (TableColumn column : table.getColumns()) {
10362                        if (!column.isStarColumn() && !column.isPseduo() && !members.contains(column)
10363                                        && namesake(column, written) == null) {
10364                                return null;
10365                        }
10366                }
10367                return lists;
10368        }
10369
10370        /** Whether {@code resultSet} has a column of the name {@code name}. */
10371        private boolean namedIn(ResultSet resultSet, String name) {
10372                for (ResultColumn column : resultSet.getColumns()) {
10373                        if (SQLUtil.sameName(option.getVendor(), ESQLDataObjectType.dotColumn, column.getName(), name)) {
10374                                return true;
10375                        }
10376                }
10377                return false;
10378        }
10379
10380        /**
10381         * A reference by name to a column of the derived table {@code table} that its alias list does not name (Mantis 4873),
10382         * where the list may be shorter than the query ({@link #columnListMayBeShorter}): a column past the list keeps its
10383         * name. A reference names its column; no position is assumed, so a SELECT * of the derived table or a list it fills
10384         * is not affected.
10385         */
10386        private void readPastAliasList(TTable table, TObjectName columnName, AbstractRelationship relation) {
10387                if (!columnListMayBeShorter(option.getVendor()) || table.getSubquery() == null
10388                                || table.getAliasClause() == null || table.getAliasClause().getColumns() == null
10389                                || !(modelManager.getModel(table.getSubquery()) instanceof ResultSet)) {
10390                        return;
10391                }
10392                List<ResultColumn> query = ((ResultSet) modelManager.getModel(table.getSubquery())).getColumns();
10393                int listed = table.getAliasClause().getColumns().size();
10394                // the column of the query named so; the output publishes the columns of one name in a select as one column, so
10395                // with two of them what the name reads is not told apart
10396                int named = -1;
10397                for (int j = 0; j < query.size(); j++) {
10398                        if (!query.get(j).isStarColumn() && referencesColumn(columnName, query.get(j))) {
10399                                if (named >= 0) {
10400                                        return;
10401                                }
10402                                named = j;
10403                        }
10404                }
10405                boolean starBefore = false;
10406                for (int j = 0; j < named; j++) {
10407                        starBefore |= query.get(j).isStarColumn();
10408                }
10409                // past the list wherever the stars before it end; after a star it may stand there too; else the list renames it
10410                boolean past = named >= listed;
10411                if (past || (named >= 0 && starBefore)) {
10412                        readQueryColumn(query.get(named), listed, columnName, relation);
10413                }
10414                // the stars that may stand for a column of the name past the list. PostgreSQL rejects a read of a name two columns
10415                // past the list have: none where the column stands past the list, else those after it (one before it reaching
10416                // past the list would put it there too). DuckDB names the later ones past the list k_1 (Codex review): those
10417                // before the column where it stands past the list, else all of them
10418                boolean duckdb = option.getVendor() == EDbVendor.dbvduckdb;
10419                if (past && !duckdb) {
10420                        return;
10421                }
10422                // the output publishes a star's column of the name as the column named so: only where that one names the same column
10423                if (named >= 0 && !plainReference(query.get(named))) {
10424                        return;
10425                }
10426                int from = duckdb ? 0 : named + 1;
10427                int to = duckdb && past ? named : query.size();
10428                for (int j = from; j < to; j++) {
10429                        if (query.get(j).isStarColumn() && !starOmits(query.get(j), columnName)) {
10430                                readThroughStar(query.get(j), columnName, relation);
10431                        }
10432                }
10433        }
10434
10435        /** Whether {@code column} is a select item that names a column, without an alias. */
10436        private static boolean plainReference(ResultColumn column) {
10437                if (!(column.getColumnObject() instanceof TResultColumn)) {
10438                        return false;
10439                }
10440                TResultColumn item = (TResultColumn) column.getColumnObject();
10441                return item.getAliasClause() == null && item.getExpr() != null
10442                                && item.getExpr().getExpressionType() == EExpressionType.simple_object_name_t;
10443        }
10444
10445        /**
10446         * Reads {@code column} of the query of a derived table past its alias list of {@code listed} names: a column of a set
10447         * operation reads the columns of its selects that can stand past the list, leaving out one its select puts within
10448         * the list whatever its stars stand for (Codex review).
10449         */
10450        private void readQueryColumn(ResultColumn column, int listed, TObjectName columnName, AbstractRelationship relation) {
10451                List<ResultColumn> selects = setColumnSources.get(column);
10452                if (selects != null) {
10453                        List<ResultColumn> past = new ArrayList<ResultColumn>();
10454                        for (ResultColumn select : selects) {
10455                                if (!standsWithin(select, listed)) {
10456                                        past.add(select);
10457                                }
10458                        }
10459                        if (past.size() < selects.size()) {
10460                                for (ResultColumn select : past) {
10461                                        relation.addSource(new ResultColumnRelationshipElement(select));
10462                                }
10463                                return;
10464                        }
10465                }
10466                // the column itself: read by name, the output could take another column of the name for it
10467                relation.addSource(new ResultColumnRelationshipElement(column));
10468        }
10469
10470        /** Whether {@code column} stands at one of the first {@code listed} positions of its select, with no star before it. */
10471        private static boolean standsWithin(ResultColumn column, int listed) {
10472                List<ResultColumn> columns = column.getResultSet() == null ? null : column.getResultSet().getColumns();
10473                for (int i = 0; columns != null && i < columns.size() && i < listed; i++) {
10474                        if (columns.get(i) == column) {
10475                                return true;
10476                        }
10477                        if (columns.get(i).isStarColumn()) {
10478                                return false;
10479                        }
10480                }
10481                return false;
10482        }
10483
10484        /** Whether the star item {@code star} leaves the column {@code name} out or replaces it (EXCEPT, REPLACE). */
10485        private static boolean starOmits(ResultColumn star, TObjectName name) {
10486                if (!(star.getColumnObject() instanceof TResultColumn)) {
10487                        return false;
10488                }
10489                TResultColumn item = (TResultColumn) star.getColumnObject();
10490                if (item.getExceptColumnList() != null) {
10491                        for (TObjectName except : item.getExceptColumnList()) {
10492                                if (DlineageUtil.sameColumnName(except, name)) {
10493                                        return true;
10494                                }
10495                        }
10496                }
10497                if (item.getReplaceExprAsIdentifiers() != null) {
10498                        for (TReplaceExprAsIdentifier replace : item.getReplaceExprAsIdentifiers()) {
10499                                if (DlineageUtil.sameColumnName(replace.getIdentifier(), name)) {
10500                                        return true;
10501                                }
10502                        }
10503                }
10504                return false;
10505        }
10506
10507        /** {@code columnName} read through {@code star}: the column of that name the star stands for. */
10508        private void readThroughStar(ResultColumn star, TObjectName columnName, AbstractRelationship relation) {
10509                if (!containsStarColumn(star, columnName)) {
10510                        star.bindStarLinkColumn(columnName);
10511                }
10512                relation.addSource(new ResultColumnRelationshipElement(star, columnName));
10513        }
10514
10515        /** A column of {@code queryTable}, a derived table, past its alias list: the query's {@code sourceColumn} under its own name. */
10516        private void derivedColumnPastList(QueryTable queryTable, ResultColumn sourceColumn) {
10517                ResultColumn targetColumn = modelFactory.createSelectSetResultColumn(queryTable, sourceColumn);
10518                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
10519                relation.setEffectType(EffectType.select);
10520                relation.setTarget(new ResultColumnRelationshipElement(targetColumn));
10521                relation.addSource(new ResultColumnRelationshipElement(sourceColumn));
10522        }
10523
10524        /** Whether {@code stmt} is the query an INSERT writes, whose columns the INSERT reads by position. */
10525        private static boolean isInsertSubquery(TSelectSqlStatement stmt) {
10526                return stmt.getParentStmt() instanceof TInsertSqlStatement
10527                                && ((TInsertSqlStatement) stmt.getParentStmt()).getSubQuery() == stmt;
10528        }
10529
10530        /**
10531         * The columns a {@code SELECT *} of {@code table} expands to, in order, or null when they are not known: a created
10532         * table's columns, or the columns the only SELECT ... INTO of a permanent table wrote, in its select-list order,
10533         * while every column of the table is one of them or of the same name (an INSERT into the table writes rows, not
10534         * columns). A column of another name, which a later statement read, leaves the order unproven (Mantis 4856).
10535         */
10536        private List<TableColumn> starExpansionColumns(Table table) {
10537                if (table.isCreateTable()) {
10538                        return table.getColumns();
10539                }
10540                List<TableColumn> written = selectIntoColumnLists.get(table);
10541                if (written == null || written.isEmpty() || !table.isDetermined()
10542                                || selectIntoWithUnknownColumns.contains(table)) {
10543                        return null;
10544                }
10545                Set<TableColumn> members = Collections.newSetFromMap(new IdentityHashMap<TableColumn, Boolean>());
10546                members.addAll(written);
10547                int present = 0;
10548                for (TableColumn column : table.getColumns()) {
10549                        if (column.isStarColumn() || column.isPseduo()) {
10550                                continue;
10551                        }
10552                        if (members.contains(column)) {
10553                                present++;
10554                        } else if (namesake(column, written) == null) {
10555                                return null;
10556                        }
10557                }
10558                return present == written.size() ? written : null;
10559        }
10560
10561        /**
10562         * The source element of {@code column} at a position of a list of targets (INSERT, CREATE TABLE ... AS, a view's or
10563         * a CTE's column list): a star of unknown column order ({@link #unknownOrderStar}) is read whole, never by the member
10564         * named like the target or standing at its ordinal (Mantis 4860, owner ruling 2026-10-03), as Mantis 4856 reads one
10565         * in an INSERT column list ({@link #insertListSource}). Any other column keeps its element.
10566         */
10567        private ResultColumnRelationshipElement positionSource(ResultColumn column) {
10568                ResultColumnRelationshipElement source = new ResultColumnRelationshipElement(column);
10569                if (unknownOrderStar(column)) {
10570                        source.setPositionUnproven(true);
10571                }
10572                return source;
10573        }
10574
10575        /**
10576         * Whether {@code column} is a star whose members only other statements name, over one table that proves no column
10577         * order: the table has a star of its own, and a table the script creates (CREATE TABLE ... AS, SELECT ... INTO)
10578         * starts with it ({@code CREATE TABLE q AS SELECT k AS p, *} proves q.p first). A member its own statement names
10579         * keeps its pairing (owner decision 2026-10-22, {@code WHERE sender = '1'}). Not a variable (a record SELECT ...
10580         * INTO fills), which the simple output leaves out, nor a star with EXCEPT or REPLACE.
10581         */
10582        private boolean unknownOrderStar(ResultColumn column) {
10583                List<Object> tables = column.isStarColumn() && !linkedInItsStatement(column) ? starTableModels(column) : null;
10584                if (tables == null || tables.size() != 1) {
10585                        return false;
10586                }
10587                Table table = (Table) tables.get(0);
10588                if (table.isVariable() || !containStarColumn(table.getColumns())) {
10589                        return false;
10590                }
10591                if (table.isCreatedInSql()) {
10592                        // the columns of a table the script creates stand in its select list's order
10593                        for (TableColumn tableColumn : table.getColumns()) {
10594                                if (!tableColumn.isPseduo()) {
10595                                        return tableColumn.isStarColumn();
10596                                }
10597                        }
10598                }
10599                return true;
10600        }
10601
10602        /**
10603         * Whether a name the star {@code column} links is written in the statement being analyzed, the star's own: the
10604         * names of other statements that a set operation's star carries say nothing of the order. Without a token or a
10605         * statement to tell, a name counts as the statement's own.
10606         */
10607        private boolean linkedInItsStatement(ResultColumn column) {
10608                if (!column.hasStarLinkColumn()) {
10609                        return false;
10610                }
10611                TCustomSqlStatement statement = stmtStack.isEmpty() ? null : stmtStack.firstElement();
10612                TSourceToken start = statement == null ? null : statement.getStartToken();
10613                TSourceToken end = statement == null ? null : statement.getEndToken();
10614                for (TObjectName link : column.getStarLinkColumnList()) {
10615                        TSourceToken token = link.getStartToken();
10616                        if (start == null || end == null || token == null || token.container == start.container
10617                                        && token.posinlist >= start.posinlist && token.posinlist <= end.posinlist) {
10618                                return true;
10619                        }
10620                }
10621                return false;
10622        }
10623
10624        /**
10625         * The source of an INSERT column-list target the column {@code resultColumn} of {@code subquery} feeds. A star
10626         * whose members the analysis does not know reads them in an unknown order, so no member is the one at the target's
10627         * position: the target reads the star, never a member named like the target (Mantis 4856), when the star's one
10628         * table has a star of its own for it to read.
10629         */
10630        private ResultColumnRelationshipElement insertListSource(ResultColumn resultColumn, TSelectSqlStatement subquery) {
10631                ResultColumnRelationshipElement source = new ResultColumnRelationshipElement(resultColumn);
10632                if (!resultColumn.isStarColumn() || resultColumn.hasStarLinkColumn()
10633                                || subquery.getSetOperatorType() != ESetOperatorType.none
10634                                || !(resultColumn.getColumnObject() instanceof TResultColumn)) {
10635                        return source;
10636                }
10637                TObjectName star = ((TResultColumn) resultColumn.getColumnObject()).getFieldAttr();
10638                List<TTable> tables = star == null ? null : star.getSourceTableList();
10639                Object model = tables == null || tables.size() != 1 ? null : modelManager.getModel(tables.get(0));
10640                // a variable (a record SELECT ... INTO fills in PostgreSQL or Oracle) is left out of the simple output, so a
10641                // target reading only its * would lose its upstream there
10642                if (model instanceof Table && !((Table) model).isVariable() && containStarColumn(((Table) model).getColumns())) {
10643                        source.setPositionUnproven(true);
10644                }
10645                return source;
10646        }
10647
10648        /**
10649         * Whether a SELECT ... INTO {@code tableName} creates a table that only the analyzed code can change: a
10650         * transient temporary table, {@code INTO #t} or {@code INTO TEMP t}, by the rule resolver2 registers it with
10651         * ({@link TemporaryTableSemantics#ofSelectInto}). A permanent table, or a global {@code ##t} other sessions see,
10652         * may be altered by code the analysis never reads. {@link DlineageUtil#isTempTable} knows only SQL Server's
10653         * {@code #t}, and must stay so: it also drops a table when its routine ends, which a SAP IQ {@code #} table
10654         * outlives (Mantis 4715).
10655         */
10656        private boolean selectIntoFixesColumns(Table tableModel, TObjectName tableName) {
10657                return DlineageUtil.isTempTable(tableModel, option.getVendor()) || TemporaryTableSemantics
10658                                .isTransient(TemporaryTableSemantics.ofSelectInto(option.getVendor(), tableName));
10659        }
10660
10661        /** Whether the metadata describes {@code table} with a star it could not resolve, so not every column is known. */
10662        private static boolean metadataHoldsUnresolvedStar(Table table) {
10663                TSQLEnv sqlEnv = ModelBindingManager.getGlobalSQLEnv();
10664                TSQLTable metadata = sqlEnv == null ? null : sqlEnv.searchTable(ModelFactory.getQualifiedTableName(table));
10665                if (metadata == null || metadata.getColumnList() == null) {
10666                        return false;
10667                }
10668                for (TSQLColumn column : metadata.getColumnList()) {
10669                        if (column.isUnresolvedStar()) {
10670                                return true;
10671                        }
10672                }
10673                return false;
10674        }
10675
10676        /** The column of {@code columns} that is {@code column} under the identifier rules, or null. */
10677        private static TableColumn namesake(TableColumn column, List<TableColumn> columns) {
10678                for (TableColumn candidate : columns) {
10679                        boolean same = column.getIdentifierKey() != null && candidate.getIdentifierKey() != null
10680                                        ? column.getIdentifierKey().equals(candidate.getIdentifierKey())
10681                                        // names that are no identifier (a STRUCT path) compare as column paths, as matching a reference does
10682                                        : DlineageUtil.sameColumnPath(column.getName(), candidate.getName());
10683                        if (same) {
10684                                return candidate;
10685                        }
10686                }
10687                return null;
10688        }
10689
10690        private void analyzeAlterTableStmt(TAlterTableStatement stmt) {
10691                TTable oldNameTable = stmt.getTargetTable();
10692                if (oldNameTable == null) {
10693                        return;
10694                }
10695
10696                Table oldNameTableModel = modelFactory.createTable(oldNameTable);
10697                // an ALTER can drop, add or reorder columns, so the SELECT ... INTO's column order no longer holds
10698                selectIntoColumnLists.remove(oldNameTableModel);
10699                selectIntoAlternatives.remove(oldNameTableModel);
10700                TObjectName oldStarColumn = DlineageUtil.syntheticStarName();
10701                TableColumn oldTableStarColumn = modelFactory.createTableColumn(oldNameTableModel, oldStarColumn, true);
10702
10703                for (int i = 0; stmt.getAlterTableOptionList() != null && i < stmt.getAlterTableOptionList().size(); i++) {
10704                        TAlterTableOption option = stmt.getAlterTableOptionList().getAlterTableOption(i);
10705                        if (option.getOptionType() == EAlterTableOptionType.RenameTable
10706                                        || option.getOptionType() == EAlterTableOptionType.swapWith) {
10707                                TObjectName newTableName = option.getNewTableName();
10708                                Stack<TParseTreeNode> list = newTableName.getStartToken().getNodesStartFromThisToken();
10709                                boolean containsTable = false;
10710                                for (int j = 0; j < list.size(); j++) {
10711                                        if (list.get(j) instanceof TTable) {
10712                                                TTable newTableTable = (TTable) list.get(j);
10713                                                Table newNameTableModel = modelFactory.createTable(newTableTable);
10714                                                newNameTableModel.setStarStmt("rename_table");
10715
10716                                                TObjectName newStarColumn = DlineageUtil.syntheticStarName();
10717                                                TableColumn newTableStarColumn = modelFactory.createTableColumn(newNameTableModel,
10718                                                                newStarColumn, true);
10719
10720                                                Process process = modelFactory.createProcess(stmt);
10721                                                newNameTableModel.addProcess(process);
10722                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
10723                                                relation.setEffectType(
10724                                                                option.getOptionType() == EAlterTableOptionType.RenameTable ? EffectType.rename_table
10725                                                                                : EffectType.swap_table);                                               
10726                                                if (option.getOptionType() == EAlterTableOptionType.RenameTable) {
10727                                                        relation.setTarget(new TableColumnRelationshipElement(newTableStarColumn));
10728                                                        relation.addSource(new TableColumnRelationshipElement(oldTableStarColumn));
10729                                                } else if (option.getOptionType() == EAlterTableOptionType.swapWith) {
10730                                                        relation.setTarget(new TableColumnRelationshipElement(oldTableStarColumn));
10731                                                        relation.addSource(new TableColumnRelationshipElement(newTableStarColumn));
10732                                                }
10733                                                relation.setProcess(process);
10734                                                containsTable = true;
10735
10736                                                if ((oldNameTableModel.isCreateTable() || oldNameTableModel.hasSQLEnv())) {
10737                                                        oldTableStarColumn.setShowStar(false);
10738                                                        relation.setShowStarRelation(false);
10739                                                }
10740
10741                                                if ((newNameTableModel.isCreateTable() || newNameTableModel.hasSQLEnv())) {
10742                                                        newTableStarColumn.setShowStar(false);
10743                                                        relation.setShowStarRelation(false);
10744                                                }
10745                                        }
10746                                }
10747                                if (!containsTable) {
10748                                        Table newNameTableModel = modelFactory.createTableByName(newTableName);
10749                                        newNameTableModel.setStarStmt("rename_table");
10750                                        TObjectName newStarColumn = DlineageUtil.syntheticStarName();
10751                                        TableColumn newTableStarColumn = modelFactory.createTableColumn(newNameTableModel, newStarColumn,
10752                                                        true);
10753
10754                                        Process process = modelFactory.createProcess(stmt);
10755                                        newNameTableModel.addProcess(process);
10756                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
10757                                        relation.setEffectType(
10758                                                        option.getOptionType() == EAlterTableOptionType.RenameTable ? EffectType.rename_table
10759                                                                        : EffectType.swap_table);
10760                                        if (option.getOptionType() == EAlterTableOptionType.RenameTable) {
10761                                                relation.setTarget(new TableColumnRelationshipElement(newTableStarColumn));
10762                                                relation.addSource(new TableColumnRelationshipElement(oldTableStarColumn));
10763                                        } else if (option.getOptionType() == EAlterTableOptionType.swapWith) {
10764                                                relation.setTarget(new TableColumnRelationshipElement(oldTableStarColumn));
10765                                                relation.addSource(new TableColumnRelationshipElement(newTableStarColumn));
10766                                        }
10767                                        relation.setProcess(process);
10768                                        if ((oldNameTableModel.isCreateTable() || oldNameTableModel.hasSQLEnv())) {
10769                                                oldTableStarColumn.setShowStar(false);
10770                                                relation.setShowStarRelation(false);
10771                                        }
10772
10773                                        if ((newNameTableModel.isCreateTable() || newNameTableModel.hasSQLEnv())) {
10774                                                newTableStarColumn.setShowStar(false);
10775                                                relation.setShowStarRelation(false);
10776                                        }
10777
10778                                }
10779                        }
10780                        else if (option.getOptionType() == EAlterTableOptionType.appendFrom) {
10781                                TObjectName newTableName = option.getSourceTableName();
10782                                Stack<TParseTreeNode> list = newTableName.getStartToken().getNodesStartFromThisToken();
10783                                boolean containsTable = false;
10784                                for (int j = 0; j < list.size(); j++) {
10785                                        if (list.get(j) instanceof TTable) {
10786                                                TTable newTableTable = (TTable) list.get(j);
10787                                                Table newNameTableModel = modelFactory.createTable(newTableTable);
10788                                                newNameTableModel.setStarStmt("append_from");
10789
10790                                                TObjectName newStarColumn = DlineageUtil.syntheticStarName();
10791                                                TableColumn newTableStarColumn = modelFactory.createTableColumn(newNameTableModel,
10792                                                                newStarColumn, true);
10793
10794                                                Process process = modelFactory.createProcess(stmt);
10795                                                newNameTableModel.addProcess(process);
10796                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
10797                                                relation.setEffectType(EffectType.append_from);
10798                                                relation.setTarget(new TableColumnRelationshipElement(oldTableStarColumn));
10799                                                relation.addSource(new TableColumnRelationshipElement(newTableStarColumn));
10800                                                relation.setProcess(process);
10801                                                containsTable = true;
10802
10803                                                if ((oldNameTableModel.isCreateTable() || oldNameTableModel.hasSQLEnv())) {
10804                                                        oldTableStarColumn.setShowStar(false);
10805                                                        relation.setShowStarRelation(false);
10806                                                }
10807
10808                                                if ((newNameTableModel.isCreateTable() || newNameTableModel.hasSQLEnv())) {
10809                                                        newTableStarColumn.setShowStar(false);
10810                                                        relation.setShowStarRelation(false);
10811                                                }
10812                                        }
10813                                }
10814                                if (!containsTable) {
10815                                        Table newNameTableModel = modelFactory.createTableByName(newTableName);
10816                                        newNameTableModel.setStarStmt("append_from");
10817                                        TObjectName newStarColumn = DlineageUtil.syntheticStarName();
10818                                        TableColumn newTableStarColumn = modelFactory.createTableColumn(newNameTableModel, newStarColumn,
10819                                                        true);
10820
10821                                        Process process = modelFactory.createProcess(stmt);
10822                                        newNameTableModel.addProcess(process);
10823                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
10824                                        relation.setEffectType(EffectType.append_from);
10825                                        relation.setTarget(new TableColumnRelationshipElement(oldTableStarColumn));
10826                                        relation.addSource(new TableColumnRelationshipElement(newTableStarColumn));
10827                                        relation.setProcess(process);
10828                                        if ((oldNameTableModel.isCreateTable() || oldNameTableModel.hasSQLEnv())) {
10829                                                oldTableStarColumn.setShowStar(false);
10830                                                relation.setShowStarRelation(false);
10831                                        }
10832
10833                                        if ((newNameTableModel.isCreateTable() || newNameTableModel.hasSQLEnv())) {
10834                                                newTableStarColumn.setShowStar(false);
10835                                                relation.setShowStarRelation(false);
10836                                        }
10837
10838                                }
10839                        }
10840                        else if (option.getOptionType() == EAlterTableOptionType.exchangePartition) {
10841                                TObjectName newTableName = option.getNewTableName();
10842                                Stack<TParseTreeNode> list = newTableName.getStartToken().getNodesStartFromThisToken();
10843                                boolean containsTable = false;
10844                                for (int j = 0; j < list.size(); j++) {
10845                                        if (list.get(j) instanceof TTable) {
10846                                                TTable newTableTable = (TTable) list.get(j);
10847                                                Table newNameTableModel = modelFactory.createTable(newTableTable);
10848                                                newNameTableModel.setStarStmt("exchange_partition");
10849
10850                                                TObjectName newStarColumn = DlineageUtil.syntheticStarName();
10851                                                TableColumn newTableStarColumn = modelFactory.createTableColumn(newNameTableModel,
10852                                                                newStarColumn, true);
10853
10854                                                Process process = modelFactory.createProcess(stmt);
10855                                                newNameTableModel.addProcess(process);
10856                                                {
10857                                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
10858                                                        relation.setEffectType(EffectType.exchange_partition);
10859                                                        relation.setTarget(new TableColumnRelationshipElement(oldTableStarColumn));
10860                                                        relation.addSource(new TableColumnRelationshipElement(newTableStarColumn));
10861                                                        relation.setProcess(process);
10862                                                        if (option.getPartitionName() != null) {
10863                                                                relation.setPartition(option.getPartitionName().toString());
10864                                                        }
10865                                                        if ((oldNameTableModel.isCreateTable() || oldNameTableModel.hasSQLEnv())) {
10866                                                                oldTableStarColumn.setShowStar(false);
10867                                                                relation.setShowStarRelation(false);
10868                                                        }
10869
10870                                                        if ((newNameTableModel.isCreateTable() || newNameTableModel.hasSQLEnv())) {
10871                                                                newTableStarColumn.setShowStar(false);
10872                                                                relation.setShowStarRelation(false);
10873                                                        }
10874                                                }
10875                                                {
10876                                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
10877                                                        relation.setEffectType(EffectType.exchange_partition);
10878                                                        relation.setTarget(new TableColumnRelationshipElement(newTableStarColumn));
10879                                                        relation.addSource(new TableColumnRelationshipElement(oldTableStarColumn));
10880                                                        relation.setProcess(process);
10881                                                        if (option.getPartitionName() != null) {
10882                                                                relation.setPartition(option.getPartitionName().toString());
10883                                                        }
10884                                                        if ((oldNameTableModel.isCreateTable() || oldNameTableModel.hasSQLEnv())) {
10885                                                                oldTableStarColumn.setShowStar(false);
10886                                                                relation.setShowStarRelation(false);
10887                                                        }
10888
10889                                                        if ((newNameTableModel.isCreateTable() || newNameTableModel.hasSQLEnv())) {
10890                                                                newTableStarColumn.setShowStar(false);
10891                                                                relation.setShowStarRelation(false);
10892                                                        }
10893                                                }
10894                                                containsTable = true;   
10895                                        }
10896                                }
10897                                if (!containsTable) {
10898                                        Table newNameTableModel = modelFactory.createTableByName(newTableName);
10899                                        newNameTableModel.setStarStmt("exchange_partition");
10900                                        TObjectName newStarColumn = DlineageUtil.syntheticStarName();
10901                                        TableColumn newTableStarColumn = modelFactory.createTableColumn(newNameTableModel, newStarColumn,
10902                                                        true);
10903
10904                                        Process process = modelFactory.createProcess(stmt);
10905                                        newNameTableModel.addProcess(process);
10906                                        {
10907                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
10908                                                relation.setEffectType(EffectType.exchange_partition);
10909                                                relation.setTarget(new TableColumnRelationshipElement(oldTableStarColumn));
10910                                                relation.addSource(new TableColumnRelationshipElement(newTableStarColumn));
10911                                                if (option.getPartitionName() != null) {
10912                                                        relation.setPartition(option.getPartitionName().toString());
10913                                                }
10914                                                relation.setProcess(process);
10915                                                if ((oldNameTableModel.isCreateTable() || oldNameTableModel.hasSQLEnv())) {
10916                                                        oldTableStarColumn.setShowStar(false);
10917                                                        relation.setShowStarRelation(false);
10918                                                }
10919
10920                                                if ((newNameTableModel.isCreateTable() || newNameTableModel.hasSQLEnv())) {
10921                                                        newTableStarColumn.setShowStar(false);
10922                                                        relation.setShowStarRelation(false);
10923                                                }
10924                                        }
10925                                        {
10926                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
10927                                                relation.setEffectType(EffectType.exchange_partition);
10928                                                relation.setTarget(new TableColumnRelationshipElement(newTableStarColumn));
10929                                                relation.addSource(new TableColumnRelationshipElement(oldTableStarColumn));
10930                                                if (option.getPartitionName() != null) {
10931                                                        relation.setPartition(option.getPartitionName().toString());
10932                                                }
10933                                                relation.setProcess(process);
10934                                                if ((oldNameTableModel.isCreateTable() || oldNameTableModel.hasSQLEnv())) {
10935                                                        oldTableStarColumn.setShowStar(false);
10936                                                        relation.setShowStarRelation(false);
10937                                                }
10938
10939                                                if ((newNameTableModel.isCreateTable() || newNameTableModel.hasSQLEnv())) {
10940                                                        newTableStarColumn.setShowStar(false);
10941                                                        relation.setShowStarRelation(false);
10942                                                }
10943                                        }
10944                                }
10945                        }
10946                        else if (option.getOptionType() == EAlterTableOptionType.switchPartition) {
10947                                analyzeSwitchPartition(stmt, option, oldNameTableModel, oldTableStarColumn);
10948                        }
10949                        else if(option.getOptionType() == EAlterTableOptionType.setLocation) {
10950                                TObjectName location = option.getTableLocation();
10951                                Process process = modelFactory.createProcess(stmt);
10952                                process.setType("Set Table Location");
10953                                oldNameTableModel.addProcess(process);
10954                                Table uriFile = modelFactory.createTableByName(location, true);
10955                                uriFile.setPath(true);
10956                                for (int j = 0; j < oldNameTableModel.getColumns().size(); j++) {
10957                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
10958                                        TObjectName fileUri = DlineageUtil.syntheticStarName();
10959                                        TableColumn fileUriColumn = modelFactory.createFileUri(uriFile, fileUri);
10960                                        relation.addSource(new TableColumnRelationshipElement(fileUriColumn));
10961                                        relation.setTarget(new TableColumnRelationshipElement(oldNameTableModel.getColumns().get(j)));
10962                                        relation.setProcess(process);
10963                                }
10964                        }
10965                        else if (option.getOptionType() == EAlterTableOptionType.AddColumn
10966                                        || option.getOptionType() == EAlterTableOptionType.addColumnIfNotExists) {
10967                                if (option.getColumnDefinitionList() != null) {
10968                                        for (TColumnDefinition column : option.getColumnDefinitionList()) {
10969                                                if (column != null && column.getColumnName() != null) {
10970                                                        List<TableColumn> columnsBefore = new ArrayList<TableColumn>(oldNameTableModel.getColumns());
10971                                                        TableColumn tableColumn = modelFactory.createTableColumn(oldNameTableModel, column.getColumnName(), true);
10972                                                        if(this.option.getAnalyzeMode() == AnalyzeMode.crud) {
10973                                                                CrudRelationship crudRelationship = modelFactory.createCrudRelation();
10974                                                                crudRelationship.setTarget(new TableColumnRelationshipElement(tableColumn));
10975                                                                crudRelationship.setEffectType(EffectType.add_table_column);
10976                                                        }
10977                                                        // the added column's fields, as a CREATE TABLE declaring the column makes them; a column the
10978                                                        // table already had (ADD COLUMN IF NOT EXISTS) keeps its own (Mantis 4846)
10979                                                        if (tableColumn != null && nestedProfile().buildsDeclaredTree()
10980                                                                        && !containsSame(columnsBefore, tableColumn)) {
10981                                                                createStructFieldNodes(tableColumn, column.getDatatype(),
10982                                                                                new ArrayList<gudusoft.gsqlparser.common.structured.StructuredPathSegment>(),
10983                                                                                column.getColumnName(), new ArrayList<TableColumn>());
10984                                                        }
10985                                                }
10986                                        }
10987                                }
10988                        }
10989                        else if (option.getOptionType() == EAlterTableOptionType.DropColumn && this.option.getAnalyzeMode() == AnalyzeMode.crud) {
10990                                if (option.getColumnNameList() != null) {
10991                                        for (TObjectName column : option.getColumnNameList()) {
10992                                                TableColumn tableColumn = modelFactory.createTableColumn(oldNameTableModel, column, true);
10993                                                CrudRelationship crudRelationship = modelFactory.createCrudRelation();
10994                                                crudRelationship.setTarget(new TableColumnRelationshipElement(tableColumn));
10995                                                crudRelationship.setEffectType(EffectType.drop_table_column);
10996                                        }
10997                                }
10998                        }
10999                        else if(option.getOptionType() == EAlterTableOptionType.AddConstraint || option.getOptionType() == EAlterTableOptionType.AddConstraintFK
11000                                        || option.getOptionType() == EAlterTableOptionType.AddConstraintPK || option.getOptionType() == EAlterTableOptionType.AddConstraintUnique
11001                                        || option.getOptionType() == EAlterTableOptionType.AddConstraintIndex){
11002                                if (option.getTableConstraint() != null) {
11003                                        TConstraint alertTableConstraint = option.getTableConstraint();
11004                                        TPTNodeList<TColumnWithSortOrder> keyNames = alertTableConstraint.getColumnList();
11005                                        if (keyNames == null) {
11006                                                continue;
11007                                        }
11008                                        for (int k = 0; k < keyNames.size(); k++) {
11009                                                TObjectName keyName = keyNames.getElement(k).getColumnName();
11010                                                TObjectName referencedTableName = alertTableConstraint.getReferencedObject();
11011                                                Table tableModel = modelFactory.createTableByName(stmt.getTableName());
11012                                                TableColumn tableConstraint = modelFactory.createTableColumn(tableModel, keyName, true);
11013                                                if(alertTableConstraint.getConstraint_type() == EConstraintType.primary_key){
11014                                                        tableConstraint.setPrimaryKey(true);
11015                                                }
11016                                                else if(alertTableConstraint.getConstraint_type() == EConstraintType.table_index){
11017                                                        tableConstraint.setIndexKey(true);
11018                                                }
11019                                                else if(alertTableConstraint.getConstraint_type() == EConstraintType.unique){
11020                                                        tableConstraint.setUnqiueKey(true);
11021                                                }
11022                                                else if (alertTableConstraint.getConstraint_type() == EConstraintType.foreign_key) {
11023                                                        tableConstraint.setForeignKey(true);
11024                                                        Table referencedTable = modelManager.getTableByName(DlineageUtil.getTableIdentityKey(referencedTableName.toString()));
11025                                                        if (referencedTable == null) {
11026                                                                referencedTable = modelFactory.createTableByName(referencedTableName);
11027                                                        }
11028                                                        TObjectNameList referencedTableColumns = alertTableConstraint.getReferencedColumnList();
11029                                                        if (k == keyNames.size() - 1) {
11030                                                                captureCompositeForeignKey(tableModel, keyNames, referencedTable,
11031                                                                                referencedTableColumns);
11032                                                        }
11033                                                        if (referencedTableColumns != null) {
11034                                                                for (int j = 0; j < referencedTableColumns.size(); j++) {
11035                                                                        TableColumn tableColumn = modelFactory.createTableColumn(referencedTable,
11036                                                                                        referencedTableColumns.getObjectName(j), false);
11037                                                                        if (tableColumn != null) {
11038                                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
11039                                                                                relation.addSource(new TableColumnRelationshipElement(tableColumn));
11040                                                                                relation.setTarget(new TableColumnRelationshipElement(tableConstraint));
11041                                                                                relation.setEffectType(EffectType.foreign_key);
11042                                                                                Process process = modelFactory.createProcess(stmt);
11043                                                                                relation.setProcess(process);
11044                                                                                if(this.option.isShowERDiagram()){
11045                                                                                        ERRelationship erRelation = modelFactory.createERRelation();
11046                                                                                        erRelation.addSource(new TableColumnRelationshipElement(tableColumn));
11047                                                                                        erRelation.setTarget(new TableColumnRelationshipElement(tableConstraint));
11048                                                                                }
11049                                                                        }
11050                                                                }
11051                                                        }
11052                                                        else{
11053                                                                TableColumn tableColumn = modelFactory.createTableColumn(referencedTable, keyName, false);
11054                                                                if (tableColumn != null) {
11055                                                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
11056                                                                        relation.addSource(new TableColumnRelationshipElement(tableColumn));
11057                                                                        relation.setTarget(new TableColumnRelationshipElement(tableConstraint));
11058                                                                        relation.setEffectType(EffectType.foreign_key);
11059                                                                        if(this.option.isShowERDiagram()){
11060                                                                                ERRelationship erRelation = modelFactory.createERRelation();
11061                                                                                erRelation.addSource(new TableColumnRelationshipElement(tableColumn));
11062                                                                                erRelation.setTarget(new TableColumnRelationshipElement(tableConstraint));
11063                                                                        }
11064                                                                }
11065                                                        }
11066                                                }
11067                                        }
11068                                }
11069                                else if (option.getConstraintList() != null) {
11070                                        for(int iCons=0; iCons<option.getConstraintList().size(); iCons++){
11071                                                TConstraint alertTableConstraint = option.getConstraintList().getConstraint(iCons);
11072                                                TPTNodeList<TColumnWithSortOrder> keyNames = alertTableConstraint.getColumnList();
11073                                                if(keyNames != null){
11074                                                        for (int k = 0; k < keyNames.size(); k++) {
11075                                                                TObjectName keyName = keyNames.getElement(k).getColumnName();
11076                                                                TObjectName referencedTableName = alertTableConstraint.getReferencedObject();
11077                                                                Table tableModel = modelFactory.createTableByName(stmt.getTableName());
11078                                                                TableColumn tableConstraint = modelFactory.createTableColumn(tableModel, keyName, true);
11079                                                                if(alertTableConstraint.getConstraint_type() == EConstraintType.primary_key){
11080                                                                        tableConstraint.setPrimaryKey(true);
11081                                                                }
11082                                                                else if(alertTableConstraint.getConstraint_type() == EConstraintType.table_index){
11083                                                                        tableConstraint.setIndexKey(true);
11084                                                                }
11085                                                                else if(alertTableConstraint.getConstraint_type() == EConstraintType.unique){
11086                                                                        tableConstraint.setUnqiueKey(true);
11087                                                                }
11088                                                                else if (alertTableConstraint.getConstraint_type() == EConstraintType.foreign_key) {
11089                                                                        tableConstraint.setForeignKey(true);
11090                                                                        Table referencedTable = modelManager.getTableByName(DlineageUtil.getTableIdentityKey(referencedTableName.toString()));
11091                                                                        if (referencedTable == null) {
11092                                                                                referencedTable = modelFactory.createTableByName(referencedTableName);
11093                                                                        }
11094                                                                        TObjectNameList referencedTableColumns = alertTableConstraint.getReferencedColumnList();
11095                                                                        if (k == keyNames.size() - 1) {
11096                                                                                captureCompositeForeignKey(tableModel, keyNames, referencedTable,
11097                                                                                                referencedTableColumns);
11098                                                                        }
11099                                                                        if (referencedTableColumns != null) {
11100                                                                                for (int j = 0; j < referencedTableColumns.size(); j++) {
11101                                                                                        TableColumn tableColumn = modelFactory.createTableColumn(referencedTable,
11102                                                                                                        referencedTableColumns.getObjectName(j), false);
11103                                                                                        if (tableColumn != null) {
11104                                                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
11105                                                                                                relation.addSource(new TableColumnRelationshipElement(tableColumn));
11106                                                                                                relation.setTarget(new TableColumnRelationshipElement(tableConstraint));
11107                                                                                                relation.setEffectType(EffectType.foreign_key);
11108                                                                                                Process process = modelFactory.createProcess(stmt);
11109                                                                                                relation.setProcess(process);
11110                                                                                                if(this.option.isShowERDiagram()){
11111                                                                                                        ERRelationship erRelation = modelFactory.createERRelation();
11112                                                                                                        erRelation.addSource(new TableColumnRelationshipElement(tableColumn));
11113                                                                                                        erRelation.setTarget(new TableColumnRelationshipElement(tableConstraint));
11114                                                                                                }
11115                                                                                        }
11116                                                                                }
11117                                                                        }
11118                                                                        else{
11119                                                                                TableColumn tableColumn = modelFactory.createTableColumn(referencedTable, keyName, false);
11120                                                                                if (tableColumn != null) {
11121                                                                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
11122                                                                                        relation.addSource(new TableColumnRelationshipElement(tableColumn));
11123                                                                                        relation.setTarget(new TableColumnRelationshipElement(tableConstraint));
11124                                                                                        relation.setEffectType(EffectType.foreign_key);
11125                                                                                        Process process = modelFactory.createProcess(stmt);
11126                                                                                        relation.setProcess(process);
11127                                                                                        if(this.option.isShowERDiagram()){
11128                                                                                                ERRelationship erRelation = modelFactory.createERRelation();
11129                                                                                                erRelation.addSource(new TableColumnRelationshipElement(tableColumn));
11130                                                                                                erRelation.setTarget(new TableColumnRelationshipElement(tableConstraint));
11131                                                                                        }
11132                                                                                }
11133                                                                        }
11134                                                                }
11135                                                        }
11136                                                }
11137                                        }
11138                                }
11139                                else if (option.getIndexCols() != null){
11140                                        TPTNodeList<TColumnWithSortOrder> keyNames = option.getIndexCols();
11141                                        for (int k = 0; k < keyNames.size(); k++) {
11142                                                TObjectName keyName = keyNames.getElement(k).getColumnName();
11143                                                Table tableModel = modelFactory.createTableByName(stmt.getTableName());
11144                                                TableColumn tableConstraint = modelFactory.createTableColumn(tableModel, keyName, true);
11145                                                if(option.getOptionType() == EAlterTableOptionType.AddConstraintPK){
11146                                                        tableConstraint.setPrimaryKey(true);
11147                                                }
11148                                                else if(option.getOptionType() == EAlterTableOptionType.AddConstraintIndex){
11149                                                        tableConstraint.setIndexKey(true);
11150                                                }
11151                                                else if(option.getOptionType() == EAlterTableOptionType.AddConstraintUnique){
11152                                                        tableConstraint.setUnqiueKey(true);
11153                                                }
11154                                                else if (option.getOptionType() == EAlterTableOptionType.AddConstraintFK) {
11155                                                        TObjectName referencedTableName = option.getReferencedObjectName();
11156                                                        tableConstraint.setForeignKey(true);
11157                                                        Table referencedTable = modelManager.getTableByName(DlineageUtil.getTableIdentityKey(referencedTableName.toString()));
11158                                                        if (referencedTable == null) {
11159                                                                referencedTable = modelFactory.createTableByName(referencedTableName);
11160                                                        }
11161                                                        TObjectNameList referencedTableColumns = option.getReferencedColumnList();
11162                                                        if (k == keyNames.size() - 1) {
11163                                                                captureCompositeForeignKey(tableModel, keyNames, referencedTable,
11164                                                                                referencedTableColumns);
11165                                                        }
11166                                                        if (referencedTableColumns != null) {
11167                                                                for (int j = 0; j < referencedTableColumns.size(); j++) {
11168                                                                        TableColumn tableColumn = modelFactory.createTableColumn(referencedTable,
11169                                                                                        referencedTableColumns.getObjectName(j), false);
11170                                                                        if (tableColumn != null) {
11171                                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
11172                                                                                relation.addSource(new TableColumnRelationshipElement(tableColumn));
11173                                                                                relation.setTarget(new TableColumnRelationshipElement(tableConstraint));
11174                                                                                relation.setEffectType(EffectType.foreign_key);
11175                                                                                Process process = modelFactory.createProcess(stmt);
11176                                                                                relation.setProcess(process);
11177                                                                                if(this.option.isShowERDiagram()){
11178                                                                                        ERRelationship erRelation = modelFactory.createERRelation();
11179                                                                                        erRelation.addSource(new TableColumnRelationshipElement(tableColumn));
11180                                                                                        erRelation.setTarget(new TableColumnRelationshipElement(tableConstraint));
11181                                                                                }
11182                                                                        }
11183                                                                }
11184                                                        }
11185                                                        else{
11186                                                                TableColumn tableColumn = modelFactory.createTableColumn(referencedTable, keyName, false);
11187                                                                if (tableColumn != null) {
11188                                                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
11189                                                                        relation.addSource(new TableColumnRelationshipElement(tableColumn));
11190                                                                        relation.setTarget(new TableColumnRelationshipElement(tableConstraint));
11191                                                                        relation.setEffectType(EffectType.foreign_key);
11192                                                                        Process process = modelFactory.createProcess(stmt);
11193                                                                        relation.setProcess(process);
11194                                                                        if(this.option.isShowERDiagram()){
11195                                                                                ERRelationship erRelation = modelFactory.createERRelation();
11196                                                                                erRelation.addSource(new TableColumnRelationshipElement(tableColumn));
11197                                                                                erRelation.setTarget(new TableColumnRelationshipElement(tableConstraint));
11198                                                                        }
11199                                                                }
11200                                                        }
11201                                                }
11202                                        }
11203                                }
11204                        }
11205                }
11206        }
11207
11208        private void analyzeAlterViewStmt(TAlterViewStatement stmt) {
11209                if (stmt.getAlterViewOption() == EAlterViewOption.asSelect) {
11210                        analyzeCreateViewStmt(stmt, stmt.getSelectSqlStatement(), null, stmt.getViewName());
11211                } else {
11212                        throw new UnsupportedOperationException("Can't handle this alter view statement case, alter option = "+ stmt.getAlterViewOption().name());
11213                }
11214        }
11215        
11216        private void analyzeDeleteStmt(TDeleteSqlStatement stmt) {
11217                TTable targetTable = stmt.getTargetTable();
11218                if (targetTable == null)
11219                        return;
11220
11221                TTable table = targetTable;
11222                Table tableModel = null;
11223                TSelectSqlStatement projectedSubquery = null;
11224                TObjectNameList exposedColumns = null;
11225                if (targetTable.getCTE() != null) {
11226                        projectedSubquery = targetTable.getCTE().getSubquery();
11227                        exposedColumns = targetTable.getCTE().getColumnList();
11228                } else if (targetTable.getLinkTable() != null
11229                                && targetTable.getLinkTable().getSubquery() != null) {
11230                        projectedSubquery = targetTable.getLinkTable().getSubquery();
11231                        exposedColumns = getProjectedTargetColumns(targetTable.getLinkTable());
11232                } else if (targetTable.getSubquery() != null) {
11233                        projectedSubquery = targetTable.getSubquery();
11234                        exposedColumns = getProjectedTargetColumns(targetTable);
11235                }
11236
11237                if (projectedSubquery != null) {
11238                        CteWriteTargetResolution resolution = resolveCteDeleteTarget(projectedSubquery);
11239                        if (resolution.isWritable()) {
11240                                analyzeProjectedWriteTarget(targetTable, projectedSubquery, exposedColumns, null);
11241                                table = resolution.baseTable;
11242                                tableModel = modelFactory.createTable(table);
11243                                createProjectedWriteImpact(projectedSubquery, tableModel, EffectType.delete, null);
11244                        } else {
11245                                analyzeProjectedWriteTarget(targetTable, projectedSubquery, exposedColumns, null);
11246                                addCteWriteTargetHint(targetTable, resolution.failureReason);
11247                        }
11248                } else {
11249                        tableModel = modelFactory.createTable(table);
11250                }
11251                if (tableModel != null && getTableLinkedColumns(table) != null
11252                                && getTableLinkedColumns(table).size() > 0) {
11253                        for (int j = 0; j < getTableLinkedColumns(table).size(); j++) {
11254                                TObjectName object = getTableLinkedColumns(table).getObjectName(j);
11255
11256                                if (object.getDbObjectType() == EDbObjectType.variable) {
11257                                        continue;
11258                                }
11259
11260                                if (object.getColumnNameOnly().startsWith("@")
11261                                                && (option.getVendor() == EDbVendor.dbvmssql || option.getVendor() == EDbVendor.dbvazuresql)) {
11262                                        continue;
11263                                }
11264
11265                                if (object.getColumnNameOnly().startsWith(":")
11266                                                && (option.getVendor() == EDbVendor.dbvhana || option.getVendor() == EDbVendor.dbvteradata)) {
11267                                        continue;
11268                                }
11269
11270                                if (!isBuiltInFunctionName(object)) {
11271                                        if (object.getSourceTable() == null || object.getSourceTable() == table) {
11272                                                modelFactory.createTableColumn(tableModel, object, false);
11273                                        }
11274                                }
11275                        }
11276                }
11277
11278                if(option.getAnalyzeMode() == AnalyzeMode.crud && tableModel != null) {
11279                        CrudRelationship crudRelationship = modelFactory.createCrudRelation();
11280                        crudRelationship.setTarget(new TableRelationshipElement(tableModel));
11281                        crudRelationship.setEffectType(EffectType.delete);
11282                }
11283                
11284                if (stmt.getWhereClause() != null && stmt.getWhereClause().getCondition() != null) {
11285                        analyzeFilterCondition(tableModel, stmt.getWhereClause().getCondition(), null, JoinClauseType.where,
11286                                        EffectType.delete);
11287                }
11288        }
11289
11290        private TObjectName getProcedureName(TStoredProcedureSqlStatement stmt) {
11291                if (stmt instanceof TTeradataCreateProcedure) {
11292                        return ((TTeradataCreateProcedure) stmt).getProcedureName();
11293                }
11294                return DlineageUtil.getProcedureOrFunctionName(stmt);
11295        }
11296
11297        private void analyzePlsqlCreatePackage(TPlsqlCreatePackage stmt) {
11298                TObjectName procedureName = getProcedureName(stmt);
11299                OraclePackage oraclePackage;
11300                if (procedureName != null) {
11301                        RoutineAliasKey[] packageKeys = ModelFactory.packageAliasKeys(stmt);
11302                        RoutineAliasKey packageKey = packageKeys == null ? null : packageKeys[0];
11303                        if (this.modelManager.getOraclePackage(packageKey) == null) {
11304                                oraclePackage = this.modelFactory.createOraclePackage(stmt);
11305
11306                                if (stmt.getParameterDeclarations() != null) {
11307                                        TParameterDeclarationList parameters = stmt.getParameterDeclarations();
11308
11309                                        for (int i = 0; i < parameters.size(); ++i) {
11310                                                TParameterDeclaration parameter = parameters.getParameterDeclarationItem(i);
11311                                                if (parameter.getParameterName() != null) {
11312                                                        this.modelFactory.createProcedureArgument(oraclePackage, parameter, i + 1);
11313                                                } else if (parameter.getDataType() != null) {
11314                                                        this.modelFactory.createProcedureArgument(oraclePackage, parameter, i + 1);
11315                                                }
11316                                        }
11317                                }
11318
11319                                ModelBindingManager.setGlobalOraclePackage(oraclePackage);
11320                        } else {
11321                                ModelBindingManager.setGlobalOraclePackage(this.modelManager.getOraclePackage(packageKey));
11322                        }
11323                        
11324                        try {
11325                                if (stmt.getDeclareStatements() != null) {
11326                                        // Register member identities before analyzing bodies: an orchestrator often
11327                                        // precedes its callees. This does not visit or execute any member body.
11328                                        if (option.getVendor() == EDbVendor.dbvoracle) {
11329                                                for (int i = 0; i < stmt.getDeclareStatements().size(); ++i) {
11330                                                        TCustomSqlStatement member = stmt.getDeclareStatements().get(i);
11331                                                        if (member instanceof TStoredProcedureSqlStatement) {
11332                                                                TObjectName memberName = getProcedureName((TStoredProcedureSqlStatement) member);
11333                                                                if (memberName != null && member.sqlstatementtype.name().contains("procedure")
11334                                                                                && DlineageUtil.isProcedureExcluded(memberName.toString())) continue;
11335                                                                Procedure model = modelFactory.createProcedure((TStoredProcedureSqlStatement) member);
11336                                                                if (model != null) model.setParentPackage(ModelBindingManager.getGlobalOraclePackage());
11337                                                        }
11338                                                }
11339                                        }
11340                                        for (int i = 0; i < stmt.getDeclareStatements().size(); ++i) {
11341                                                analyzeCustomSqlStmt(stmt.getDeclareStatements().get(i));
11342                                        }
11343                                }
11344                                List<Pair<TBasicStmt, Procedure>> pending = pendingOracleCallArguments.remove(
11345                                                ModelBindingManager.getGlobalOraclePackage());
11346                                if (pending != null) {
11347                                        for (Pair<TBasicStmt, Procedure> item : pending) {
11348                                                TStoredProcedureSqlStatement saved = ModelBindingManager.getGlobalProcedure();
11349                                                stmtStack.push(item.first);
11350                                                try {
11351                                                        ModelBindingManager.setGlobalProcedure(oracleRoutineParent(item.first));
11352                                                        analyzeOracleCallArguments(item.second, item.first.getExpr().getFunctionCall());
11353                                                } finally {
11354                                                        stmtStack.pop();
11355                                                        ModelBindingManager.setGlobalProcedure(saved);
11356                                                }
11357                                        }
11358                                }
11359                        } finally {
11360                                pendingOracleCallArguments.remove(ModelBindingManager.getGlobalOraclePackage());
11361                                ModelBindingManager.removeGlobalOraclePackage();
11362                        }
11363                }
11364        }
11365
11366        /**
11367         * B1 (routine-summary-scc-design §2.1a): canonical signature descriptor of
11368         * a routine DECLARATION, built once at declaration time. Null when the
11369         * statement carries no resolvable name.
11370         */
11371        private gudusoft.gsqlparser.dlineage.dynamicsql.RoutineIdentity declarationIdentityOf(
11372                        TStoredProcedureSqlStatement stmt) {
11373                TObjectName name = getProcedureName(stmt);
11374                if (name == null || name.getObjectString() == null
11375                                || name.getObjectString().length() == 0) {
11376                        return null;
11377                }
11378                boolean isFunction = stmt instanceof gudusoft.gsqlparser.stmt.TCreateFunctionStmt
11379                                || stmt instanceof gudusoft.gsqlparser.stmt.oracle.TPlsqlCreateFunction;
11380                boolean isTrigger = stmt instanceof TCreateTriggerStmt
11381                                || stmt instanceof TPlsqlCreateTrigger;
11382                String packageName = null;
11383                gudusoft.gsqlparser.dlineage.dynamicsql.RoutineKind kind;
11384                if (isTrigger) {
11385                        kind = gudusoft.gsqlparser.dlineage.dynamicsql.RoutineKind.TRIGGER;
11386                } else if (ModelBindingManager.getGlobalOraclePackage() != null) {
11387                        packageName = ModelBindingManager.getGlobalOraclePackage().getName();
11388                        kind = isFunction
11389                                        ? gudusoft.gsqlparser.dlineage.dynamicsql.RoutineKind.PACKAGE_MEMBER_FUNCTION
11390                                        : gudusoft.gsqlparser.dlineage.dynamicsql.RoutineKind.PACKAGE_MEMBER_PROCEDURE;
11391                } else {
11392                        kind = isFunction
11393                                        ? gudusoft.gsqlparser.dlineage.dynamicsql.RoutineKind.FUNCTION
11394                                        : gudusoft.gsqlparser.dlineage.dynamicsql.RoutineKind.PROCEDURE;
11395                }
11396                // mssql numbered procedures: the parser drops ";N" from the AST name;
11397                // recover it from the token stream so grp;1 / grp;2 keep DISTINCT
11398                // identity scope keys (per-overload pools — codex-B3-r2 finding 6).
11399                String overloadDiscriminator = option.getVendor() == EDbVendor.dbvmssql
11400                                ? gudusoft.gsqlparser.dlineage.dynamicsql.RoutineCatalog
11401                                                .numberedGroupAfter(name)
11402                                : null;
11403                return gudusoft.gsqlparser.dlineage.dynamicsql.RoutineIdentity.of(
11404                                option.getVendor(), kind, name.getDatabaseString(),
11405                                name.getSchemaString(), packageName, name.getObjectString(),
11406                                stmt.getParameterDeclarations(), overloadDiscriminator);
11407        }
11408
11409        private void analyzeStoredProcedureStmt(TStoredProcedureSqlStatement stmt) {
11410
11411                if (stmt instanceof TPlsqlCreatePackage) {
11412                        analyzePlsqlCreatePackage((TPlsqlCreatePackage) stmt);
11413                        return;
11414                }
11415                
11416                // An anonymous block inside a routine runs as part of that routine: its temporary tables are
11417                // the routine's, and the routine is still the one being analyzed when the block ends.
11418                TStoredProcedureSqlStatement enclosingRoutine = ModelBindingManager.getGlobalProcedure();
11419                boolean innerBlock = enclosingRoutine != null && stmt.getStoredProcedureName() == null;
11420                ModelBindingManager.setGlobalProcedure(stmt);
11421                List<Map> temporaryBindings = !innerBlock && TemporaryTableSemantics.hasTransientTables(stmt.dbvendor)
11422                                ? modelManager.enterTemporaryTableScope() : null;
11423
11424                boolean pushedIdentityScope = false;
11425                try {
11426                        Procedure procedure = null;
11427
11428                        TObjectName procedureName = getProcedureName(stmt);
11429
11430                        // Honor procedure-exclusion patterns/names for procedures defined in actual SQL
11431                        // (CREATE PROCEDURE). Previously exclusion was only applied to procedures loaded
11432                        // from metadata JSON; a CREATE PROCEDURE parsed from SQL still produced its
11433                        // procedure node plus all of its inner-statement processes and relationships
11434                        // regardless of the exclusion configuration (MantisBT 4533).
11435                        //
11436                        // analyzeStoredProcedureStmt also handles functions and triggers (every dialect's
11437                        // CREATE FUNCTION / CREATE TRIGGER class extends TStoredProcedureSqlStatement), which
11438                        // are NOT procedures and must never be dropped by procedure exclusion. Gate on the
11439                        // statement type rather than enumerating classes: across all dialects the procedure
11440                        // statement types are exactly those whose ESqlStatementType name contains "procedure"
11441                        // (e.g. sstcreateprocedure, sstoraclecreateprocedure, sstmssqlcreateprocedure),
11442                        // while function/trigger types contain "function"/"trigger".
11443                        boolean isProcedureStmt = stmt.sqlstatementtype != null
11444                                        && stmt.sqlstatementtype.name().toLowerCase(Locale.ROOT).contains("procedure");
11445                        if (procedureName != null && isProcedureStmt
11446                                        && DlineageUtil.isProcedureExcluded(procedureName.toString())) {
11447                                return;
11448                        }
11449
11450                        if (procedureName != null) {
11451                                procedure = this.modelFactory.createProcedure(stmt);
11452                                if (procedure != null) {
11453                                        modelManager.bindModel(stmt, procedure);
11454                                }
11455                                if (ModelBindingManager.getGlobalOraclePackage() != null) {
11456                                        ModelBindingManager.getGlobalOraclePackage().addProcedure(procedure);
11457                                        procedure.setParentPackage(ModelBindingManager.getGlobalOraclePackage());
11458                                        // Canonical scope key of this member's variables: the same
11459                                        // value createVariable(String) keys them under while the
11460                                        // body is analyzed (fullName/name are display forms and
11461                                        // must not be touched).
11462                                        procedure.setVariableScopeKey(DlineageUtil.getProcedureParentName(stmt));
11463                                } else if (procedure != null) {
11464                                        // A label of the routine's variable scope, its name as written (variables are keyed by
11465                                        // LocalSymbolKey; the label only names the scope, as for package members above).
11466                                        procedure.setVariableScopeKey(DlineageUtil.getProcedureParentName(stmt));
11467                                }
11468                                if (procedure != null) {
11469                                        // B1 (routine-summary-scc-design §3): additive identity
11470                                        // metadata. The identityScopeKey discriminates same-named
11471                                        // overloads; LEGACY paths never consult it (B3's
11472                                        // identity-first lookup is the consumer).
11473                                        gudusoft.gsqlparser.dlineage.dynamicsql.RoutineIdentity routineIdentity =
11474                                                        declarationIdentityOf(stmt);
11475                                        procedure.setRoutineIdentity(routineIdentity);
11476                                        if (procedure.getVariableScopeKey() != null && routineIdentity != null) {
11477                                                procedure.setIdentityScopeKey(DlineageUtil.identityScopeKey(
11478                                                                procedure.getVariableScopeKey(), routineIdentity));
11479                                        }
11480                                        // B3 (routine-summary-scc-design §3): object-level
11481                                        // per-overload variable pools, keyed by the routine's frozen
11482                                        // scope and declaration identity. Mode-gated — LEGACY never
11483                                        // pushes, so the identity pool path stays cold there.
11484                                        if (option.isIdentityFirstVariablePools() && routineIdentity != null) {
11485                                                modelFactory.pushIdentityScope(procedure.getIdentityScopeKey(), procedure);
11486                                                pushedIdentityScope = true;
11487                                        }
11488                                }
11489                                if (stmt.getParameterDeclarations() != null) {
11490                                        TParameterDeclarationList parameters = stmt.getParameterDeclarations();
11491        
11492                                        for (int i = 0; i < parameters.size(); ++i) {
11493                                                TParameterDeclaration parameter = parameters.getParameterDeclarationItem(i);
11494                                                Argument argument = null;
11495                                                TObjectName argumentName = null;
11496                                                if (parameter.getParameterName() != null) {
11497                                                        argument = this.modelFactory.createProcedureArgument(procedure, parameter, i + 1);
11498                                                        argumentName = parameter.getParameterName();
11499                                                } else if (parameter.getDataType() != null) {
11500                                                        argument = this.modelFactory.createProcedureArgument(procedure, parameter, i + 1);
11501                                                }
11502                                                
11503                                                if (argument != null) {
11504                                                        if (argumentName == null) {
11505                                                                argumentName = new TObjectName();
11506                                                                argumentName.setString(argument.getName());
11507                                                        }
11508                                                        Variable variable = modelFactory.createVariable(argument.getName());
11509                                                        // B1: index the formal under the overload-
11510                                                        // discriminating identity scope too (lookup-map
11511                                                        // only; consumed by B3's identity-first path).
11512                                                        modelFactory.bindVariableUnderIdentityScope(
11513                                                                        procedure, argument.getName(), variable);
11514                                                        if (argument.getMode() == EParameterMode.in || argument.getMode() == EParameterMode.out || argument.getMode() == EParameterMode.output) {
11515                                                                variable.setSubType(SubType.of(argument.getMode().name()));
11516                                                        } else {
11517                                                                variable.setSubType(SubType.argument);
11518                                                        }
11519                                                        TableColumn parameterField;
11520                                                        if(isSimpleDataType(argument.getDataType())){
11521                                                                parameterField = modelFactory.createTableColumn(variable, argumentName, true);
11522                                                        }
11523                                                        else {
11524                                                                TObjectName variableProperties = DlineageUtil.syntheticStarName();
11525                                                                parameterField = modelFactory.createTableColumn(variable, variableProperties, true);
11526                                                        }
11527                                                        if (shouldCollectAuthoritativeLineageEvidence()) {
11528                                                                authoritativeEvidenceCollector.bindRoutineParameter(argument, parameterField);
11529                                                        }
11530                                                }
11531                                        }
11532                                }
11533                        }
11534        
11535                        if (stmt instanceof TCreateTriggerStmt) {
11536                                TCreateTriggerStmt trigger = (TCreateTriggerStmt) stmt;
11537        
11538                                if (trigger.getFunctionCall() != null) {
11539                                        modelFactory.createProcedureFromFunctionCall(trigger.getFunctionCall());
11540                                }
11541        
11542                                if (trigger.getTables() != null) {
11543                                        for (int i = 0; i < trigger.getTables().size(); i++) {
11544                                                Table tableModel = this.modelFactory.createTriggerOnTable(trigger.getTables().getTable(i));
11545                                        }
11546                                }
11547                        }
11548        
11549                        if (stmt instanceof TPlsqlCreateTrigger && ((TPlsqlCreateTrigger) stmt).getTriggeringClause()!=null
11550                                        && ((TPlsqlCreateTrigger) stmt).getTriggeringClause().getEventClause() instanceof TDmlEventClause) {
11551                                TPlsqlCreateTrigger trigger = (TPlsqlCreateTrigger) stmt;
11552                                TDmlEventClause clause = (TDmlEventClause) ((TPlsqlCreateTrigger) stmt).getTriggeringClause()
11553                                                .getEventClause();
11554                                Table sourceTable = modelFactory.createTableByName(clause.getTableName());
11555
11556                                bindTriggerCorrelationNames(trigger, sourceTable);
11557        
11558                                for (TTriggerEventItem item : clause.getEventItems()) {
11559                                        if (item instanceof TDmlEventItem) {
11560                                                if (((TDmlEventItem) item).getColumnList() != null) {
11561                                                        for (TObjectName column : ((TDmlEventItem) item).getColumnList()) {
11562                                                                modelFactory.createTableColumn(sourceTable, column, true);
11563                                                        }
11564                                                }
11565                                        }
11566                                }
11567        
11568                                for (TCustomSqlStatement subStmt : ((TPlsqlCreateTrigger) stmt).getStatements()) {
11569                                        if (!(subStmt instanceof TCommonBlock))
11570                                                continue;
11571                                        for (TCustomSqlStatement blockSubStmt : ((TCommonBlock) subStmt).getStatements()) {
11572                                                if (!(blockSubStmt instanceof TBasicStmt))
11573                                                        continue;
11574                                                TBasicStmt basicStmt = (TBasicStmt) blockSubStmt;
11575                                                TExpression expression = basicStmt.getExpr();
11576                                                if (expression != null && expression.getExpressionType() == EExpressionType.function_t) {
11577                                                        Procedure targetProcedure = modelManager.getProcedure(tableDefaultedAliasKey(nameAliasKey(expression.getFunctionCall().getFunctionName(), null)));
11578                                                        if (targetProcedure == null) {
11579                                                                targetProcedure = modelManager.getProcedure(procedure.getSchema() == null ? null : tableDefaultedAliasKey(qualifiedAliasKey(
11580                                                                                                                nameAliasKey(expression.getFunctionCall().getFunctionName(), null), procedure.getSchema())));
11581                                                        }
11582                                                        if (gudusoft.gsqlparser.dlineage.routine.RoutineShadow.enabled()) shadowCallee("triggerArguments", expression.getFunctionCall().getFunctionName(), targetProcedure,
11583                                                                        new Integer[] {null}, gudusoft.gsqlparser.catalog.runtime.CatalogObjectKind.ROUTINE);
11584                                                        if (targetProcedure != null && expression.getFunctionCall().getArgs() != null && expression
11585                                                                        .getFunctionCall().getArgs().size() == targetProcedure.getArguments().size()) {
11586                                                                for (int j = 0; j < expression.getFunctionCall().getArgs().size(); j++) {
11587                                                                        TExpression columnExpr = expression.getFunctionCall().getArgs().getExpression(j);
11588                                                                        if (columnExpr.getExpressionType() == EExpressionType.simple_object_name_t) {
11589                                                                                TObjectName columnObject = columnExpr.getObjectOperand();
11590                                                                                if (columnObject.toString().indexOf(":") != -1) {
11591                                                                                        TableColumn tableColumn = modelFactory.createTableColumn(sourceTable,
11592                                                                                                        columnObject, true);
11593                                                                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
11594                                                                                        relation.setEffectType(EffectType.trigger);
11595                                                                                        relation.addSource(new TableColumnRelationshipElement(tableColumn));
11596                                                                                        relation.setTarget(
11597                                                                                                        new ArgumentRelationshipElement(targetProcedure.getArguments().get(j)));
11598                                                                                        Process process = modelFactory.createProcess(stmt);
11599                                                                                        relation.setProcess(process);
11600                                                                                }
11601                                                                        }
11602                                                                }
11603                                                        }
11604                                                }
11605                                        }
11606                                }
11607                        }
11608        
11609                        if (stmt instanceof TMssqlCreateFunction) {
11610                                TMssqlCreateFunction createFunction = (TMssqlCreateFunction) stmt;
11611                                if (createFunction.getReturnTableVaraible() != null && createFunction.getReturnTableDefinitions() != null) {
11612                                        Variable tableModel = this.modelFactory.createVariable(createFunction.getReturnTableVaraible());
11613                                        tableModel.setVariable(true);
11614                                        tableModel.setCreateTable(true);
11615                                        if (procedure != null) {
11616                                                tableModel.setProcedureId(String.valueOf(procedure.getId()));
11617                                                tableModel.setTarget(true);
11618                                        }
11619                                        String procedureParent = createFunction.getFunctionName().toString();
11620                                        if (procedureParent != null) {
11621                                                tableModel.setParent(procedureParent);
11622                                        }
11623                                        modelManager.bindFunctionResult(nameAliasKey(createFunction.getFunctionName(), null), tableModel);
11624        
11625                                        if (createFunction.getReturnTableDefinitions() != null) {
11626                                                for (int j = 0; j < createFunction.getReturnTableDefinitions().size(); j++) {
11627                                                        TTableElement tableElement = createFunction.getReturnTableDefinitions().getTableElement(j);
11628                                                        TColumnDefinition column = tableElement.getColumnDefinition();
11629                                                        if (column != null && column.getColumnName() != null) {
11630                                                                modelFactory.createTableColumn(tableModel, column.getColumnName(), true);
11631                                                        }
11632                                                }
11633                                        }
11634                                }
11635        
11636                                if (createFunction.getReturnStmt() != null && createFunction.getReturnStmt().getSubquery() != null) {
11637                                        String procedureParent = createFunction.getFunctionName().toString();
11638                                        analyzeSelectStmt(createFunction.getReturnStmt().getSubquery());
11639                                        ResultSet resultSetModel = (ResultSet) modelManager
11640                                                        .getModel(createFunction.getReturnStmt().getSubquery());
11641                                        if (procedure != null) {
11642                                                resultSetModel.setProcedureId(String.valueOf(procedure.getId()));
11643                                                resultSetModel.setTarget(true);
11644                                        }
11645                                        modelManager.bindFunctionResult(nameAliasKey(createFunction.getFunctionName(), null), resultSetModel);
11646                                }
11647                        } else if (stmt instanceof TCreateFunctionStmt) {
11648                                TCreateFunctionStmt createFunction = (TCreateFunctionStmt) stmt;
11649                                if (createFunction.getReturnDataType() != null
11650                                                && createFunction.getReturnDataType().getColumnDefList() != null) {
11651                                        Table tableModel = this.modelFactory.createTableByName(createFunction.getFunctionName(), true);
11652                                        tableModel.setCreateTable(true);
11653                                        String procedureParent = createFunction.getFunctionName().toString();
11654                                        if (procedureParent != null) {
11655                                                tableModel.setParent(procedureParent);
11656                                        }
11657        
11658                                        modelManager.bindFunctionResult(nameAliasKey(createFunction.getFunctionName(), null), tableModel);
11659                                        for (int j = 0; j < createFunction.getReturnDataType().getColumnDefList().size(); j++) {
11660                                                TColumnDefinition column = createFunction.getReturnDataType().getColumnDefList().getColumn(j);
11661                                                if (column != null && column.getColumnName() != null) {
11662                                                        modelFactory.createTableColumn(tableModel, column.getColumnName(), true);
11663                                                }
11664                                        }
11665        
11666                                        if (createFunction.getSqlQuery() != null) {
11667                                                analyzeSelectStmt(createFunction.getSqlQuery());
11668                                                ResultSet resultSetModel = (ResultSet) modelManager.getModel(createFunction.getSqlQuery());
11669                                                if (resultSetModel != null) {
11670                                                        for (int i = 0; i < resultSetModel.getColumns().size(); i++) {
11671                                                                ResultColumn resultColumn = resultSetModel.getColumns().get(i);
11672                                                                for (int j = 0; j < tableModel.getColumns().size(); j++) {
11673                                                                        TableColumn tableColumn = tableModel.getColumns().get(j);
11674                                                                        if (sameModelColumn(resultColumn, tableColumn)) {
11675                                                                                DataFlowRelationship dataflowRelation = modelFactory.createDataFlowRelation();
11676                                                                                dataflowRelation.setEffectType(EffectType.select);
11677                                                                                dataflowRelation.addSource(new ResultColumnRelationshipElement(resultColumn));
11678                                                                                dataflowRelation.setTarget(new TableColumnRelationshipElement(tableColumn));
11679                                                                        }
11680                                                                }
11681                                                        }
11682                                                }
11683                                        }
11684                                }
11685                                
11686                                if (createFunction.getReturnStmt() != null && createFunction.getReturnStmt().getSubquery() != null) {
11687                                        String procedureParent = createFunction.getFunctionName().toString();
11688                                        analyzeSelectStmt(createFunction.getReturnStmt().getSubquery());
11689                                        ResultSet resultSetModel = (ResultSet) modelManager
11690                                                        .getModel(createFunction.getReturnStmt().getSubquery());
11691                                        if (procedure != null) {
11692                                                resultSetModel.setProcedureId(String.valueOf(procedure.getId()));
11693                                                resultSetModel.setTarget(true);
11694                                        }
11695                                        modelManager.bindFunctionResult(nameAliasKey(createFunction.getFunctionName(), null), resultSetModel);
11696                                }
11697                                
11698                                if (createFunction.getReturnStmt() != null && createFunction.getReturnStmt().getReturnExpr() != null) {
11699                                        TExpression returnExpression =  createFunction.getReturnStmt().getReturnExpr();
11700                                        ResultSet returnResult = modelFactory.createResultSet(createFunction.getReturnStmt(), false);
11701                                        ResultColumn resultColumn = modelFactory.createResultColumn(returnResult, returnExpression);
11702                                        if (shouldCollectAuthoritativeLineageEvidence()) {
11703                                                authoritativeEvidenceCollector.bindRoutineReturn(procedure, resultColumn);
11704                                        }
11705                                        
11706                                        TExpression expression = createFunction.getReturnStmt().getReturnExpr();
11707                                        analyzeResultColumnExpressionRelation(resultColumn, expression);
11708        
11709                                        if (procedure != null) {
11710                                                returnResult.setProcedureId(String.valueOf(procedure.getId()));
11711                                                returnResult.setTarget(true);
11712                                        }
11713                                        String procedureParent = createFunction.getFunctionName().toString();
11714                                        modelManager.bindFunctionResult(nameAliasKey(createFunction.getFunctionName(), null), returnResult);
11715                                }
11716                        }
11717        
11718                        if (stmt instanceof TCreateProcedureStmt) {
11719                                TCreateProcedureStmt createProcedure = (TCreateProcedureStmt) stmt;
11720                                if (EDbVendor.dbvsnowflake == option.getVendor() && createProcedure.getRoutineBodyInConstant() != null) {
11721                                        String language = createProcedure.getProcedureLanguage() == null ? null
11722                                                        : createProcedure.getProcedureLanguage().toString();
11723                                        // Only a JAVASCRIPT body can be executed by the script-based
11724                                        // SQL extractor. Other handler languages (PYTHON, JAVA, SCALA)
11725                                        // are opaque to SQL analysis: keep the procedure model with its
11726                                        // declared language and skip the body (Mantis 4590).
11727                                        if (language == null || language.equalsIgnoreCase("JAVASCRIPT")) { // non-identifier-compare: LANGUAGE keyword, not a db object name
11728                                                extractSnowflakeSQLFromProcedure(createProcedure);
11729                                        }
11730                                }
11731                        }
11732                                
11733                        if (stmt.getStatements().size() > 0) {
11734                                for (int i = 0; i < stmt.getStatements().size(); ++i) {
11735                                        this.analyzeCustomSqlStmt(stmt.getStatements().get(i));
11736                                }
11737                        }
11738
11739                        if (stmt.getBodyStatements().size() > 0) {
11740                                for (int i = 0; i < stmt.getBodyStatements().size(); ++i) {
11741                                        this.analyzeCustomSqlStmt(stmt.getBodyStatements().get(i));
11742                                }
11743                        }
11744                        
11745                        // Detect pipelined functions and build signatures
11746                        if (stmt instanceof TPlsqlCreateFunction && pipelinedAnalyzer != null) {
11747                                try {
11748                                        pipelinedAnalyzer.analyzePipelinedFunction((TPlsqlCreateFunction) stmt);
11749                                } catch (Exception e) {
11750                                        // Don't let pipelined analysis failure break main flow
11751                                }
11752                        }
11753
11754                        if (procedure != null && !getLastSelectStmt(stmt).isEmpty()) {
11755                                for (TSelectSqlStatement select : getLastSelectStmt(stmt)) {
11756                                        ResultSet resultSet = (ResultSet) modelManager.getModel(select);
11757                                        resultSet.setProcedureId(String.valueOf(procedure.getId()));
11758                                        resultSet.setTarget(true);
11759                                        modelManager.bindFunctionResult(procedure.getAliasKey(), resultSet);
11760                                }
11761//                              List<Argument> outArgs = new ArrayList<Argument>();
11762//                              for (int i = 0; i < procedure.getArguments().size(); i++) {
11763//                                      Argument argument = procedure.getArguments().get(i);
11764//                                      if (argument.getMode() == EParameterMode.out || argument.getMode() == EParameterMode.output) {
11765//                                              outArgs.add(argument);
11766//                                      }
11767//                              }
11768//
11769//                              if (resultSet != null && resultSet.getColumns().size() == outArgs.size()) {
11770//                                      for (int i = 0; i < outArgs.size(); i++) {
11771//                                              Argument argument = outArgs.get(i);
11772//                                              Variable variable = modelFactory.createVariable(argument.getName(), false);
11773//                                              if (variable != null) {
11774//                                                      DataFlowRelationship dataflowRelation = modelFactory.createDataFlowRelation();
11775//                                                      dataflowRelation.setEffectType(EffectType.output);
11776//                                                      dataflowRelation
11777//                                                                      .addSource(new ResultColumnRelationshipElement(resultSet.getColumns().get(i)));
11778//                                                      dataflowRelation
11779//                                                                      .setTarget(new TableColumnRelationshipElement(variable.getColumns().get(0)));
11780//                                              }
11781//                                      }
11782//
11783//                              }
11784                        }
11785
11786                        if (stmt instanceof TCreateFunctionStmt && ((TCreateFunctionStmt)stmt).getSqlExpression()!=null) {
11787                                TCreateFunctionStmt createFunction = (TCreateFunctionStmt) stmt;
11788                                TExpression returnExpression = createFunction.getSqlExpression();
11789
11790                                ResultSet returnResult = modelFactory.createResultSet(stmt, false);
11791                                ResultColumn resultColumn = modelFactory.createResultColumn(returnResult, returnExpression);
11792                                if (shouldCollectAuthoritativeLineageEvidence()) {
11793                                        authoritativeEvidenceCollector.bindRoutineReturn(procedure, resultColumn);
11794                                }
11795
11796                                columnsInExpr visitor = new columnsInExpr();
11797                                returnExpression.inOrderTraverse(visitor);
11798
11799                                List<TObjectName> objectNames = visitor.getObjectNames();
11800                                List<TParseTreeNode> functions = visitor.getFunctions();
11801                                List<TParseTreeNode> constants = visitor.getConstants();
11802                                List<TSelectSqlStatement> subquerys = visitor.getSubquerys();
11803
11804                                if (functions != null && !functions.isEmpty()) {
11805                                        analyzeFunctionDataFlowRelation(resultColumn, functions, EffectType.function);
11806                                }
11807                                if (subquerys != null && !subquerys.isEmpty()) {
11808                                        analyzeSubqueryDataFlowRelation(resultColumn, subquerys, EffectType.select);
11809                                }
11810                                if (objectNames != null && !objectNames.isEmpty()) {
11811                                        analyzeDataFlowRelation(resultColumn, objectNames, EffectType.select, functions);
11812                                }
11813                                if (constants != null && !constants.isEmpty()) {
11814                                        analyzeConstantDataFlowRelation(resultColumn, constants, EffectType.select, functions);
11815                                }
11816
11817                                String procedureParent = SQLUtil.trimColumnStringQuote(getProcedureParentName(stmt));
11818                                modelManager.bindFunctionResult(procedureParentAliasKey(stmt), returnResult);
11819                        }
11820                } finally {
11821                        if (pushedIdentityScope) {
11822                                modelFactory.popIdentityScope();
11823                        }
11824                        if (innerBlock) {
11825                                ModelBindingManager.setGlobalProcedure(enclosingRoutine);
11826                        } else {
11827                                ModelBindingManager.removeGlobalProcedure();
11828                        }
11829                        if (temporaryBindings != null) modelManager.leaveTemporaryTableScope(temporaryBindings);
11830                }
11831
11832        }
11833
11834        private boolean isSimpleDataType(TTypeName dataType) {
11835                if (dataType.getDataType() == EDataType.variant_t) {
11836                        return false;
11837                }
11838                if (dataType.getDataType() == EDataType.cursor_t) {
11839                        return false;
11840                }
11841                if (dataType.getDataType() == EDataType.generic_t) {
11842                        return false;
11843                }
11844                if (dataType.getDataType() == EDataType.unknown_t) {
11845                        return false;
11846                }
11847                if (dataType.getDataType() == EDataType.sql_variant_t) {
11848                        return false;
11849                }
11850                if (dataType.getDataType() == EDataType.table_t) {
11851                        return false;
11852                }
11853                if (dataType.getDataType() == EDataType.raw_t) {
11854                        return false;
11855                }
11856                if (dataType.getDataType() == EDataType.resultset_t) {
11857                        return false;
11858                }
11859                if (dataType.getDataType() == EDataType.row_t) {
11860                        return false;
11861                }
11862                if (dataType.getDataType() == EDataType.map_t) {
11863                        return false;
11864                }
11865                if (dataType.getDataType() == EDataType.anyType_t) {
11866                        return false;
11867                }
11868                if (dataType.getDataType() == EDataType.struct_t) {
11869                        return false;
11870                }
11871                if (dataType.getDataType() == EDataType.structType_t) {
11872                        return false;
11873                }
11874                if (dataType.getDataType() == EDataType.mapType_t) {
11875                        return false;
11876                }
11877                return true;
11878        }
11879
11880        /**
11881         * ClickHouse remote()/remoteSecure() expose a real db.table on another
11882         * host as a table; modelling them as plain function nodes dropped the
11883         * upstream node and every edge into it. Returns true when the call was
11884         * modelled — the model is bound to the TTable, so the columns resolver2
11885         * already linked flow through the normal machinery. The model registers
11886         * through the ordinary table registry: a remote db.table shares identity
11887         * semantics with every other ClickHouse table reference.
11888         *
11889         * Only PROVABLE sources are modelled: plain object names or quoted
11890         * string constants, taken verbatim from the SQL text. Runtime
11891         * expressions and non-string constants (NULL, numbers) fall back to the
11892         * generic function node (never publish an unprovable edge).
11893         *
11894         * The PATH functions (file/s3/url/hdfs, their *Cluster variants and
11895         * azureBlobStorage) are deliberately NOT modelled: resource paths are
11896         * case-sensitive identities that do not fit the folded table-name
11897         * registry (bare filenames collide with same-spelling tables, case
11898         * variants coalesce, and Azure's first argument is a credential-bearing
11899         * connection string). Their missing upstream remains an HONEST, VISIBLE
11900         * gap — a generic function node — until a dedicated resource-identity
11901         * layer exists; see external_source_lineage_gap.md.
11902         *
11903         * Generator table functions (numbers(), generateRandom(), zeros(), ...)
11904         * correctly have NO upstream, which the generic node already expresses.
11905         */
11906        private boolean modelClickhouseExternalTableFunction(TTable table, TFunctionCall functionCall) {
11907                if (option.getVendor() != EDbVendor.dbvclickhouse) {
11908                        return false;
11909                }
11910                if (functionCall.getFunctionName() == null || functionCall.getArgs() == null
11911                                || functionCall.getArgs().size() < 2) {
11912                        return false;
11913                }
11914                String functionName = functionCall.getFunctionName().toString();
11915                if (!isClickhouseTableFunction(functionName, "remote")
11916                                && !isClickhouseTableFunction(functionName, "remoteSecure")) {
11917                        return false;
11918                }
11919                // remote(addresses, db, table[, user, password])
11920                // remote(addresses, db.table[, user, password])
11921                TExpressionList args = functionCall.getArgs();
11922                String first = literalTableFunctionArg(args.getExpression(1));
11923                if (first == null) {
11924                        return false;
11925                }
11926                String remoteTableName;
11927                if (first.indexOf('.') > 0) {
11928                        remoteTableName = first;
11929                } else {
11930                        String second = args.size() >= 3 ? literalTableFunctionArg(args.getExpression(2)) : null;
11931                        if (second == null) {
11932                                return false; // db without a provable table argument
11933                        }
11934                        remoteTableName = first + "." + second;
11935                }
11936                Table model = clickhouseExternalSourceModels.get(remoteTableName);
11937                if (model == null) {
11938                        model = modelFactory.createTableFromCreateDDL(table, false, remoteTableName);
11939                        clickhouseExternalSourceModels.put(remoteTableName, model);
11940                }
11941                modelManager.bindCreateModel(table, model);
11942                modelManager.bindModel(table, model);
11943                return true;
11944        }
11945
11946        /** Same-literal remote() calls merge here; cleared in init(). */
11947        private final Map<String, Table> clickhouseExternalSourceModels = new LinkedHashMap<String, Table>();
11948
11949        /**
11950         * ClickHouse function names are CASE-SENSITIVE (FiLe(...) is
11951         * UNKNOWN_FUNCTION natively) — matched through the canonical
11952         * per-object-type comparison, never a case fold.
11953         */
11954        private static boolean isClickhouseTableFunction(String rawName, String candidate) {
11955                return DlineageUtil.sameName(ESQLDataObjectType.dotFunction, rawName, candidate);
11956        }
11957
11958        /** A provably literal identifier-ish argument: a plain object name or a
11959         *  QUOTED STRING constant (quotes stripped); null for anything computed —
11960         *  including non-string constants (NULL, numbers), which are not resource
11961         *  identities. */
11962        private static String literalTableFunctionArg(TExpression arg) {
11963                if (arg == null) {
11964                        return null;
11965                }
11966                if (arg.getObjectOperand() != null) {
11967                        return arg.getObjectOperand().toString();
11968                }
11969                return literalStringConstant(arg);
11970        }
11971
11972        /** The DECODED value of a quoted string constant, or null. Delegates to
11973         *  the shared ClickHouse literal decoder (IdentifierCodec) — the raw
11974         *  spelling is not the value ('db\x2etable' IS db.table), the escape
11975         *  table must match ClickHouse's parseComplexEscapeSequence exactly, and
11976         *  a value that cannot be represented safely returns null so the caller
11977         *  falls back to the generic function node. */
11978        private static String literalStringConstant(TExpression arg) {
11979                if (arg == null || arg.getConstantOperand() == null) {
11980                        return null;
11981                }
11982                return gudusoft.gsqlparser.sqlenv.IdentifierCodec
11983                                .decodeClickHouseStringLiteral(arg.getConstantOperand().toString());
11984        }
11985
11986        protected void analyzeResultColumnExpressionRelation(Object resultColumn, TExpression expression) {
11987                // SQL-LR: a nested analysis never inherits the reference of the loop that called it
11988                SqllrObservations sqllr = modelManager.getSqllrObservations();
11989                int sqllrBarrier = sqllr == null ? -1 : sqllr.openFrame();
11990                try {
11991                        analyzeResultColumnExpressionRelationInSqllrBarrier(resultColumn, expression);
11992                } finally {
11993                        if (sqllr != null) {
11994                                sqllr.closeFrame(sqllrBarrier);
11995                        }
11996                }
11997        }
11998
11999        private void analyzeResultColumnExpressionRelationInSqllrBarrier(Object resultColumn, TExpression expression) {
12000                columnsInExpr visitor = new columnsInExpr();
12001                expression.inOrderTraverse(visitor);
12002                List<TObjectName> objectNames = visitor.getObjectNames();
12003                List<TParseTreeNode> functions = visitor.getFunctions();
12004                List<TParseTreeNode> constants = visitor.getConstants();
12005                List<TSelectSqlStatement> subquerys = visitor.getSubquerys();
12006
12007                if (functions != null && !functions.isEmpty()) {
12008                        analyzeFunctionDataFlowRelation(resultColumn, functions, EffectType.function);
12009                }
12010                if (subquerys != null && !subquerys.isEmpty()) {
12011                        analyzeSubqueryDataFlowRelation(resultColumn, subquerys, EffectType.select);
12012                }
12013                if (objectNames != null && !objectNames.isEmpty()) {
12014                        analyzeDataFlowRelation(resultColumn, objectNames, EffectType.select, functions);
12015                }
12016                if (constants != null && !constants.isEmpty()) {
12017                        analyzeConstantDataFlowRelation(resultColumn, constants, EffectType.select, functions);
12018                }
12019        }
12020
12021        private void analyzeDb2ReturnStmt(TDb2ReturnStmt stmt) {
12022                if (stmt.getReturnExpr() != null) {
12023                        TExpression returnExpression = stmt.getReturnExpr();
12024                        ResultSet returnResult = modelFactory.createResultSet(stmt, true);
12025                        ResultColumn resultColumn = modelFactory.createResultColumn(returnResult, returnExpression);
12026                        bindCurrentRoutineReturn(resultColumn);
12027                        
12028                        columnsInExpr visitor = new columnsInExpr();
12029                        stmt.getReturnExpr().inOrderTraverse(visitor);
12030                        
12031                        List<TObjectName> objectNames = visitor.getObjectNames();
12032                        List<TParseTreeNode> functions = visitor.getFunctions();
12033                        List<TParseTreeNode> constants = visitor.getConstants();
12034                        List<TSelectSqlStatement> subquerys = visitor.getSubquerys();
12035
12036                        if (functions != null && !functions.isEmpty()) {
12037                                analyzeFunctionDataFlowRelation(resultColumn, functions, EffectType.function);
12038                        }
12039                        if (subquerys != null && !subquerys.isEmpty()) {
12040                                analyzeSubqueryDataFlowRelation(resultColumn, subquerys, EffectType.select);
12041                        }
12042                        if (objectNames != null && !objectNames.isEmpty()) {
12043                                analyzeDataFlowRelation(resultColumn, objectNames, EffectType.select, functions);
12044                        }
12045                        if (constants != null && !constants.isEmpty()) {
12046                                analyzeConstantDataFlowRelation(resultColumn, constants, EffectType.select, functions);
12047                        }
12048
12049                        String procedureParent = SQLUtil.trimColumnStringQuote(getProcedureParentName(stmt));
12050                        if (procedureParent != null) {
12051                                modelManager.bindFunctionResult(procedureParentAliasKey(stmt), returnResult);
12052                        }
12053                }
12054        }
12055
12056        /**
12057         * PL/pgSQL {@code RETURN QUERY EXECUTE '<text>'} (plan 09 slice 13g): a command text the input writes as literals
12058         * (string constants, joined with ||) is a query whose rows the function returns. It is analysed as dynamic SQL of
12059         * this site and its result bound as the function's result; a text with any other part is not known before it runs
12060         * and keeps the old handling. False when this is no such statement.
12061         */
12062        private boolean analyzeReturnQueryExecute(TReturnStmt stmt) {
12063                EDbVendor vendor = stmt.dbvendor;
12064                if ((vendor != EDbVendor.dbvpostgresql && vendor != EDbVendor.dbvgreenplum && vendor != EDbVendor.dbvgaussdb)
12065                                || stmt.getExpression() == null || stmt.getStartToken() == null) {
12066                        return false;
12067                }
12068                TSourceToken query = stmt.getStartToken().nextSolidToken();
12069                TSourceToken execute = query == null ? null : query.nextSolidToken();
12070                if (query == null || execute == null || !"QUERY".equalsIgnoreCase(query.toString()) // non-identifier-compare: keyword
12071                                || !"EXECUTE".equalsIgnoreCase(execute.toString())) { // non-identifier-compare: keyword
12072                        return false;
12073                }
12074                String sqlText = literalCommandText(stmt.getExpression());
12075                if (sqlText == null) {
12076                        return false;
12077                }
12078                modelManager.collectDynamicSqlHash(stmt);
12079                if (!option.isAnalyzeDynamicSql()) {
12080                        recordDynamicSqlSite(stmt, DynamicSqlSite.Kind.OTHER, DynamicSqlSite.Status.UNRESOLVED,
12081                                        "dynamic SQL inner analysis disabled");
12082                        return true;
12083                }
12084                TGSqlParser sqlparser = createSqlParser(option.getVendor());
12085                sqlparser.sqltext = sqlText;
12086                if (parseWithCommentBindings(sqlparser) != 0 || sqlparser.sqlstatements == null
12087                                || sqlparser.sqlstatements.size() == 0) {
12088                        return true; // a text that does not parse: nothing it returns is known
12089                }
12090                dynamicFoldDepth++;
12091                try {
12092                        for (int i = 0; i < sqlparser.sqlstatements.size(); i++) {
12093                                analyzeCustomSqlStmt(sqlparser.sqlstatements.get(i));
12094                        }
12095                } finally {
12096                        dynamicFoldDepth--;
12097                }
12098                if (sqlparser.sqlstatements.size() == 1 && sqlparser.sqlstatements.get(0) instanceof TSelectSqlStatement) {
12099                        Object model = modelManager.getModel(sqlparser.sqlstatements.get(0));
12100                        if (model instanceof ResultSet && ((ResultSet) model).getColumns() != null) {
12101                                ResultSet resultSet = (ResultSet) model;
12102                                ResultSet returnResult = modelFactory.createResultSet(stmt, true);
12103                                for (ResultColumn column : resultSet.getColumns()) {
12104                                        TObjectName columnName = new TObjectName();
12105                                        columnName.setString(column.getName());
12106                                        ResultColumn resultColumn = modelFactory.createResultColumn(returnResult, columnName);
12107                                        DataFlowRelationship dataflowRelation = modelFactory.createDataFlowRelation();
12108                                        dataflowRelation.setEffectType(EffectType.select);
12109                                        dataflowRelation.addSource(new ResultColumnRelationshipElement(column));
12110                                        dataflowRelation.setTarget(new ResultColumnRelationshipElement(resultColumn));
12111                                }
12112                                returnResult.setDetermined(resultSet.isDetermined());
12113                                modelManager.bindFunctionResult(procedureParentAliasKey(stmt), returnResult);
12114                        }
12115                }
12116                return true;
12117        }
12118
12119        /** The text of a command written as single-quoted string constants joined with ||, or null. */
12120        private static String literalCommandText(TExpression expression) {
12121                java.util.Deque<TExpression> pending = new java.util.ArrayDeque<TExpression>();
12122                List<TExpression> parts = new ArrayList<TExpression>();
12123                pending.push(expression);
12124                while (!pending.isEmpty()) {
12125                        TExpression e = pending.pop();
12126                        while (e != null && e.getExpressionType() == EExpressionType.parenthesis_t) {
12127                                e = e.getLeftOperand();
12128                        }
12129                        if (e == null) {
12130                                return null;
12131                        }
12132                        if (e.getExpressionType() == EExpressionType.concatenate_t) {
12133                                pending.push(e.getRightOperand());
12134                                pending.push(e.getLeftOperand());
12135                                continue;
12136                        }
12137                        parts.add(e);
12138                }
12139                StringBuilder text = new StringBuilder();
12140                for (TExpression part : parts) {
12141                        if (part.getExpressionType() != EExpressionType.simple_constant_t || part.getConstantOperand() == null
12142                                        || part.getConstantOperand().getValueToken() == null) {
12143                                return null;
12144                        }
12145                        String literal = part.getConstantOperand().getValueToken().toString();
12146                        if (literal.length() < 2 || literal.charAt(0) != '\'' || literal.charAt(literal.length() - 1) != '\'') {
12147                                return null; // E'...', $$...$$, a number: not a plain string constant
12148                        }
12149                        text.append(literal.substring(1, literal.length() - 1).replace("''", "'"));
12150                }
12151                return text.toString();
12152        }
12153
12154        private void analyzeReturnStmt(TReturnStmt stmt) {
12155                if (analyzeReturnQueryExecute(stmt)) {
12156                        return;
12157                }
12158                if (stmt.getResultColumnList() != null) {
12159                        ResultSet returnResult = modelFactory.createResultSet(stmt, true);
12160                        for (TResultColumn column : stmt.getResultColumnList()) {
12161                                ResultColumn resultColumn = modelFactory.createResultColumn(returnResult, column);
12162                                if (stmt.getResultColumnList().size() == 1) bindCurrentRoutineReturn(resultColumn);
12163
12164                                columnsInExpr visitor = new columnsInExpr();
12165                                column.getExpr().inOrderTraverse(visitor);
12166
12167                                List<TObjectName> objectNames = visitor.getObjectNames();
12168                                List<TParseTreeNode> functions = visitor.getFunctions();
12169                                List<TParseTreeNode> constants = visitor.getConstants();
12170                                List<TSelectSqlStatement> subquerys = visitor.getSubquerys();
12171
12172                                if (functions != null && !functions.isEmpty()) {
12173                                        analyzeFunctionDataFlowRelation(resultColumn, functions, EffectType.function);
12174                                }
12175                                if (subquerys != null && !subquerys.isEmpty()) {
12176                                        analyzeSubqueryDataFlowRelation(resultColumn, subquerys, EffectType.select);
12177                                }
12178                                if (objectNames != null && !objectNames.isEmpty()) {
12179                                        analyzeDataFlowRelation(resultColumn, objectNames, EffectType.select, functions);
12180                                }
12181                                if (constants != null && !constants.isEmpty()) {
12182                                        analyzeConstantDataFlowRelation(resultColumn, constants, EffectType.select, functions);
12183                                }
12184
12185                        }
12186
12187                        String procedureParent = SQLUtil.trimColumnStringQuote(getProcedureParentName(stmt));
12188                        modelManager.bindFunctionResult(procedureParentAliasKey(stmt), returnResult);
12189                }
12190                else if(stmt.getExpression()!=null){
12191                        TExpression returnExpression = stmt.getExpression();
12192                        ResultSet returnResult = modelFactory.createResultSet(stmt, true);
12193                        
12194                        columnsInExpr visitor = null;
12195                        List<TSelectSqlStatement> subquerys = null;
12196                        
12197                        if (returnExpression.getFunctionCall() != null
12198                                        && returnExpression.getFunctionCall().getFunctionName().toString().equalsIgnoreCase("table")
12199                                        && returnExpression.getFunctionCall().getArgs() != null
12200                                        && returnExpression.getFunctionCall().getArgs().size()>0) {
12201                                visitor = new columnsInExpr();
12202                                returnExpression.getFunctionCall().getArgs().getExpression(0).inOrderTraverse(visitor);
12203                                subquerys = visitor.getSubquerys();
12204                                if (subquerys != null && !subquerys.isEmpty()) {
12205                                        analyzeSelectStmt(subquerys.get(0));
12206                                        ResultSet resultSet = (ResultSet) modelManager.getModel(subquerys.get(0));
12207                                        if (resultSet != null && resultSet.getColumns() != null) {
12208                                                for (ResultColumn column : resultSet.getColumns()) {
12209                                                        TObjectName columnName = new TObjectName();
12210                                                        columnName.setString(column.getName());
12211                                                        ResultColumn resultColumn = modelFactory.createResultColumn(returnResult, columnName);
12212                                                        DataFlowRelationship dataflowRelation = modelFactory.createDataFlowRelation();
12213                                                        dataflowRelation.setEffectType(EffectType.select);
12214                                                        dataflowRelation.addSource(new ResultColumnRelationshipElement(column));
12215                                                        dataflowRelation.setTarget(new ResultColumnRelationshipElement(resultColumn));
12216                                                }
12217                                        }
12218                                        returnResult.setDetermined(resultSet.isDetermined());
12219                                        String procedureParent = SQLUtil.trimColumnStringQuote(getProcedureParentName(stmt));
12220                                        modelManager.bindFunctionResult(procedureParentAliasKey(stmt), returnResult);
12221                                        return;
12222                                }
12223                        }
12224                        
12225                        ResultColumn resultColumn = null;
12226                        if (returnExpression.getExpressionType() == EExpressionType.simple_object_name_t) {
12227                                TObjectName columnName = DlineageUtil.syntheticStarName();
12228                                resultColumn = modelFactory.createResultColumn(returnResult, columnName);
12229                        }
12230                        else {
12231                                resultColumn = modelFactory.createResultColumn(returnResult, returnExpression);
12232                        }
12233
12234                        visitor = new columnsInExpr();
12235                        stmt.getExpression().inOrderTraverse(visitor);
12236
12237                        List<TObjectName> objectNames = visitor.getObjectNames();
12238                        List<TParseTreeNode> functions = visitor.getFunctions();
12239                        List<TParseTreeNode> constants = visitor.getConstants();
12240                        subquerys = visitor.getSubquerys();
12241
12242                        if (functions != null && !functions.isEmpty()) {
12243                                analyzeFunctionDataFlowRelation(resultColumn, functions, EffectType.function);
12244                        }
12245                        if (subquerys != null && !subquerys.isEmpty()) {
12246                                analyzeSubqueryDataFlowRelation(resultColumn, subquerys, EffectType.select);
12247                        }
12248                        if (objectNames != null && !objectNames.isEmpty()) {
12249                                DataFlowRelationship relation = analyzeDataFlowRelation(resultColumn, objectNames, EffectType.select, functions);
12250                                //如果variable对应的不是一个复杂结构,则resultColumn不要设置为*
12251                                if (relation != null && relation.getTarget().getElement() == resultColumn && relation.getSources().size() == 1) {
12252                                        Object column = relation.getSources().iterator().next().getElement();
12253                                        boolean star = true;
12254                                        if (column instanceof TableColumn && !((TableColumn) column).isStarColumn()) {
12255                                                star = false;
12256                                        }
12257                                        if (column instanceof ResultColumn && !((ResultColumn) column).isStarColumn()) {
12258                                                star = false;
12259                                        }
12260                                        if (!star && returnExpression.getExpressionType() == EExpressionType.simple_object_name_t) {
12261                                                resultColumn = modelFactory.createResultColumn(returnResult, returnExpression.getObjectOperand());
12262                                                returnResult.getColumns().clear();
12263                                                returnResult.addColumn(resultColumn);
12264                                                relation.setTarget(new ResultColumnRelationshipElement(resultColumn));
12265                                        }
12266                                }
12267                        }
12268                        if (constants != null && !constants.isEmpty()) {
12269                                analyzeConstantDataFlowRelation(resultColumn, constants, EffectType.select, functions);
12270                        }
12271                        bindCurrentRoutineReturn(resultColumn);
12272
12273                        String procedureParent = SQLUtil.trimColumnStringQuote(getProcedureParentName(stmt));
12274                        if (procedureParent != null) {
12275                                modelManager.bindFunctionResult(procedureParentAliasKey(stmt), returnResult);
12276                        }
12277                }
12278        }
12279
12280        private void bindCurrentRoutineReturn(ResultColumn resultColumn) {
12281                if (!shouldCollectAuthoritativeLineageEvidence()) return;
12282                TStoredProcedureSqlStatement declaration = ModelBindingManager.getGlobalProcedure();
12283                Object routine = declaration == null ? null : modelManager.getModel(declaration);
12284                if (routine instanceof Procedure) {
12285                        authoritativeEvidenceCollector.bindRoutineReturn((Procedure) routine, resultColumn);
12286                }
12287        }
12288
12289        private void analyzeMssqlReturnStmt(TMssqlReturn stmt) {
12290                if (stmt.getResultColumnList() != null) {
12291                        ResultSet returnResult = modelFactory.createResultSet(stmt, true);
12292                        for (TResultColumn column : stmt.getResultColumnList()) {
12293                                ResultColumn resultColumn = modelFactory.createResultColumn(returnResult, column);
12294                                if (stmt.getResultColumnList().size() == 1) bindCurrentRoutineReturn(resultColumn);
12295
12296                                columnsInExpr visitor = new columnsInExpr();
12297                                column.getExpr().inOrderTraverse(visitor);
12298
12299                                List<TObjectName> objectNames = visitor.getObjectNames();
12300                                List<TParseTreeNode> functions = visitor.getFunctions();
12301                                List<TParseTreeNode> constants = visitor.getConstants();
12302                                List<TSelectSqlStatement> subquerys = visitor.getSubquerys();
12303
12304                                if (functions != null && !functions.isEmpty()) {
12305                                        analyzeFunctionDataFlowRelation(resultColumn, functions, EffectType.function);
12306                                }
12307                                if (subquerys != null && !subquerys.isEmpty()) {
12308                                        analyzeSubqueryDataFlowRelation(resultColumn, subquerys, EffectType.select);
12309                                }
12310                                if (objectNames != null && !objectNames.isEmpty()) {
12311                                        analyzeDataFlowRelation(resultColumn, objectNames, EffectType.select, functions);
12312                                }
12313                                if (constants != null && !constants.isEmpty()) {
12314                                        analyzeConstantDataFlowRelation(resultColumn, constants, EffectType.select, functions);
12315                                }
12316
12317                        }
12318
12319                        String procedureParent = SQLUtil.trimColumnStringQuote(getProcedureParentName(stmt));
12320                        modelManager.bindFunctionResult(procedureParentAliasKey(stmt), returnResult);
12321                }
12322                else if(stmt.getReturnExpr()!=null){
12323                        TExpression returnExpression = stmt.getReturnExpr();
12324                        ResultSet returnResult = modelFactory.createResultSet(stmt, true);
12325                        
12326                        if (returnExpression.getSubQuery() == null) {
12327                                ResultColumn resultColumn = modelFactory.createResultColumn(returnResult, returnExpression);
12328                                columnsInExpr visitor = new columnsInExpr();
12329                                stmt.getReturnExpr().inOrderTraverse(visitor);
12330
12331                                List<TObjectName> objectNames = visitor.getObjectNames();
12332                                List<TParseTreeNode> functions = visitor.getFunctions();
12333                                List<TParseTreeNode> constants = visitor.getConstants();
12334                                List<TSelectSqlStatement> subquerys = visitor.getSubquerys();
12335
12336                                if (functions != null && !functions.isEmpty()) {
12337                                        analyzeFunctionDataFlowRelation(resultColumn, functions, EffectType.function);
12338                                }
12339                                if (subquerys != null && !subquerys.isEmpty()) {
12340                                        analyzeSubqueryDataFlowRelation(resultColumn, subquerys, EffectType.select);
12341                                }
12342                                if (objectNames != null && !objectNames.isEmpty()) {
12343                                        analyzeDataFlowRelation(resultColumn, objectNames, EffectType.select, functions);
12344                                }
12345                                if (constants != null && !constants.isEmpty()) {
12346                                        analyzeConstantDataFlowRelation(resultColumn, constants, EffectType.select, functions);
12347                                }
12348                                bindCurrentRoutineReturn(resultColumn);
12349                        }
12350                        else {
12351                                analyzeSelectStmt(returnExpression.getSubQuery());
12352                                ResultSet subResultSet = (ResultSet)modelManager.getModel(returnExpression.getSubQuery());
12353                                if (subResultSet != null) {
12354                                        for (ResultColumn subResultColumn : subResultSet.getColumns()) {
12355                                                TObjectName objectName = new TObjectName();
12356                                                objectName.setString(subResultColumn.getName());
12357                                                ResultColumn resultColumn = modelFactory.createResultColumn(returnResult, objectName);
12358                                                DataFlowRelationship dataflowRelation = modelFactory.createDataFlowRelation();
12359                                                dataflowRelation.setEffectType(EffectType.select);
12360                                                dataflowRelation.addSource(new ResultColumnRelationshipElement(subResultColumn));
12361                                                dataflowRelation.setTarget(new ResultColumnRelationshipElement(resultColumn));
12362
12363                                        }
12364                                        
12365                                        if(subResultSet.getRelationRows().hasRelation()) {
12366                                                ImpactRelationship impactRelation = modelFactory.createImpactRelation();
12367                                                impactRelation.setEffectType(EffectType.select);
12368                                                impactRelation.addSource(new RelationRowsRelationshipElement<ResultSetRelationRows>(
12369                                                                subResultSet.getRelationRows()));
12370                                                impactRelation.setTarget(
12371                                                                new RelationRowsRelationshipElement<ResultSetRelationRows>(returnResult.getRelationRows()));
12372                                        }
12373                                }
12374                        }
12375                        
12376                        String procedureParent = SQLUtil.trimColumnStringQuote(getProcedureParentName(stmt));
12377                        if (procedureParent != null) {
12378                                modelManager.bindFunctionResult(procedureParentAliasKey(stmt), returnResult);
12379                        }
12380                }
12381                else if (stmt.getSubquery() != null) {
12382                        analyzeSelectStmt(stmt.getSubquery());
12383                        ResultSet subResultSet = (ResultSet)modelManager.getModel(stmt.getSubquery());
12384                        String procedureParent = SQLUtil.trimColumnStringQuote(getProcedureParentName(stmt));
12385                        if (procedureParent != null && subResultSet!=null) {
12386                                modelManager.bindFunctionResult(procedureParentAliasKey(stmt), subResultSet);
12387                        }
12388                }
12389        }
12390        
12391        /**
12392         * The column that stands for a composite variable as a WHOLE (#695).
12393         *
12394         * <p>A PL/SQL record variable used to be modelled as one opaque column, so
12395         * "the" column of a variable was always {@code getColumns().get(0)}. Once
12396         * records expand to per-field columns, {@code get(0)} silently means "an
12397         * arbitrary field" — the edge that FILLS the record then lands on one
12398         * field (or, worse, every producer field is fanned onto it). The whole-
12399         * record binding must instead land on the record's own {@code *} column,
12400         * which stays distinct per variable instance and can be expanded once the
12401         * pairing is known.
12402         */
12403        private TableColumn compositeBindingColumn(Variable variable) {
12404                if (variable.getColumns() == null || variable.getColumns().isEmpty()) {
12405                        return tableStarColumn(variable);
12406                }
12407                if (variable.getColumns().size() == 1) {
12408                        return variable.getColumns().get(0);
12409                }
12410                if (variable.isTypeOrderedFields()) {
12411                        // Fields from the TYPE declaration: the star expands onto exactly
12412                        // those fields at serialization — every one is genuinely filled.
12413                        return tableStarColumn(variable);
12414                }
12415                // Consumption-shaped fields (pseudo-columns, expression names) carry no
12416                // per-field meaning; expanding a star over them fans every producer onto
12417                // every artifact. Keep the historical whole-record binding.
12418                return variable.getColumns().get(0);
12419        }
12420
12421        /**
12422         * The {@code *} column of {@code table}, created on first use with the
12423         * expand-star convention (as the %ROWTYPE declaration path does): the
12424         * serializer expands a relation targeting it onto the sibling fields it
12425         * star-links, so the edge always cites DECLARED columns — a relationship
12426         * must never cite a column the model does not declare.
12427         */
12428        private TableColumn tableStarColumn(Table table) {
12429                if (table.getColumns() != null) {
12430                        for (TableColumn existing : table.getColumns()) {
12431                                if (existing.isStarColumn()) {
12432                                        return existing;
12433                                }
12434                        }
12435                }
12436                TableColumn starColumn = modelFactory.createTableColumn(table, starObjectName(), true);
12437                starColumn.setShowStar(false);
12438                starColumn.setExpandStar(true);
12439                return starColumn;
12440        }
12441
12442        private TObjectName starObjectName() {
12443                TObjectName starColumn = DlineageUtil.syntheticStarName();
12444                return starColumn;
12445        }
12446
12447        private void analyzeFetchStmt(TFetchStmt stmt) {
12448                if (stmt.getVariableNames() != null) {
12449                        for (int i = 0; i < stmt.getVariableNames().size(); i++) {
12450                                TExpression variableExpression = stmt.getVariableNames().getExpression(i);
12451                                if (variableExpression.getExpressionType() == EExpressionType.simple_object_name_t) {
12452                                        TObjectName columnObject = variableExpression.getObjectOperand();
12453                                        if (columnObject.getDbObjectType() == EDbObjectType.variable) {
12454                                                continue;
12455                                        }
12456
12457                                        if (columnObject.getColumnNameOnly().startsWith("@") && (option.getVendor() == EDbVendor.dbvmssql
12458                                                        || option.getVendor() == EDbVendor.dbvazuresql)) {
12459                                                continue;
12460                                        }
12461
12462                                        if (columnObject.getColumnNameOnly().startsWith(":") && (option.getVendor() == EDbVendor.dbvhana
12463                                                        || option.getVendor() == EDbVendor.dbvteradata)) {
12464                                                continue;
12465                                        }
12466
12467                                        Variable cursorVariable = modelFactory.createReferencedVariable(columnObject);
12468                                        cursorVariable.setSubType(SubType.record);
12469                                        if (cursorVariable.isDetermined()) {
12470                                                if (stmt.getCursorName() != null) {
12471                                                        // The cursor FETCH names: declared here, in an enclosing routine or in the package.
12472                                                        Table cursor = modelManager.visibleLocal(withoutColon(stmt.getCursorName().toString()),
12473                                                                        LocalSymbolKey.Kind.CURSOR);
12474                                                        if (cursor != null) {
12475                                                                // FETCH ... INTO binds POSITIONALLY (#695): the k-th
12476                                                                // cursor column fills the k-th record field, whatever
12477                                                                // either is called. Matching by name paired
12478                                                                // like-named columns that are NOT each other's source
12479                                                                // and, when nothing matched, fanned every cursor
12480                                                                // column onto every field — edges the SQL never
12481                                                                // asserts. A field the cursor's known width cannot
12482                                                                // account for keeps the cursor's own * as its source:
12483                                                                // filled, but by an unresolved component.
12484                                                                List<TableColumn> recordFields = cursorVariable.getColumns();
12485                                                                List<TableColumn> cursorColumns = cursor.getColumns();
12486                                                                if (columnObject.toString().indexOf('.') != -1) {
12487                                                                        // FETCH ... INTO rec.field names its target
12488                                                                        // explicitly (#695): bind THAT field to the
12489                                                                        // cursor column at this INTO position —
12490                                                                        // iterating every field here fanned each
12491                                                                        // cursor column onto the whole record.
12492                                                                        TableColumn namedField = modelFactory.createTableColumn(cursorVariable,
12493                                                                                        columnObject, true);
12494                                                                        if (namedField != null) {
12495                                                                                DataFlowRelationship dottedRelation = modelFactory.createDataFlowRelation();
12496                                                                                dottedRelation.setEffectType(EffectType.cursor);
12497                                                                                if (cursorColumns != null && i < cursorColumns.size()) {
12498                                                                                        dottedRelation.addSource(
12499                                                                                                        new TableColumnRelationshipElement(cursorColumns.get(i)));
12500                                                                                } else {
12501                                                                                        dottedRelation.addSource(
12502                                                                                                        new TableColumnRelationshipElement(tableStarColumn(cursor)));
12503                                                                                }
12504                                                                                dottedRelation.setTarget(new TableColumnRelationshipElement(namedField));
12505                                                                        }
12506                                                                        continue;
12507                                                                }
12508                                                                boolean ordinalPairing = stmt.getVariableNames().size() == 1
12509                                                                                && cursorColumns != null && !cursorColumns.isEmpty()
12510                                                                                && cursorVariable.isTypeOrderedFields();
12511                                                                // A SCALAR at INTO position i pairs with cursor
12512                                                                // column i when the widths agree — positional by
12513                                                                // the INTO list itself, no type order involved.
12514                                                                boolean intoPositional = recordFields.size() == 1
12515                                                                                && cursorColumns != null
12516                                                                                && cursorColumns.size() == stmt.getVariableNames().size();
12517                                                                boolean hasRealField = false;
12518                                                                for (TableColumn field : recordFields) {
12519                                                                        if (field.getName() != null && !field.isStarColumn()) {
12520                                                                                hasRealField = true;
12521                                                                                break;
12522                                                                        }
12523                                                                }
12524                                                                int fieldOrdinal = 0;
12525                                                                for (int k = 0; k < recordFields.size(); k++) {
12526                                                                        TableColumn variableProperty = recordFields.get(k);
12527                                                                        if (variableProperty.isStarColumn() && hasRealField) {
12528                                                                                // With real fields present the wildcard is a
12529                                                                                // binding artifact (codex-696 F2). When it is
12530                                                                                // the ONLY column it IS the whole-record
12531                                                                                // binding — skipping it dropped every fill
12532                                                                                // whose consumption comes after the FETCH.
12533                                                                                continue;
12534                                                                        }
12535                                                                        DataFlowRelationship dataflowRelation = modelFactory.createDataFlowRelation();
12536                                                                        dataflowRelation.setEffectType(EffectType.cursor);
12537                                                                        if (ordinalPairing && fieldOrdinal < cursorColumns.size()) {
12538                                                                                dataflowRelation.addSource(
12539                                                                                                new TableColumnRelationshipElement(cursorColumns.get(fieldOrdinal)));
12540                                                                        } else if (intoPositional) {
12541                                                                                dataflowRelation.addSource(
12542                                                                                                new TableColumnRelationshipElement(cursorColumns.get(i)));
12543                                                                        } else if (stmt.getVariableNames().size() > 1) {
12544                                                                                dataflowRelation.addSource(
12545                                                                                                new TableColumnRelationshipElement(tableStarColumn(cursor), i));
12546                                                                        } else {
12547                                                                                dataflowRelation.addSource(
12548                                                                                                new TableColumnRelationshipElement(tableStarColumn(cursor)));
12549                                                                        }
12550                                                                        dataflowRelation.setTarget(new TableColumnRelationshipElement(variableProperty));
12551                                                                        fieldOrdinal++;
12552                                                                }
12553                                                        }
12554                                                }
12555                                                
12556                                        } else {
12557                                                TableColumn variableProperty = null;
12558                                                if (stmt.getVariableNames().size() == 1) {
12559                                                        if (cursorVariable.getColumns() == null || cursorVariable.getColumns().isEmpty()) {
12560                                                                TObjectName starColumn = DlineageUtil.syntheticStarName();
12561                                                                variableProperty = modelFactory.createTableColumn(cursorVariable, starColumn, true);
12562                                                        } else {
12563                                                                variableProperty = cursorVariable.getColumns().get(0);
12564                                                        }
12565                                                } else {
12566                                                        variableProperty = modelFactory.createTableColumn(cursorVariable, columnObject, true);
12567                                                }
12568
12569                                                if (stmt.getCursorName() != null) {
12570                                                        // The cursor FETCH names: declared here, in an enclosing routine or in the package.
12571                                                        Table cursor = modelManager.visibleLocal(withoutColon(stmt.getCursorName().toString()),
12572                                                                        LocalSymbolKey.Kind.CURSOR);
12573                                                        if (cursor != null) {
12574                                                                for (int j = 0; j < cursor.getColumns().size(); j++) {
12575                                                                        DataFlowRelationship dataflowRelation = modelFactory.createDataFlowRelation();
12576                                                                        dataflowRelation.setEffectType(EffectType.cursor);
12577                                                                        if (stmt.getVariableNames().size() == 1) {
12578                                                                                dataflowRelation.addSource(
12579                                                                                                new TableColumnRelationshipElement(cursor.getColumns().get(j)));
12580                                                                        } else {
12581                                                                                dataflowRelation.addSource(
12582                                                                                                new TableColumnRelationshipElement(cursor.getColumns().get(j), i));
12583                                                                        }
12584                                                                        dataflowRelation.setTarget(new TableColumnRelationshipElement(variableProperty));
12585                                                                }
12586                                                        }
12587                                                }
12588                                        }
12589                                }
12590                        }
12591                }
12592        }
12593
12594        private void analyzeFetchStmt(TMssqlFetch stmt) {
12595                if (stmt.getVariableNames() != null) {
12596                        for (int i = 0; i < stmt.getVariableNames().size(); i++) {
12597                                TObjectName columnObject = stmt.getVariableNames().getObjectName(i);
12598                                Variable cursorVariable = modelFactory.createReferencedVariable(columnObject);
12599                                cursorVariable.setCreateTable(true);
12600                                cursorVariable.setSubType(SubType.record);
12601                                TableColumn variableProperty = null;
12602                                if (stmt.getVariableNames().size() == 1) {
12603                                        if (cursorVariable.getColumns() == null || cursorVariable.getColumns().isEmpty()) {
12604                                                TObjectName starColumn = DlineageUtil.syntheticStarName();
12605                                                variableProperty = modelFactory.createTableColumn(cursorVariable, starColumn, true);
12606                                        } else {
12607                                                variableProperty = cursorVariable.getColumns().get(0);
12608                                        }
12609                                } else {
12610                                        variableProperty = modelFactory.createTableColumn(cursorVariable, columnObject, true);
12611                                }
12612
12613                                if (stmt.getCursorName() != null) {
12614                                        // The cursor FETCH names: declared here, in an enclosing routine or in the package.
12615                                        Table cursor = modelManager.visibleLocal(withoutColon(stmt.getCursorName().toString()),
12616                                                        LocalSymbolKey.Kind.CURSOR);
12617                                        if (cursor != null) {
12618                                                for (int j = 0; j < cursor.getColumns().size(); j++) {
12619                                                        DataFlowRelationship dataflowRelation = modelFactory.createDataFlowRelation();
12620                                                        dataflowRelation.setEffectType(EffectType.cursor);
12621                                                        if (stmt.getVariableNames().size() == 1) {
12622                                                                dataflowRelation
12623                                                                                .addSource(new TableColumnRelationshipElement(cursor.getColumns().get(j)));
12624                                                        } else {
12625                                                                dataflowRelation
12626                                                                                .addSource(new TableColumnRelationshipElement(cursor.getColumns().get(j), i));
12627                                                        }
12628                                                        dataflowRelation.setTarget(new TableColumnRelationshipElement(variableProperty));
12629                                                }
12630                                        }
12631                                }
12632                        }
12633
12634                }
12635        }
12636
12637        private void analyzeLoopStmt(TLoopStmt stmt) {
12638
12639                if (stmt.getCursorName() != null && stmt.getIndexName() != null) {
12640                        modelManager.bindCursorIndex(stmt.getIndexName(), stmt.getCursorName());
12641                }
12642
12643                if (stmt.getRecordName() != null && stmt.getSubquery() != null) {
12644                        Variable cursorTempTable = modelFactory.createCursor(stmt);
12645                        cursorTempTable.setVariable(true);
12646                        cursorTempTable.setSubType(SubType.cursor);
12647                        modelManager.bindCursorModel(stmt, cursorTempTable);
12648                        analyzeSelectStmt(stmt.getSubquery());
12649
12650                        TableColumn cursorColumn = null;
12651                        if (cursorTempTable.getColumns() == null || cursorTempTable.getColumns().isEmpty()) {
12652                                TObjectName starColumn = DlineageUtil.syntheticStarName();
12653                                cursorColumn = modelFactory.createTableColumn(cursorTempTable, starColumn, true);
12654                        } else {
12655                                cursorColumn = cursorTempTable.getColumns().get(0);
12656                        }
12657
12658
12659                        ResultSet resultSetModel = (ResultSet) modelManager.getModel(stmt.getSubquery());
12660                        if (resultSetModel != null) {
12661                                for (int i = 0; i < resultSetModel.getColumns().size(); i++) {
12662                                        ResultColumn resultColumn = resultSetModel.getColumns().get(i);
12663                                        DataFlowRelationship dataflowRelation = modelFactory.createDataFlowRelation();
12664                                        dataflowRelation.setEffectType(EffectType.cursor);
12665                                        dataflowRelation.addSource(new ResultColumnRelationshipElement(resultColumn));
12666                                        dataflowRelation.setTarget(new TableColumnRelationshipElement(cursorColumn));
12667                                }
12668                        }
12669                }
12670
12671                for (int i = 0; i < stmt.getStatements().size(); i++) {
12672                        analyzeCustomSqlStmt(stmt.getStatements().get(i));
12673                }
12674        }
12675
12676        private void analyzeForStmt(TForStmt stmt) {
12677                if (stmt.getSubquery() == null) {
12678                        return;
12679                }
12680
12681                Variable cursorTempTable = modelFactory.createCursor(stmt);
12682                cursorTempTable.setVariable(true);
12683                cursorTempTable.setSubType(SubType.cursor);
12684                cursorTempTable.setCreateTable(true);
12685                modelManager.bindCursorModel(stmt, cursorTempTable);
12686                analyzeSelectStmt(stmt.getSubquery());
12687
12688                TableColumn cursorColumn = null;
12689                if (cursorTempTable.getColumns() == null || cursorTempTable.getColumns().isEmpty()) {
12690                        TObjectName starColumn = DlineageUtil.syntheticStarName();
12691                        cursorColumn = modelFactory.createTableColumn(cursorTempTable, starColumn, true);
12692                } else {
12693                        cursorColumn = cursorTempTable.getColumns().get(0);
12694                }
12695
12696
12697                ResultSet resultSetModel = (ResultSet) modelManager.getModel(stmt.getSubquery());
12698                if (resultSetModel != null) {
12699                        for (int i = 0; i < resultSetModel.getColumns().size(); i++) {
12700                                ResultColumn resultColumn = resultSetModel.getColumns().get(i);
12701                                DataFlowRelationship dataflowRelation = modelFactory.createDataFlowRelation();
12702                                dataflowRelation.setEffectType(EffectType.cursor);
12703                                dataflowRelation.addSource(new ResultColumnRelationshipElement(resultColumn));
12704                                dataflowRelation.setTarget(new TableColumnRelationshipElement(cursorColumn));
12705                        }
12706                }
12707
12708                if (stmt.getStatements() != null && stmt.getStatements().size() > 0) {
12709                        for (int i = 0; i < stmt.getStatements().size(); i++) {
12710                                analyzeCustomSqlStmt(stmt.getStatements().get(i));
12711                        }
12712                }
12713        }
12714
12715        private static String withoutColon(String name) {
12716                return name != null && name.startsWith(":") ? name.substring(1) : name;
12717        }
12718
12719        /**
12720         * The column of a record's own table that a field of the record names, for a record declared as a row of that
12721         * table. A field written {@code rec.field} (as in {@code SELECT ... INTO rec.field}) is a reference to the
12722         * variable: it names no column and has no identity, so the column is named by the field's own segment, as the
12723         * table spells it. Any other field name is already a column's.
12724         */
12725        private TObjectName recordFieldColumnName(TObjectName field) {
12726                if (field != null && field.getDbObjectType() == EDbObjectType.variable && field.getPartToken() == null
12727                                && field.getSchemaToken() != null && field.getObjectToken() != null) {
12728                        return TObjectName.createObjectName(option.getVendor(), EDbObjectType.column, field.getObjectToken());
12729                }
12730                return field;
12731        }
12732
12733        /**
12734         * Resolve a declared name visible from the CURRENT scope (#695): a TYPE
12735         * declared at package level is visible from every nested procedure, but
12736         * a same-scope lookup misses it and the declaring variable silently falls
12737         * back to one opaque variant column — the record-fill edges then have no
12738         * per-field target and are lost. Walk the scope chain outward
12739         * (procedure → enclosing procedures → package → top level); never create.
12740         */
12741        private Variable resolveVariableInScope(String name) {
12742                if (SQLUtil.isEmpty(name)) {
12743                        return null;
12744                }
12745                Table found = modelManager.lookupVisibleLocalSymbol(
12746                                LocalSymbolKey.of(null, withoutColon(name), LocalSymbolKey.Kind.TYPE),
12747                                stmtStack.isEmpty() ? null : stmtStack.peek(), true);
12748                return found instanceof Variable ? (Variable) found : null;
12749        }
12750
12751        /**
12752         * Materialize the fields of a record-typed FORMAL argument from its
12753         * declared type before binding an actual to it (#695). Without this the
12754         * formal is one opaque column and the argument-fill edge either lands on
12755         * an arbitrary field or is lost. Field order follows the TYPE declaration
12756         * unless consumption already created some fields (name-deduped), so
12757         * ordinal use of formal fields must not rely on this method alone.
12758         */
12759        private void materializeArgumentFields(Variable variable, Argument argument) {
12760                if (variable == null || argument == null || argument.getDataType() == null) {
12761                        return;
12762                }
12763                if (variable.isDetermined() && variable.getColumns() != null && variable.getColumns().size() > 1) {
12764                        return;
12765                }
12766                Variable typeModel = resolveVariableInScope(argument.getDataType().getDataTypeName());
12767                if (typeModel == null || typeModel == variable || !typeModel.isDetermined()
12768                                || typeModel.getColumns() == null || typeModel.getColumns().isEmpty()) {
12769                        return;
12770                }
12771                // Fields created by earlier consumption sit BEFORE the type fields we
12772                // append here, so the column list is only in type order when nothing
12773                // preceded it (codex-696 F2). Ordinal pairing must not trust a mixed
12774                // list.
12775                boolean hadRealFields = false;
12776                if (variable.getColumns() != null) {
12777                        for (TableColumn existing : variable.getColumns()) {
12778                                if (existing.getName() != null && !existing.isStarColumn()) {
12779                                        hadRealFields = true;
12780                                        break;
12781                                }
12782                        }
12783                }
12784                for (TableColumn typeField : typeModel.getColumns()) {
12785                        if (typeField.getName() == null || typeField.isStarColumn()) {
12786                                continue;
12787                        }
12788                        TObjectName fieldName = new TObjectName();
12789                        fieldName.setString(typeField.getName());
12790                        modelFactory.createTableColumn(variable, fieldName, true);
12791                }
12792                // A formal declared before this had only its wildcard column; now that
12793                // the real fields exist, a relation binding the record as a whole must
12794                // expand onto them at serialization instead of citing the bare star.
12795                if (variable.getColumns() != null) {
12796                        for (TableColumn existing : variable.getColumns()) {
12797                                if (existing.isStarColumn()) {
12798                                        existing.setShowStar(false);
12799                                        existing.setExpandStar(true);
12800                                }
12801                        }
12802                }
12803                variable.setDetermined(true);
12804                if (!hadRealFields) {
12805                        variable.setTypeOrderedFields(true);
12806                }
12807        }
12808
12809        /**
12810         * The variable model of a callee's FORMAL argument, shaped for binding
12811         * (#695). {@code createVariable(Procedure, name, false)} deliberately
12812         * refuses a record-typed formal it recovers through the declaration-time
12813         * scope key (one wildcard column = nothing exact to bind to). Recover the
12814         * variable anyway, materialize its fields from the declared type, and only
12815         * bind when a real field emerged — otherwise keep the historical refusal.
12816         */
12817        private Variable resolveFormalVariable(Procedure callee, Argument argument) {
12818                Variable variable = modelFactory.createVariable(callee, argument.getName(), false);
12819                if (variable != null) {
12820                        materializeArgumentFields(variable, argument);
12821                        return variable;
12822                }
12823                Variable recovered = modelFactory.findFormalVariable(callee, argument.getName());
12824                if (recovered == null) {
12825                        return null;
12826                }
12827                materializeArgumentFields(recovered, argument);
12828                if (recovered.getColumns() != null) {
12829                        for (TableColumn column : recovered.getColumns()) {
12830                                if (!column.isStarColumn()) {
12831                                        return recovered;
12832                                }
12833                        }
12834                }
12835                return null;
12836        }
12837
12838        private void analyzeVarDeclStmt(TVarDeclStmt stmt) {
12839                TTypeName typeName = stmt.getDataType();
12840                if (typeName != null && typeName.toString().toUpperCase(Locale.ROOT).indexOf("ROWTYPE") != -1) {
12841                        Variable cursorVariable = modelFactory.createVariable(stmt.getElementName());
12842                        cursorVariable.setSubType(SubType.record_type);
12843
12844                        Table variableTable = modelFactory.createRowTypeAnchor(typeName.getDataTypeName());
12845                        cursorVariable.setRowTypeAnchor(variableTable);
12846                        if(!variableTable.isCreateTable()) {
12847                                TObjectName starColumn1 = DlineageUtil.syntheticStarName();
12848                                TableColumn variableTableStarColumn = modelFactory.createTableColumn(variableTable, starColumn1, true);
12849                                variableTableStarColumn.setShowStar(false);
12850                                variableTableStarColumn.setExpandStar(true);
12851
12852                                TObjectName starColumn = DlineageUtil.syntheticStarName();
12853                                TableColumn variableProperty = modelFactory.createTableColumn(cursorVariable, starColumn, true);
12854                                variableProperty.setShowStar(false);
12855                                variableProperty.setExpandStar(true);
12856
12857                                DataFlowRelationship dataflowRelation = modelFactory.createDataFlowRelation();
12858                                dataflowRelation.setEffectType(EffectType.rowtype);
12859                                dataflowRelation.addSource(new TableColumnRelationshipElement(variableTableStarColumn));
12860                                dataflowRelation.setTarget(new TableColumnRelationshipElement(variableProperty));
12861                        } else {
12862                                for (TableColumn sourceColumn : variableTable.getColumns()) {
12863                                        String columnName = sourceColumn.getName();
12864                                        TObjectName targetColumn = new TObjectName();
12865                                        targetColumn.setString(columnName);
12866                                        TableColumn variableProperty = modelFactory.createTableColumn(cursorVariable, targetColumn, true);
12867                                        DataFlowRelationship dataflowRelation = modelFactory.createDataFlowRelation();
12868                                        dataflowRelation.setEffectType(EffectType.rowtype);
12869                                        dataflowRelation.addSource(new TableColumnRelationshipElement(sourceColumn));
12870                                        dataflowRelation.setTarget(new TableColumnRelationshipElement(variableProperty));
12871                                }
12872                        }
12873                } else if (stmt.getElementName() != null) {
12874                        Variable variable = modelFactory.createVariable(stmt.getElementName());
12875                        variable.setCreateTable(true);
12876                        variable.setSubType(SubType.record);
12877                        TableColumn tableColumn = null;
12878                        if (stmt.getDataType() != null && (resolveVariableInScope(stmt.getDataType().getDataTypeName())!=null || isCursorType(stmt.getDataType().getDataTypeName()))) {
12879                                Variable cursorVariable = resolveVariableInScope(stmt.getDataType().getDataTypeName());
12880                                if (cursorVariable != null) {
12881                                        if (cursorVariable.getSubType() == SubType.record_type) {
12882                                                variable.setSubType(SubType.record_type);
12883                                        }
12884                                        if(cursorVariable.isDetermined()) {
12885                                                for (int k = 0; k < cursorVariable.getColumns().size(); k++) {
12886                                                        TableColumn sourceColumn = cursorVariable.getColumns().get(k);
12887                                                        TObjectName objectName = new TObjectName();
12888                                                        objectName.setString(sourceColumn.getName());
12889                                                        tableColumn = modelFactory.createTableColumn(variable, objectName, true);
12890                                                        DataFlowRelationship dataflowRelation = modelFactory.createDataFlowRelation();
12891                                                        dataflowRelation.setEffectType(EffectType.cursor);
12892                                                        dataflowRelation
12893                                                                        .addSource(new TableColumnRelationshipElement(sourceColumn));
12894                                                        dataflowRelation.setTarget(new TableColumnRelationshipElement(tableColumn));
12895                                                }
12896                                                variable.setDetermined(true);
12897                                                variable.setTypeOrderedFields(true);
12898                                                return;
12899                                        }
12900                                        else {
12901                                                TObjectName objectName = DlineageUtil.syntheticStarName();
12902                                                tableColumn = modelFactory.createTableColumn(variable, objectName, true);
12903                                                DataFlowRelationship dataflowRelation = modelFactory.createDataFlowRelation();
12904                                                dataflowRelation.setEffectType(EffectType.cursor);
12905                                                dataflowRelation
12906                                                                .addSource(new TableColumnRelationshipElement(cursorVariable.getColumns().get(0)));
12907                                                dataflowRelation.setTarget(new TableColumnRelationshipElement(tableColumn));
12908                                        }
12909                                }
12910                                else {
12911                                        TObjectName objectName = DlineageUtil.syntheticStarName();
12912                                        tableColumn = modelFactory.createTableColumn(variable, objectName, true);                                       
12913                                }
12914                        } else {
12915                                tableColumn = modelFactory.createTableColumn(variable, stmt.getElementName(), true);
12916                                tableColumn.setVariant(true);
12917                        }
12918
12919                        if (stmt.getDefaultValue() != null) {
12920                                columnsInExpr visitor = new columnsInExpr();
12921                                stmt.getDefaultValue().inOrderTraverse(visitor);
12922                                List<TObjectName> objectNames = visitor.getObjectNames();
12923                                List<TParseTreeNode> functions = visitor.getFunctions();
12924                                List<TParseTreeNode> constants = visitor.getConstants();
12925                                List<TSelectSqlStatement> subquerys = visitor.getSubquerys();
12926
12927                                if (functions != null && !functions.isEmpty()) {
12928                                        analyzeFunctionDataFlowRelation(tableColumn, functions, EffectType.function);
12929                                }
12930                                if (subquerys != null && !subquerys.isEmpty()) {
12931                                        analyzeSubqueryDataFlowRelation(tableColumn, subquerys, EffectType.select);
12932                                }
12933                                if (objectNames != null && !objectNames.isEmpty()) {
12934                                        analyzeDataFlowRelation(tableColumn, objectNames, EffectType.select, functions);
12935                                }
12936                                if (constants != null && !constants.isEmpty()) {
12937                                        analyzeConstantDataFlowRelation(tableColumn, constants, EffectType.select, functions);
12938                                }
12939                        }
12940                        
12941                        
12942                }
12943        }
12944
12945        private boolean isCursorType(String dataTypeName) {
12946                if (dataTypeName != null && dataTypeName.toLowerCase(Locale.ROOT).contains("cursor")) {
12947                        return true;
12948                }
12949                return false;
12950        }
12951
12952        private void analyzeSetStmt(TSetStmt stmt) {
12953                TExpression right = stmt.getVariableValue();
12954                TObjectName columnObject = stmt.getVariableName();
12955                if (columnObject != null) {
12956                        TableColumn tableColumn = null;
12957                        Variable tableModel;
12958                        if (columnObject.toString().indexOf(".") != -1) {
12959                                List<String> splits = SQLUtil.parseNames(columnObject.toString());
12960                                tableModel = modelFactory.createReferencedVariable(splits.get(splits.size() - 2), true);
12961                        } else {
12962                                tableModel = modelFactory.createReferencedVariable(columnObject);
12963                        }
12964                        tableModel.setCreateTable(true);
12965                        tableModel.setSubType(SubType.record);
12966
12967                        if (tableModel.getColumns() == null || tableModel.getColumns().isEmpty()) {
12968                                tableColumn = modelFactory.createTableColumn(tableModel, columnObject, true);
12969                        } else {
12970                                tableColumn = tableModel.getColumns().get(0);
12971                        }
12972
12973                        if (tableColumn != null && right!=null) {
12974                                columnsInExpr visitor = new columnsInExpr();
12975                                right.inOrderTraverse(visitor);
12976                                List<TObjectName> objectNames = visitor.getObjectNames();
12977                                List<TParseTreeNode> functions = visitor.getFunctions();
12978                                List<TParseTreeNode> constants = visitor.getConstants();
12979                                List<TSelectSqlStatement> subquerys = visitor.getSubquerys();
12980
12981                                if (functions != null && !functions.isEmpty()) {
12982                                        analyzeFunctionDataFlowRelation(tableColumn, functions, EffectType.function);
12983                                }
12984                                if (subquerys != null && !subquerys.isEmpty()) {
12985                                        analyzeSubqueryDataFlowRelation(tableColumn, subquerys, EffectType.select);
12986                                }
12987                                if (objectNames != null && !objectNames.isEmpty()) {
12988                                        analyzeDataFlowRelation(tableColumn, objectNames, EffectType.select, functions);
12989                                }
12990                                if (constants != null && !constants.isEmpty()) {
12991                                        analyzeConstantDataFlowRelation(tableColumn, constants, EffectType.select, functions);
12992                                }
12993                                
12994                                if(columnObject.toString().equalsIgnoreCase("search_path") && !constants.isEmpty()) {
12995                                        ModelBindingManager.setGlobalSchema(constants.get(0).toString());
12996                                }
12997                        }
12998                }
12999
13000                if(stmt.getAssignments()!=null){
13001                        for (int i = 0; i < stmt.getAssignments().size(); i++) {
13002                                TSetAssignment assignStmt = stmt.getAssignments().getElement(i);
13003                                analyzeSetAssignmentStmt(assignStmt);
13004                        }
13005                }
13006        }
13007
13008        private void analyzeSetAssignmentStmt(TSetAssignment stmt) {
13009                TExpression right = stmt.getParameterValue();
13010                TObjectName columnObject = stmt.getParameterName();
13011                if (columnObject != null) {
13012                        TableColumn tableColumn = null;
13013                        Variable tableModel;
13014                        if (columnObject.toString().indexOf(".") != -1) {
13015                                List<String> splits = SQLUtil.parseNames(columnObject.toString());
13016                                tableModel = modelFactory.createReferencedVariable(splits.get(splits.size() - 2), true);
13017                        } else {
13018                                tableModel = modelFactory.createReferencedVariable(columnObject);
13019                        }
13020                        tableModel.setCreateTable(true);
13021                        tableModel.setSubType(SubType.record);
13022                        if (tableModel.getColumns() == null || tableModel.getColumns().isEmpty()) {
13023                                tableColumn = modelFactory.createTableColumn(tableModel, columnObject, true);
13024                        } else {
13025                                tableColumn = tableModel.getColumns().get(0);
13026                        }
13027
13028                        if (tableColumn != null && right!=null) {
13029                                columnsInExpr visitor = new columnsInExpr();
13030                                right.inOrderTraverse(visitor);
13031                                List<TObjectName> objectNames = visitor.getObjectNames();
13032                                List<TParseTreeNode> functions = visitor.getFunctions();
13033                                List<TParseTreeNode> constants = visitor.getConstants();
13034                                List<TSelectSqlStatement> subquerys = visitor.getSubquerys();
13035
13036                                if (functions != null && !functions.isEmpty()) {
13037                                        analyzeFunctionDataFlowRelation(tableColumn, functions, EffectType.function);
13038                                }
13039                                if (subquerys != null && !subquerys.isEmpty()) {
13040                                        analyzeSubqueryDataFlowRelation(tableColumn, subquerys, EffectType.select);
13041                                }
13042                                if (objectNames != null && !objectNames.isEmpty()) {
13043                                        analyzeDataFlowRelation(tableColumn, objectNames, EffectType.select, functions);
13044                                }
13045                                if (constants != null && !constants.isEmpty()) {
13046                                        analyzeConstantDataFlowRelation(tableColumn, constants, EffectType.select, functions);
13047                                }
13048
13049                                if(columnObject.toString().equalsIgnoreCase("search_path") && !constants.isEmpty()) {
13050                                        ModelBindingManager.setGlobalSchema(constants.get(0).toString());
13051                                }
13052                        }
13053                }
13054        }
13055
13056        private void analyzeMssqlSetStmt(TMssqlSet stmt) {
13057                TExpression right = stmt.getVarExpr();
13058                TObjectName columnObject = stmt.getVarName();
13059                if (columnObject != null) {
13060                        TableColumn tableColumn = null;
13061                        Variable tableModel;
13062                        if (columnObject.toString().indexOf(".") != -1) {
13063                                List<String> splits = SQLUtil.parseNames(columnObject.toString());
13064                                tableModel = modelFactory.createReferencedVariable(splits.get(splits.size() - 2), true);
13065                        } else {
13066                                tableModel = modelFactory.createReferencedVariable(columnObject);
13067                        }
13068                        tableModel.setCreateTable(true);
13069                        tableModel.setSubType(SubType.record);
13070                        if (tableModel.getColumns() == null || tableModel.getColumns().isEmpty()) {
13071                                tableColumn = modelFactory.createTableColumn(tableModel, columnObject, true);
13072                        }
13073                        else {
13074                                tableColumn = tableModel.getColumns().get(0);
13075                        }
13076
13077                        if (tableColumn != null && right!=null) {
13078                                columnsInExpr visitor = new columnsInExpr();
13079                                right.inOrderTraverse(visitor);
13080                                List<TObjectName> objectNames = visitor.getObjectNames();
13081                                List<TParseTreeNode> functions = visitor.getFunctions();
13082                                List<TParseTreeNode> constants = visitor.getConstants();
13083                                List<TSelectSqlStatement> subquerys = visitor.getSubquerys();
13084
13085                                if (functions != null && !functions.isEmpty()) {
13086                                        analyzeFunctionDataFlowRelation(tableColumn, functions, EffectType.function);
13087                                }
13088                                if (subquerys != null && !subquerys.isEmpty()) {
13089                                        analyzeSubqueryDataFlowRelation(tableColumn, subquerys, EffectType.select);
13090                                }
13091                                if (objectNames != null && !objectNames.isEmpty()) {
13092                                        analyzeDataFlowRelation(tableColumn, objectNames, EffectType.select, functions);
13093                                }
13094                                if (constants != null && !constants.isEmpty()) {
13095                                        analyzeConstantDataFlowRelation(tableColumn, constants, EffectType.select, functions);
13096                                }
13097
13098                                if(columnObject.toString().equalsIgnoreCase("search_path") && !constants.isEmpty()) {
13099                                        ModelBindingManager.setGlobalSchema(constants.get(0).toString());
13100                                }
13101                        }
13102                        else if(tableColumn!=null && stmt.getSubquery()!=null){
13103                                analyzeCustomSqlStmt(stmt.getSubquery());
13104                                analyzeSubqueryDataFlowRelation(tableColumn, Arrays.asList(stmt.getSubquery()), EffectType.select);
13105                        }
13106                }
13107        }
13108
13109        /**
13110         * Oracle analog of the T-SQL default-path evaluation (plan Phase 5): when the
13111         * parser's token fold left an EXECUTE IMMEDIATE partial (parameter placeholders)
13112         * or empty, run the AST interpreter over the file's statements — same-file
13113         * literal calls bind parameters naturally — plus literal caller statements
13114         * harvested from OTHER files of a multi-file analysis. The interpreter stores
13115         * evaluated text on each TExecImmeStmt (getEvaluatedDynamicSQLs), which
13116         * getDynamicSQL()/getDynamicStatements() then prefer over the token fold; the
13117         * existing placeholder-contamination classifier keeps half-bound results honest.
13118         * Best-effort: any interpreter failure leaves the token-fold behavior intact.
13119         */
13120        private void maybeEvaluatePlsqlDynamicSql(TExecImmeStmt execImmeStmt) {
13121                if (option.getVendor() != EDbVendor.dbvoracle) {
13122                        return;
13123                }
13124                if (!execImmeStmt.getEvaluatedDynamicSQLs().isEmpty()) {
13125                        return;
13126                }
13127                if (execImmeStmt.getDynamicSQL() != null && !execImmeStmt.isDynamicSQLPartial()) {
13128                        // Fully folded already — do not disturb resolved sites.
13129                        return;
13130                }
13131                TGSqlParser owner = execImmeStmt.getGsqlparser();
13132                if (owner == null || owner.getSqlstatements() == null) {
13133                        return;
13134                }
13135                String ownText = owner.sqltext;
13136                if (ownText == null || !plsqlEvaluatedTexts.add(ownText)) {
13137                        return; // one interpreter run per analysis unit
13138                }
13139                try {
13140                        // Definitions plus fully-literal-vetted caller statements only (same-file
13141                        // first, then cross-file): a caller passing a variable would make the
13142                        // interpreter fold the unevaluated operand to an EMPTY string, silently
13143                        // corrupting object names - such callers never enter the list. Empty
13144                        // list = no vetted caller anywhere = nothing to bind, skip evaluation.
13145                        List<TCustomSqlStatement> toInterpret = gudusoft.gsqlparser.dlineage.dynamicsql.DynamicSqlLineageResolver
13146                                        .plsqlInterpretationStatements(owner.getSqlstatements(), ownText,
13147                                                        routineCatalog, 4);
13148                        if (toInterpret.isEmpty()) {
13149                                return;
13150                        }
13151                        TStatementList combined = new TStatementList();
13152                        for (TCustomSqlStatement stmt : toInterpret) {
13153                                combined.add(stmt);
13154                        }
13155                        TSQLEnv evalEnv = new TSQLEnv(EDbVendor.dbvoracle) {
13156                                @Override
13157                                public void initSQLEnv() {
13158                                }
13159                        };
13160                        new gudusoft.gsqlparser.compiler.TASTEvaluator(combined,
13161                                        new gudusoft.gsqlparser.compiler.TGlobalScope(evalEnv)).eval();
13162                } catch (RuntimeException ex) {
13163                        // interpretation is best-effort; token-fold behavior remains
13164                } catch (java.lang.Error err) {
13165                        if (err instanceof ThreadDeath) {
13166                                throw (ThreadDeath) err;
13167                        }
13168                        // e.g. StackOverflow on pathological nesting — keep the analysis alive
13169                }
13170        }
13171
13172        /**
13173         * Default-path dynamic-SQL evaluation (plan Phase 4a): when the token-level fold
13174         * left an EXEC / sp_executesql / EXEC(@var) site opaque, abstractly evaluate the
13175         * enclosing procedure's string building with no call-site bindings and try to
13176         * materialize this site's SQL. Returns null when no usable text was obtained.
13177         */
13178        private gudusoft.gsqlparser.dlineage.dynamicsql.DynamicSqlLineageResolver.MaterializedSite evaluatedDynamicSqlFor(
13179                        TCustomSqlStatement execStmt) {
13180                if (option.getVendor() != EDbVendor.dbvmssql) {
13181                        return null;
13182                }
13183                // Prefer the first fully-concrete materialization; otherwise the first
13184                // usable partial text; otherwise the first entry (carries the reason).
13185                gudusoft.gsqlparser.dlineage.dynamicsql.DynamicSqlLineageResolver.MaterializedSite best = null;
13186                for (gudusoft.gsqlparser.dlineage.dynamicsql.DynamicSqlLineageResolver.MaterializedSite candidate : selectVariants(execStmt)) {
13187                        if (candidate.sqlText != null && !candidate.partial) {
13188                                return candidate;
13189                        }
13190                        if (best == null || (best.sqlText == null && candidate.sqlText != null)) {
13191                                best = candidate;
13192                        }
13193                }
13194                return best;
13195        }
13196
13197        /**
13198         * Why every evaluated variant of {@code execStmt} is SQL NULL for certain (a query over a literal relation
13199         * returned no row), so the site runs nothing on any of them; null when a variant may run something, or there is
13200         * no evaluation (Mantis 4962).
13201         */
13202        private String provenNullReasonOf(TCustomSqlStatement execStmt) {
13203                if (option.getVendor() != EDbVendor.dbvmssql) {
13204                        return null;
13205                }
13206                String reason = null;
13207                for (gudusoft.gsqlparser.dlineage.dynamicsql.DynamicSqlLineageResolver.MaterializedSite site : selectVariants(execStmt)) {
13208                        if (site.provenNullReason == null) {
13209                                return null;
13210                        }
13211                        if (reason == null) {
13212                                reason = site.provenNullReason;
13213                        }
13214                }
13215                return reason;
13216        }
13217
13218        /**
13219         * The evaluated variants of {@code execStmt} the default analysis uses, in variant order (Mantis 4962): the
13220         * procedure's variants that hold the site; when bindings reached the site and they stand for every call of the
13221         * procedure in the analysis, the bound ones only, even when none of them gives usable text: the unbound
13222         * evaluation is then what a caller nobody wrote would see. When some call is not represented (over the cap, not
13223         * bound, in a routine body, of uncertain ownership), the unbound variants stand for it and stay.
13224         */
13225        private List<gudusoft.gsqlparser.dlineage.dynamicsql.DynamicSqlLineageResolver.MaterializedSite> selectVariants(TCustomSqlStatement execStmt) {
13226                List<gudusoft.gsqlparser.dlineage.dynamicsql.DynamicSqlLineageResolver.MaterializedSite> candidates = new ArrayList<gudusoft.gsqlparser.dlineage.dynamicsql.DynamicSqlLineageResolver.MaterializedSite>();
13227                TCustomSqlStatement proc = option.getVendor() == EDbVendor.dbvmssql ? enclosingStoredProcedure() : null;
13228                if (proc == null) {
13229                        return candidates;
13230                }
13231                DynamicEvaluation evaluation = dynamicEvaluationOf(proc);
13232                if (evaluation.presetsRejected) {
13233                        // the presets bind the procedure but cannot be evaluated: no variant stands in for them
13234                        return candidates;
13235                }
13236                boolean bound = false;
13237                for (Map<TCustomSqlStatement, gudusoft.gsqlparser.dlineage.dynamicsql.DynamicSqlLineageResolver.MaterializedSite> variant : evaluation.variants) {
13238                        gudusoft.gsqlparser.dlineage.dynamicsql.DynamicSqlLineageResolver.MaterializedSite candidate = variant.get(execStmt);
13239                        if (candidate != null) {
13240                                candidates.add(candidate);
13241                                bound |= candidate.bindingSource.getKind() != gudusoft.gsqlparser.dlineage.dynamicsql.BindingSource.Kind.NONE;
13242                        }
13243                }
13244                if (bound && evaluation.bindingsExhaustive) {
13245                        for (Iterator<gudusoft.gsqlparser.dlineage.dynamicsql.DynamicSqlLineageResolver.MaterializedSite> it = candidates.iterator(); it.hasNext();) {
13246                                if (it.next().bindingSource.getKind() == gudusoft.gsqlparser.dlineage.dynamicsql.BindingSource.Kind.NONE) {
13247                                        it.remove();
13248                                }
13249                        }
13250                }
13251                return candidates;
13252        }
13253
13254        /** The innermost stored procedure on the statement stack, or null outside one. */
13255        private TCustomSqlStatement enclosingStoredProcedure() {
13256                for (int i = stmtStack.size() - 1; i >= 0; i--) {
13257                        if (stmtStack.get(i) instanceof TStoredProcedureSqlStatement) {
13258                                return stmtStack.get(i);
13259                        }
13260                }
13261                return null;
13262        }
13263
13264        /**
13265         * The procedure's dynamic-SQL evaluation, built on first use: no bindings first, then one group of variants
13266         * per call-site binding set, same-file callers before the other units of the analysis (cap 4 overall). Each
13267         * step stands alone: a step that throws leaves the others' variants in place and becomes a note naming the
13268         * step and the exception, which every site of the procedure reports, rather than silently cutting the
13269         * evaluation short (Mantis 4962). Calls that name the procedure but are not bound are noted the same way.
13270         */
13271        private DynamicEvaluation dynamicEvaluationOf(TCustomSqlStatement proc) {
13272                DynamicEvaluation evaluation = dynamicEvalCache.get(proc);
13273                if (evaluation != null) {
13274                        return evaluation;
13275                }
13276                evaluation = new DynamicEvaluation();
13277                dynamicEvalCache.put(proc, evaluation);
13278                EDbVendor vendor = option.getVendor();
13279                // two variables the catalog's identifier rules keep apart but that differ only in case would share a value
13280                String collision = gudusoft.gsqlparser.dlineage.dynamicsql.DynamicSqlLineageResolver.caseCollision(proc, sqlenv);
13281                if (collision != null) {
13282                        evaluation.note("the dynamic SQL of the procedure is not evaluated: " + collision);
13283                        evaluation.presetsRejected = true;
13284                        evaluation.bindingsExhaustive = true;
13285                        return evaluation;
13286                }
13287                try {
13288                        evaluation.variants.addAll(gudusoft.gsqlparser.dlineage.dynamicsql.DynamicSqlLineageResolver
13289                                        .materializeSiteVariants(proc, vendor, null, dynamicLineageOptions()));
13290                } catch (RuntimeException ex) {
13291                        evaluation.note(dynamicEvaluationFailure("dynamic SQL evaluation without call-site bindings", ex));
13292                }
13293                // Values preset in the catalog for the parameters (Mantis 4962) bind the procedure ahead of its calls in
13294                // the analysis: they stand for every call, which are then not used.
13295                Map<String, gudusoft.gsqlparser.dlineage.dynamicsql.SqlValue> presets = null;
13296                try {
13297                        presets = gudusoft.gsqlparser.dlineage.dynamicsql.DynamicSqlLineageResolver.presetBindings(proc, sqlenv,
13298                                        null, null);
13299                } catch (RuntimeException ex) {
13300                        evaluation.note(dynamicEvaluationFailure("reading the parameter presets of the catalog", ex));
13301                }
13302                if (presets != null && presets.size() > dynamicLineageOptions().getMaxBindings()) {
13303                        evaluation.note("the presets of the procedure give " + presets.size() + " bindings, more than maxBindings("
13304                                        + dynamicLineageOptions().getMaxBindings() + "): its dynamic SQL is not evaluated with them");
13305                        evaluation.presetsRejected = true;
13306                        evaluation.bindingsExhaustive = true;
13307                        return evaluation;
13308                }
13309                if (presets != null) {
13310                        try {
13311                                evaluation.variants.addAll(gudusoft.gsqlparser.dlineage.dynamicsql.DynamicSqlLineageResolver
13312                                                .materializeSiteVariants(proc, vendor, presets, dynamicLineageOptions(),
13313                                                                gudusoft.gsqlparser.dlineage.dynamicsql.DynamicSqlLineageResolver.presetSource(proc,
13314                                                                                presets)));
13315                        } catch (RuntimeException ex) {
13316                                evaluation.note(dynamicEvaluationFailure("dynamic SQL evaluation with the preset parameters", ex));
13317                        }
13318                        evaluation.bindingsExhaustive = true;
13319                        noteCallsNotUsed(evaluation, proc);
13320                        return evaluation;
13321                }
13322                // Call-site binding discovery (plan Phase 2/4a): literal EXEC calls to
13323                // this proc in the same file supply parameter values that can turn a
13324                // placeholder-bearing template into fully-known SQL.
13325                int bindingSets = 0;
13326                gudusoft.gsqlparser.dlineage.dynamicsql.DynamicSqlLineageResolver.CallSiteDiscovery sameFile = null;
13327                try {
13328                        // a call whose actuals are not literals is bound through its caller: the catalog's presets and the other
13329                        // units bind the caller's own parameters (Mantis 4962)
13330                        sameFile = gudusoft.gsqlparser.dlineage.dynamicsql.DynamicSqlLineageResolver.discoverCallSites(proc, 4,
13331                                        sqlenv, routineCatalog, dynamicLineageOptions());
13332                } catch (RuntimeException ex) {
13333                        evaluation.note(dynamicEvaluationFailure("call-site binding discovery", ex));
13334                }
13335                if (sameFile != null) {
13336                        bindingSets = sameFile.getBindingSets().size();
13337                        addBoundVariants(evaluation, proc, sameFile);
13338                }
13339                evaluation.bindingsExhaustive = sameFile != null && sameFile.isExhaustive();
13340                // Cross-file discovery (plan Phase 5): literal EXEC calls in OTHER
13341                // files of this multi-file analysis. Same-file bindings keep priority;
13342                // the overall binding-set cap stays at 4.
13343                if (routineCatalog != null && bindingSets < 4) {
13344                        gudusoft.gsqlparser.dlineage.dynamicsql.DynamicSqlLineageResolver.CallSiteDiscovery crossFile = null;
13345                        try {
13346                                crossFile = gudusoft.gsqlparser.dlineage.dynamicsql.DynamicSqlLineageResolver
13347                                                .discoverCrossFileCallSites(proc, routineCatalog, 4 - bindingSets, sqlenv, dynamicLineageOptions());
13348                        } catch (RuntimeException ex) {
13349                                evaluation.note(dynamicEvaluationFailure("call-site binding discovery in the other units", ex));
13350                        }
13351                        if (crossFile != null) {
13352                                addBoundVariants(evaluation, proc, crossFile);
13353                        }
13354                        evaluation.bindingsExhaustive &= crossFile != null && crossFile.isExhaustive();
13355                } else if (routineCatalog != null) {
13356                        // the same file gave the bindings the cap allows: the other units' calls are not looked at
13357                        evaluation.bindingsExhaustive = false;
13358                        evaluation.note("calls of the procedure in the other units are not looked at: the same file gives the "
13359                                        + bindingSets + " bindings evaluated");
13360                }
13361                return evaluation;
13362        }
13363
13364        /** Notes that the calls of {@code proc} in the analysis are not used, when there are any: presets bind it. */
13365        private void noteCallsNotUsed(DynamicEvaluation evaluation, TCustomSqlStatement proc) {
13366                int calls = 0;
13367                try {
13368                        gudusoft.gsqlparser.dlineage.dynamicsql.DynamicSqlLineageResolver.CallSiteDiscovery sameFile =
13369                                        gudusoft.gsqlparser.dlineage.dynamicsql.DynamicSqlLineageResolver.discoverCallSites(proc, 4);
13370                        calls += sameFile.getBindingSets().size() + (sameFile.isExhaustive() ? 0 : 1);
13371                        if (routineCatalog != null) {
13372                                gudusoft.gsqlparser.dlineage.dynamicsql.DynamicSqlLineageResolver.CallSiteDiscovery crossFile =
13373                                                gudusoft.gsqlparser.dlineage.dynamicsql.DynamicSqlLineageResolver
13374                                                                .discoverCrossFileCallSites(proc, routineCatalog, 4);
13375                                calls += crossFile.getBindingSets().size() + (crossFile.isExhaustive() ? 0 : 1);
13376                        }
13377                } catch (RuntimeException ex) {
13378                        calls++;
13379                }
13380                if (calls > 0) {
13381                        evaluation.note("calls of the procedure in the analysis are not used: its parameters are preset in the"
13382                                        + " catalog");
13383                }
13384        }
13385
13386        /** Materializes one group of variants per discovered binding set, each step on its own; keeps the notes. */
13387        private void addBoundVariants(DynamicEvaluation evaluation, TCustomSqlStatement proc,
13388                        gudusoft.gsqlparser.dlineage.dynamicsql.DynamicSqlLineageResolver.CallSiteDiscovery discovery) {
13389                for (String note : discovery.getNotes()) {
13390                        evaluation.note(note);
13391                }
13392                List<Map<String, gudusoft.gsqlparser.dlineage.dynamicsql.SqlValue>> sets = discovery.getBindingSets();
13393                for (int i = 0; i < sets.size(); i++) {
13394                        try {
13395                                evaluation.variants.addAll(gudusoft.gsqlparser.dlineage.dynamicsql.DynamicSqlLineageResolver
13396                                                .materializeSiteVariants(proc, option.getVendor(), sets.get(i), dynamicLineageOptions(),
13397                                                                i < discovery.getBindingSources().size() ? discovery.getBindingSources().get(i) : null));
13398                        } catch (RuntimeException ex) {
13399                                evaluation.note(dynamicEvaluationFailure("dynamic SQL evaluation with the call-site binding at "
13400                                                + discovery.getSources().get(i) + " " + sets.get(i), ex));
13401                        }
13402                }
13403        }
13404
13405        /** The dynamic SQL evaluation's options: the defaults, with what this analysis's {@link Option} sets. */
13406        private gudusoft.gsqlparser.dlineage.dynamicsql.DynamicLineageOptions dynamicLineageOptions() {
13407                return gudusoft.gsqlparser.dlineage.dynamicsql.DynamicLineageOptions.createDefault()
13408                                .setCaseSensitiveCompare(option.isDynamicSqlCaseSensitiveCompare());
13409        }
13410
13411        private static String dynamicEvaluationFailure(String step, RuntimeException ex) {
13412                return step + " failed: " + ex.getClass().getName()
13413                                + (ex.getMessage() == null ? "" : ": " + ex.getMessage());
13414        }
13415
13416        /**
13417         * A site's diagnostic with the notes of its procedure's dynamic-SQL evaluation appended. Only the sites of the
13418         * procedure itself take them, not those inside SQL a dynamic site runs. The evaluation is built here when no
13419         * site has needed it yet, so every site of the procedure gets the same notes whatever the order they are
13420         * recorded in (a literal EXEC never needs it otherwise).
13421         */
13422        private String withDynamicEvaluationNotes(String diagnostic) {
13423                if (dynamicFoldDepth != 0 || option == null || option.getVendor() != EDbVendor.dbvmssql) {
13424                        return diagnostic;
13425                }
13426                TCustomSqlStatement proc = enclosingStoredProcedure();
13427                DynamicEvaluation evaluation = proc == null ? null : dynamicEvaluationOf(proc);
13428                if (evaluation == null || evaluation.notes.isEmpty()) {
13429                        return diagnostic;
13430                }
13431                StringBuilder sb = new StringBuilder(diagnostic == null ? "" : diagnostic);
13432                for (String note : evaluation.notes) {
13433                        if (sb.indexOf(note) >= 0) {
13434                                continue;
13435                        }
13436                        if (sb.length() > 0) {
13437                                sb.append("; ");
13438                        }
13439                        sb.append(note);
13440                }
13441                return sb.toString();
13442        }
13443
13444        /** All distinct usable materializations for one EXEC, in stable variant order. */
13445        private List<gudusoft.gsqlparser.dlineage.dynamicsql.DynamicSqlLineageResolver.MaterializedSite> evaluatedDynamicSqlVariantsFor(
13446                        TCustomSqlStatement execStmt, boolean concreteOnly) {
13447                // one variant per text; when two bindings give the same text, its diagnostic names both
13448                Map<String, gudusoft.gsqlparser.dlineage.dynamicsql.DynamicSqlLineageResolver.MaterializedSite> distinct =
13449                                new LinkedHashMap<String, gudusoft.gsqlparser.dlineage.dynamicsql.DynamicSqlLineageResolver.MaterializedSite>();
13450                for (gudusoft.gsqlparser.dlineage.dynamicsql.DynamicSqlLineageResolver.MaterializedSite candidate : selectVariants(execStmt)) {
13451                        if (candidate.sqlText == null || (concreteOnly && candidate.partial) || isWholeTextHole(candidate)) {
13452                                continue;
13453                        }
13454                        String key = candidate.partial + "\u0000" + candidate.sqlText;
13455                        gudusoft.gsqlparser.dlineage.dynamicsql.DynamicSqlLineageResolver.MaterializedSite kept = distinct.get(key);
13456                        // the same text from another binding: one variant with the weaker evidence of the two, naming both
13457                        distinct.put(key, kept == null ? candidate : kept.mergedWith(candidate));
13458                }
13459                return new ArrayList<gudusoft.gsqlparser.dlineage.dynamicsql.DynamicSqlLineageResolver.MaterializedSite>(distinct.values());
13460        }
13461
13462        /**
13463         * Positions of what is analyzed inside dynamic SQL are the source's (Mantis 4851): each token of the parsed
13464         * {@code inner} text takes the line and column of the character it starts on, in the string literal of the
13465         * source that character was read from ({@link gudusoft.gsqlparser.dlineage.dynamicsql.SqlFragment#pieces}). A
13466         * token that starts in a part read from no literal (a variable's value, a transform's output) takes the place
13467         * that part is spliced in, just past the closing quote of the literal before it, else the position of
13468         * {@code exec}, the statement that runs the SQL; so a node's span never runs backwards. Nothing changes when the
13469         * fragments do not spell the text. A string assembled out of source order, and dynamic SQL inside dynamic SQL,
13470         * take the EXEC's position as a whole. The token's offset into the analyzed text is kept: hole marking reads it.
13471         */
13472        private void remapDynamicPositions(TGSqlParser inner,
13473                        List<gudusoft.gsqlparser.dlineage.dynamicsql.SqlFragment> fragments, TCustomSqlStatement exec) {
13474                if (inner == null || inner.sourcetokenlist == null || inner.sqltext == null || fragments == null
13475                                || fragments.isEmpty() || exec == null || exec.getStartToken() == null) {
13476                        return;
13477                }
13478                // Dynamic SQL inside dynamic SQL: its literal's tokens already carry positions in the outer string, whose
13479                // quotes are doubled once more, so a character count from them would drift; it takes the EXEC's position.
13480                boolean exact = dynamicFoldDepth == 0;
13481                int n = inner.sqltext.length();
13482                long[] lines = new long[n];
13483                long[] columns = new long[n];
13484                // past the closing quote of a piece that ends before index i: where an unread part after it is spliced in
13485                long[] spliceLines = new long[n + 1];
13486                long[] spliceColumns = new long[n + 1];
13487                int base = 0;
13488                for (gudusoft.gsqlparser.dlineage.dynamicsql.SqlFragment f : fragments) {
13489                        if (f.isLiteral()) {
13490                                for (gudusoft.gsqlparser.dlineage.dynamicsql.SqlFragment.SourcePiece piece : f.pieces) {
13491                                        long[] end = piece.fill(lines, columns, base);
13492                                        int after = base + piece.textStart + piece.length;
13493                                        if (after >= 0 && after <= n) {
13494                                                spliceLines[after] = end[0];
13495                                                spliceColumns[after] = end[1];
13496                                        }
13497                                }
13498                        }
13499                        base += f.text.length();
13500                }
13501                if (base != n) {
13502                        return;
13503                }
13504                long execLine = exec.getStartToken().lineNo;
13505                long execColumn = exec.getStartToken().columnNo;
13506                long carryLine = execLine;
13507                long carryColumn = execColumn;
13508                for (int i = 0; i < n; i++) {
13509                        if (spliceLines[i] > 0) {
13510                                carryLine = spliceLines[i];
13511                                carryColumn = spliceColumns[i];
13512                        }
13513                        if (lines[i] <= 0) {
13514                                lines[i] = carryLine;
13515                                columns[i] = carryColumn;
13516                        }
13517                        // a string assembled out of source order (a part prepended, one literal used twice) has no span that
13518                        // runs forwards through the source: the whole text takes the EXEC's position
13519                        if (i > 0 && (lines[i] < lines[i - 1] || (lines[i] == lines[i - 1] && columns[i] < columns[i - 1]))) {
13520                                exact = false;
13521                        }
13522                }
13523                if (!exact) {
13524                        java.util.Arrays.fill(lines, execLine);
13525                        java.util.Arrays.fill(columns, execColumn);
13526                }
13527                for (int i = 0; i < inner.sourcetokenlist.size(); i++) {
13528                        TSourceToken token = inner.sourcetokenlist.get(i);
13529                        int at = (int) token.offset;
13530                        if (at >= 0 && at < n) {
13531                                token.lineNo = lines[at];
13532                                token.columnNo = columns[at];
13533                        }
13534                }
13535        }
13536
13537        private boolean analyzeEvaluatedDynamicSqlVariants(TCustomSqlStatement stmt,
13538                        DynamicSqlSite.Kind kind, boolean concreteOnly) {
13539                boolean handled = false;
13540                for (gudusoft.gsqlparser.dlineage.dynamicsql.DynamicSqlLineageResolver.MaterializedSite site :
13541                                evaluatedDynamicSqlVariantsFor(stmt, concreteOnly)) {
13542                        handled |= analyzeEvaluatedDynamicSql(stmt, kind, site);
13543                }
13544                return handled;
13545        }
13546
13547        /**
13548         * Analyze the materialized text of a dynamic site through the ordinary inner-SQL
13549         * path and record a truthful site status. Returns true when the site was handled
13550         * (recorded) here; false when the text was unusable and the caller should record
13551         * its own UNRESOLVED diagnostic.
13552         */
13553        private boolean analyzeEvaluatedDynamicSql(TCustomSqlStatement stmt, DynamicSqlSite.Kind kind,
13554                        gudusoft.gsqlparser.dlineage.dynamicsql.DynamicSqlLineageResolver.MaterializedSite site) {
13555                if (site == null || site.sqlText == null) {
13556                        return false;
13557                }
13558                DynamicSqlTrustMode trustMode = option.getDynamicSqlTrustMode();
13559                boolean provenanceIncomplete = site.provenanceIncomplete;
13560                DynamicHoleEvidence holeEvidence = dynamicHoleEvidence(site);
13561                int holeCount = holeEvidence.count;
13562                boolean holeCountExact = holeEvidence.exact;
13563                Set<Relationship> shadowBefore = provenanceIncomplete && trustMode == DynamicSqlTrustMode.SHADOW
13564                                ? snapshotDynamicRelationships() : null;
13565                int relsBeforeDynamic = modelManager.getRelations().length;
13566                int dynamicSitesBefore = dynamicSqlSites.size();
13567                // This is the ONE place partially-materialized text reaches the ordinary inner-SQL
13568                // path, and the site carries exact provenance — so the endpoints it introduces can be
13569                // marked from real HOLE fragments rather than from a name heuristic.
13570                Set<Table> templateTablesBefore = snapshotTemplateCandidateTables(
13571                                site.partial || provenanceIncomplete);
13572                TGSqlParser sqlparser = createSqlParser(option.getVendor());
13573                sqlparser.sqltext = site.sqlText;
13574                boolean foldStatementsAnalyzed = false;
13575                int result = parseWithCommentBindings(sqlparser);
13576                if (result == 0) {
13577                        remapDynamicPositions(sqlparser, site.fragments, stmt);
13578                }
13579                if (result == 0 && sqlparser.sqlstatements != null) {
13580                        foldStatementsAnalyzed = true;
13581                        List<TConstant> templatedFiles = templateTablesBefore == null
13582                                        ? Collections.<TConstant>emptyList() : noteTemplatedLoadFiles(site, sqlparser);
13583                        dynamicFoldDepth++;
13584                        try {
13585                                for (int i = 0; i < sqlparser.sqlstatements.size(); i++) {
13586                                        analyzeCustomSqlStmt(sqlparser.sqlstatements.get(i));
13587                                }
13588                        } finally {
13589                                dynamicFoldDepth--;
13590                                forgetTemplatedLoadFiles(templatedFiles);
13591                        }
13592                }
13593                if (foldStatementsAnalyzed) {
13594                        markDynamicTemplateEndpoints(templateTablesBefore, site, sqlparser);
13595                }
13596                int[] dynCounts = classifyDynamicSiteLineage(relsBeforeDynamic, site.partial);
13597                int unprovenCount = countNewDynamicRelationships(shadowBefore);
13598                if (result != 0) {
13599                        // Evaluated text always derives from variables, and the evaluation is
13600                        // flow-insensitive — the variable may hold a different value at runtime.
13601                        // A parse failure on it is therefore data-driven UNRESOLVED, never
13602                        // PARSE_ERROR (which stays reserved for compile-time literal arguments,
13603                        // matching the fold path's contract).
13604                        String reason = site.partial ? "partially evaluated dynamic SQL failed to parse; unresolved parts remain"
13605                                        : "evaluated dynamic SQL failed to parse; value is flow-insensitive";
13606                        if (provenanceIncomplete && trustMode == DynamicSqlTrustMode.SHADOW) {
13607                                reason = dynamicTrustReason(reason + "; SHADOW retained legacy publication behavior",
13608                                                unprovenCount, holeCount, holeCountExact);
13609                        }
13610                        recordDynamicSqlSite(stmt, kind, DynamicSqlSite.Status.UNRESOLVED, reason,
13611                                        dynCounts[0], dynCounts[1], unprovenCount,
13612                                        holeCount, holeCountExact, trustMode,
13613                                        relsBeforeDynamic, dynamicSitesBefore, null, site.diagnostic,
13614                                        holeEvidence.fragments, holeEvidence.materializedText, site.executionCertain);
13615                        return true;
13616                }
13617                boolean usable = dynCounts[2] == 0
13618                                && (dynCounts[0] > 0 || (dynCounts[1] == 0 && dynCounts[3] > 0));
13619                // a statement that moves no data (DELETE, TRUNCATE, DROP), its text known in full: resolved, and rightly
13620                // without lineage (Mantis 4962); any other statement without lineage may be one the analysis misses
13621                if (!usable && !site.partial && !provenanceIncomplete && holeCount == 0 && dynCounts[0] == 0
13622                                && dynCounts[1] == 0 && dynCounts[2] == 0 && dynCounts[3] == 0
13623                                && movesNoData(sqlparser.sqlstatements)) {
13624                        usable = true;
13625                }
13626                DynamicSqlSite.Status status = usable ? DynamicSqlSite.Status.RESOLVED
13627                                : DynamicSqlSite.Status.UNRESOLVED;
13628                String reason = usable ? null
13629                                : (dynCounts[2] != 0
13630                                                ? "dynamic SQL references a runtime-built object name; lineage source is a placeholder"
13631                                                : "evaluated dynamic SQL produced no resolved lineage");
13632                if (provenanceIncomplete && trustMode == DynamicSqlTrustMode.SHADOW) {
13633                        status = unprovenCount > 0 ? DynamicSqlSite.Status.PARTIAL
13634                                        : DynamicSqlSite.Status.UNRESOLVED;
13635                        reason = dynamicTrustReason("SHADOW retained legacy publication behavior",
13636                                        unprovenCount, holeCount, holeCountExact);
13637                }
13638                recordDynamicSqlSite(stmt, kind,
13639                                status, reason, dynCounts[0], dynCounts[1], unprovenCount,
13640                                holeCount, holeCountExact, trustMode,
13641                                relsBeforeDynamic, dynamicSitesBefore, null, site.diagnostic,
13642                                holeEvidence.fragments, holeEvidence.materializedText, site.executionCertain);
13643                if (foldStatementsAnalyzed && !dynamicSqlSites.isEmpty()) {
13644                        for (int i = 0; i < sqlparser.sqlstatements.size(); i++) {
13645                                dynamicInnerSiteIndexes.put(sqlparser.sqlstatements.get(i), dynamicSqlSites.size() - 1);
13646                        }
13647                }
13648                return true;
13649        }
13650
13651        /**
13652         * Whether every statement of a dynamic SQL text only removes rows or objects and moves no data: DELETE that reads
13653         * no table but its target (no WHERE, no OUTPUT, no join, no second table), TRUNCATE TABLE, DROP TABLE.
13654         */
13655        private static boolean movesNoData(TStatementList statements) {
13656                if (statements == null || statements.size() == 0) {
13657                        return false;
13658                }
13659                for (int i = 0; i < statements.size(); i++) {
13660                        TCustomSqlStatement stmt = statements.get(i);
13661                        if (stmt instanceof TDeleteSqlStatement) {
13662                                TDeleteSqlStatement delete = (TDeleteSqlStatement) stmt;
13663                                if (delete.getOutputClause() != null || delete.getWhereClause() != null
13664                                                || (delete.joins != null && delete.joins.size() > 1)
13665                                                || (delete.tables != null && delete.tables.size() > 1)) {
13666                                        return false;
13667                                }
13668                                // DELETE t FROM dbo.t t JOIN dbo.u u ON ...: one join entry whose items read another table
13669                                for (int j = 0; delete.joins != null && j < delete.joins.size(); j++) {
13670                                        if (delete.joins.getJoin(j).getJoinItems() != null
13671                                                        && delete.joins.getJoin(j).getJoinItems().size() > 0) {
13672                                                return false;
13673                                        }
13674                                }
13675                        } else if (!(stmt instanceof TTruncateStatement) && !(stmt instanceof TDropTableSqlStatement)) {
13676                                return false;
13677                        }
13678                }
13679                return true;
13680        }
13681
13682        /**
13683         * Trigger correlation names (:NEW / :OLD and REFERENCING aliases) denote rows of the
13684         * trigger's ON table. Bind them so name-based lookups from body statements (e.g. the
13685         * assignment target in ":alias.col := expr") resolve to the base table. Only
13686         * ':'-prefixed and trigger-scoped keys are registered - bare alias names could collide
13687         * with real tables elsewhere in the file.
13688         */
13689        private void bindTriggerCorrelationNames(TPlsqlCreateTrigger trigger, Table sourceTable) {
13690                List<String> correlationNames = new ArrayList<String>();
13691                correlationNames.add("NEW");
13692                correlationNames.add("OLD");
13693                if (trigger.getTriggeringClause() != null
13694                                && trigger.getTriggeringClause().getReferencingClause() != null
13695                                && trigger.getTriggeringClause().getReferencingClause().getReferencingItems() != null) {
13696                        for (TTriggerReferencingItem item : trigger.getTriggeringClause().getReferencingClause()
13697                                        .getReferencingItems()) {
13698                                if (item.getCorrelationName() != null) {
13699                                        correlationNames.add(item.getCorrelationName().toString());
13700                                }
13701                        }
13702                }
13703                Object bodyScope = trigger.getStatements() != null && trigger.getStatements().size() > 0
13704                                ? LocalScopes.scopeOf(modelManager, trigger.getStatements().get(0)) : null;
13705                for (String correlationName : correlationNames) {
13706                        // By name as written: at top level (:NEW in a WHEN clause), and in the trigger body's scope.
13707                        modelManager.publishLocalSymbol(LocalSymbolKey.of(null, correlationName, LocalSymbolKey.Kind.CORRELATION),
13708                                        sourceTable);
13709                        if (bodyScope != null) {
13710                                modelManager.publishLocalSymbol(LocalSymbolKey.of(bodyScope, correlationName,
13711                                                LocalSymbolKey.Kind.CORRELATION), sourceTable);
13712                        }
13713                }
13714        }
13715
13716        private void analyzeAssignStmt(TAssignStmt stmt) {
13717                TExpression left = stmt.getLeft();
13718                TExpression right = stmt.getExpression();
13719                TObjectName columnObject = null;
13720                if (left == null) {
13721                        columnObject = stmt.getVariableName();
13722                } else if (left.getExpressionType() == EExpressionType.simple_object_name_t) {
13723                        columnObject = left.getObjectOperand();
13724                }
13725                if (columnObject != null) {
13726                        TableColumn tableColumn = null;
13727                        List<TableColumn> recordFillTargets = null;
13728                        if (columnObject.getDbObjectType() == EDbObjectType.variable || stmt.getVariableName() != null) {
13729                                Variable tableModel;
13730                                if (columnObject.toString().indexOf(".") != -1) {
13731                                        List<String> splits = SQLUtil.parseNames(columnObject.toString());
13732                                        tableModel = modelFactory.createReferencedVariable(splits.get(splits.size() - 2), true);
13733                                } else {
13734                                        tableModel = modelFactory.createReferencedVariable(columnObject);
13735                                }
13736                                tableModel.setCreateTable(true);
13737                                tableModel.setSubType(SubType.record);
13738                                if (tableModel.getColumns() == null || tableModel.getColumns().isEmpty()) {
13739                                        tableColumn = modelFactory.createTableColumn(tableModel, columnObject, true);
13740                                } else if (tableModel.getColumns().size() == 1) {
13741                                        tableColumn = tableModel.getColumns().get(0);
13742                                } else {
13743                                        // Whole-record assignment to a field-expanded record (#695):
13744                                        // every field is filled by the right-hand side, so the fill
13745                                        // edge is emitted per field. Binding get(0) instead attached
13746                                        // the whole assignment to one arbitrary field and left the
13747                                        // rest with no upstream at all.
13748                                        tableColumn = tableModel.getColumns().get(0);
13749                                        recordFillTargets = tableModel.getColumns();
13750                                }
13751                        } else {
13752                                List<String> splits = SQLUtil.parseNames(columnObject.toString());
13753                                if (splits.size() > 1) {
13754                                        // The record or trigger correlation the target's qualifier names; else a table of that name.
13755                                        Table tableModel = modelManager.visibleLocal(splits.get(splits.size() - 2), LocalSymbolKey.Kind.VARIABLE);
13756                                        if (tableModel == null) {
13757                                                tableModel = modelManager
13758                                                                .getTableByName(DlineageUtil.getTableIdentityKey(splits.get(splits.size() - 2)));
13759                                        }
13760                                        if (tableModel != null) {
13761                                                tableColumn = modelFactory.createTableColumn(tableModel, columnObject, true);
13762                                        }
13763                                }
13764                        }
13765
13766                        if (tableColumn != null && right != null) {
13767                                columnsInExpr visitor = new columnsInExpr();
13768                                right.inOrderTraverse(visitor);
13769                                List<TObjectName> objectNames = visitor.getObjectNames();
13770                                List<TParseTreeNode> functions = visitor.getFunctions();
13771                                List<TParseTreeNode> constants = visitor.getConstants();
13772                                List<TSelectSqlStatement> subquerys = visitor.getSubquerys();
13773
13774                                List<TableColumn> bindTargets = recordFillTargets != null ? recordFillTargets
13775                                                : Arrays.asList(tableColumn);
13776                                for (TableColumn bindTarget : bindTargets) {
13777                                        Transform transform = new Transform();
13778                                        transform.setType(Transform.EXPRESSION);
13779                                        transform.setCode(right);
13780                                        bindTarget.setTransform(transform);
13781                                        if (functions != null && !functions.isEmpty()) {
13782                                                analyzeFunctionDataFlowRelation(bindTarget, functions, EffectType.function);
13783                                        }
13784                                        if (subquerys != null && !subquerys.isEmpty()) {
13785                                                analyzeSubqueryDataFlowRelation(bindTarget, subquerys, EffectType.select);
13786                                        }
13787                                        if (objectNames != null && !objectNames.isEmpty()) {
13788                                                analyzeDataFlowRelation(bindTarget, objectNames, EffectType.select, functions);
13789                                        }
13790                                        if (constants != null && !constants.isEmpty()) {
13791                                                analyzeConstantDataFlowRelation(bindTarget, constants, EffectType.select, functions);
13792                                        }
13793                                }
13794                        }
13795                }
13796        }
13797
13798        private void analyzeOpenForStmt(TOpenforStmt stmt) {
13799                if (stmt.getSubquery() == null) {
13800                        return;
13801                }
13802
13803                Variable cursorTempTable = modelFactory.createCursor(stmt);
13804                cursorTempTable.setVariable(true);
13805                cursorTempTable.setSubType(SubType.cursor);
13806                modelManager.bindCursorModel(stmt, cursorTempTable);
13807                analyzeSelectStmt(stmt.getSubquery());
13808
13809                TableColumn cursorColumn = null;
13810                if (cursorTempTable.getColumns() == null || cursorTempTable.getColumns().isEmpty()) {
13811                        TObjectName starColumn = DlineageUtil.syntheticStarName();
13812                        cursorColumn = modelFactory.createTableColumn(cursorTempTable, starColumn, true);
13813                } else {
13814                        cursorColumn = cursorTempTable.getColumns().get(0);
13815                }
13816
13817                ResultSet resultSetModel = (ResultSet) modelManager.getModel(stmt.getSubquery());
13818                if (resultSetModel != null) {
13819                        for (int i = 0; i < resultSetModel.getColumns().size(); i++) {
13820                                ResultColumn resultColumn = resultSetModel.getColumns().get(i);
13821                                DataFlowRelationship dataflowRelation = modelFactory.createDataFlowRelation();
13822                                dataflowRelation.setEffectType(EffectType.cursor);
13823                                dataflowRelation.addSource(new ResultColumnRelationshipElement(resultColumn));
13824                                dataflowRelation.setTarget(new TableColumnRelationshipElement(cursorColumn));
13825                        }
13826                }
13827        }
13828
13829        private void analyzeCursorDeclStmt(TCursorDeclStmt stmt) {
13830                if (stmt.getSubquery() == null) {
13831                        return;
13832                }
13833
13834                Variable cursorTempTable = modelFactory.createCursor(stmt);
13835                cursorTempTable.setVariable(true);
13836                cursorTempTable.setSubType(SubType.cursor);
13837                modelManager.bindCursorModel(stmt, cursorTempTable);
13838                analyzeSelectStmt(stmt.getSubquery());
13839                
13840                ResultSet resultSetModel = (ResultSet) modelManager.getModel(stmt.getSubquery());
13841                if(resultSetModel!=null && resultSetModel.isDetermined()) {
13842                        for (int i = 0; i < resultSetModel.getColumns().size(); i++) {
13843                                ResultColumn resultColumn = resultSetModel.getColumns().get(i);
13844                                TObjectName columnName = new TObjectName();
13845                                columnName.setString(resultColumn.getName());
13846                                TableColumn cursorColumn = modelFactory.createTableColumn(cursorTempTable, columnName, true);           
13847                                DataFlowRelationship dataflowRelation = modelFactory.createDataFlowRelation();
13848                                dataflowRelation.setEffectType(EffectType.cursor);
13849                                dataflowRelation.addSource(new ResultColumnRelationshipElement(resultColumn));
13850                                dataflowRelation.setTarget(new TableColumnRelationshipElement(cursorColumn));
13851                        }
13852                }
13853                else {
13854                        TableColumn cursorColumn = null;
13855                        if (cursorTempTable.getColumns() == null || cursorTempTable.getColumns().isEmpty()) {
13856                                TObjectName starColumn = DlineageUtil.syntheticStarName();
13857                                cursorColumn = modelFactory.createTableColumn(cursorTempTable, starColumn, true);
13858                        } else {
13859                                cursorColumn = cursorTempTable.getColumns().get(0);
13860                        }
13861                        if (resultSetModel != null) {
13862                                for (int i = 0; i < resultSetModel.getColumns().size(); i++) {
13863                                        ResultColumn resultColumn = resultSetModel.getColumns().get(i);
13864                                        DataFlowRelationship dataflowRelation = modelFactory.createDataFlowRelation();
13865                                        dataflowRelation.setEffectType(EffectType.cursor);
13866                                        dataflowRelation.addSource(new ResultColumnRelationshipElement(resultColumn));
13867                                        dataflowRelation.setTarget(new TableColumnRelationshipElement(cursorColumn));
13868                                }
13869                        }
13870                }
13871                
13872        }
13873
13874        private void analyzeDb2Declare(TDb2SqlVariableDeclaration stmt) {
13875                TDeclareVariableList variables = stmt.getVariables();
13876                if (variables == null) {
13877                        return;
13878                }
13879                for (int i = 0; i < variables.size(); i++) {
13880                        TDeclareVariable variable = variables.getDeclareVariable(i);
13881                        if (variable.getTableTypeDefinitions() != null && variable.getTableTypeDefinitions().size() > 0) {
13882
13883
13884                                TObjectName tableName = variable.getVariableName();
13885                                TTableElementList columns = variable.getTableTypeDefinitions();
13886
13887                                Table tableModel = modelFactory.createTableByName(tableName, true);
13888                                tableModel.setCreateTable(true);
13889                                String procedureParent = getProcedureParentName(stmt);
13890                                if (procedureParent != null) {
13891                                        tableModel.setParent(procedureParent);
13892                                }
13893                                modelManager.bindFunctionResult(procedureParentAliasKey(stmt), tableModel);
13894
13895                                for (int j = 0; j < columns.size(); j++) {
13896                                        TTableElement tableElement = columns.getTableElement(j);
13897                                        TColumnDefinition column = tableElement.getColumnDefinition();
13898                                        if (column != null && column.getColumnName() != null) {
13899                                                modelFactory.createTableColumn(tableModel, column.getColumnName(), true);
13900                                        }
13901                                }
13902                        } else if (variable.getVariableName() != null) {
13903                                Variable cursorVariable = modelFactory.createVariable(variable.getVariableName());
13904                                cursorVariable.setCreateTable(true);
13905                                cursorVariable.setSubType(SubType.record);
13906                                if(variable.getDatatype()!=null && isSimpleDataType(variable.getDatatype())){
13907                                        TableColumn variableProperty = modelFactory.createTableColumn(cursorVariable,
13908                                                        variable.getVariableName(), true);
13909                                }
13910                                else {
13911                                        TObjectName variableProperties = DlineageUtil.syntheticStarName();
13912                                        TableColumn variableProperty = modelFactory.createTableColumn(cursorVariable,
13913                                                        variableProperties, true);
13914                                }
13915                        }
13916                }
13917        }
13918
13919        private void analyzeMssqlDeclare(TMssqlDeclare stmt) {
13920                if (stmt.getDeclareType() == EDeclareType.variable) {
13921                        TDeclareVariableList variables = stmt.getVariables();
13922                        if (variables == null) {
13923                                return;
13924                        }
13925                        for (int i = 0; i < variables.size(); i++) {
13926                                TDeclareVariable variable = variables.getDeclareVariable(i);
13927                                if (variable.getTableTypeDefinitions() != null && variable.getTableTypeDefinitions().size() > 0) {
13928
13929
13930                                        TObjectName tableName = variable.getVariableName();
13931                                        TTableElementList columns = variable.getTableTypeDefinitions();
13932
13933                                        Variable tableModel = modelFactory.createVariable(tableName);
13934                                        tableModel.setVariable(true);
13935                    tableModel.setCreateTable(true);
13936                                        String procedureParent = getProcedureParentName(stmt);
13937                                        if (procedureParent != null) {
13938                                                tableModel.setParent(procedureParent);
13939                                        }
13940                                        modelManager.bindFunctionResult(procedureParentAliasKey(stmt), tableModel);
13941
13942                                        for (int j = 0; j < columns.size(); j++) {
13943                                                TTableElement tableElement = columns.getTableElement(j);
13944                                                TColumnDefinition column = tableElement.getColumnDefinition();
13945                                                if (column != null && column.getColumnName() != null) {
13946                                                        modelFactory.createTableColumn(tableModel, column.getColumnName(), true);
13947                                                }
13948                                        }
13949                                } else if (variable.getVariableName() != null) {
13950                                        Variable cursorVariable = modelFactory.createVariable(variable.getVariableName());
13951                                        cursorVariable.setCreateTable(true);
13952                                        cursorVariable.setSubType(SubType.record);
13953                                        TableColumn variableProperty = null;
13954                                        if (variable.getDatatype() != null && isSimpleDataType(variable.getDatatype())) {
13955                                                variableProperty = modelFactory.createTableColumn(cursorVariable, variable.getVariableName(),
13956                                                                true);
13957                                        } else {
13958                                                TObjectName variableProperties = DlineageUtil.syntheticStarName();
13959                                                variableProperty = modelFactory.createTableColumn(cursorVariable, variableProperties, true);
13960                                        }
13961                                        
13962                                        if (variable.getDefaultValue() != null && variable.getDefaultValue().getSubQuery() != null) {
13963                                                analyzeSelectStmt(variable.getDefaultValue().getSubQuery());
13964                                                ResultSet resultSetModel = (ResultSet) modelManager
13965                                                                .getModel(variable.getDefaultValue().getSubQuery());
13966                                                if (variableProperty != null && resultSetModel != null) {
13967                                                        for (ResultColumn column : resultSetModel.getColumns()) {
13968                                                                DataFlowRelationship dataflowRelation = modelFactory.createDataFlowRelation();
13969                                                                dataflowRelation.setEffectType(EffectType.select);
13970                                                                dataflowRelation.addSource(new ResultColumnRelationshipElement(column));
13971                                                                dataflowRelation.setTarget(new TableColumnRelationshipElement(variableProperty));
13972                                                        }
13973                                                }
13974                                        }
13975                                }
13976                        }
13977                } else if (stmt.getDeclareType() == EDeclareType.cursor) {
13978                        Variable cursorTempTable = modelFactory.createCursor(stmt);
13979                        cursorTempTable.setVariable(true);
13980                        cursorTempTable.setSubType(SubType.cursor);
13981                        modelManager.bindCursorModel(stmt, cursorTempTable);
13982                        analyzeSelectStmt(stmt.getSubquery());
13983                        ResultSet resultSetModel = (ResultSet)modelManager.getModel(stmt.getSubquery());
13984                        if (resultSetModel != null && resultSetModel.isDetermined()) {
13985                                for(ResultColumn resultColumn: resultSetModel.getColumns()){
13986                                        TObjectName starColumn = new TObjectName();
13987                                        starColumn.setString(resultColumn.getName());
13988                                        TableColumn cursorColumn = modelFactory.createTableColumn(cursorTempTable, starColumn, true);
13989                                        DataFlowRelationship dataflowRelation = modelFactory.createDataFlowRelation();
13990                                        dataflowRelation.setEffectType(EffectType.cursor);
13991                                        dataflowRelation.addSource(new ResultColumnRelationshipElement(resultColumn));
13992                                        dataflowRelation.setTarget(new TableColumnRelationshipElement(cursorColumn));
13993                                }
13994                        }
13995                        else {
13996                                TableColumn cursorColumn = null;
13997                                if (cursorTempTable.getColumns() == null || cursorTempTable.getColumns().isEmpty()) {
13998                                        TObjectName starColumn = DlineageUtil.syntheticStarName();
13999                                        cursorColumn = modelFactory.createTableColumn(cursorTempTable, starColumn, true);
14000                                        cursorColumn.setShowStar(false);
14001                                        cursorColumn.setExpandStar(true);
14002                                } else {
14003                                        cursorColumn = cursorTempTable.getColumns().get(0);
14004                                }
14005
14006                                if (resultSetModel != null) {
14007                                        for (int i = 0; i < resultSetModel.getColumns().size(); i++) {
14008                                                ResultColumn resultColumn = resultSetModel.getColumns().get(i);
14009                                                DataFlowRelationship dataflowRelation = modelFactory.createDataFlowRelation();
14010                                                dataflowRelation.setEffectType(EffectType.cursor);
14011                                                dataflowRelation.addSource(new ResultColumnRelationshipElement(resultColumn));
14012                                                dataflowRelation.setTarget(new TableColumnRelationshipElement(cursorColumn));
14013                                        }
14014                                }
14015                        }
14016                }
14017        }
14018
14019
14020        private boolean analyzeMssqlJsonDeclare(TMssqlDeclare stmt, String jsonName, Table jsonTable) {
14021                TDeclareVariableList variables = stmt.getVariables();
14022                if (variables == null) {
14023                        return false;
14024                }
14025                for (int i = 0; i < variables.size(); i++) {
14026                        TDeclareVariable variable = variables.getDeclareVariable(i);
14027                        TObjectName variableName = variable.getVariableName();
14028                        if (DlineageUtil.sameQualifiedName(ESQLDataObjectType.dotTable, variableName.toString(), jsonName)) {
14029                                if (variable.getDefaultValue() != null) {
14030                                        Table variableTable = modelFactory.createJsonVariable(variableName);
14031                                        variableTable.setVariable(true);
14032                                        variableTable.setSubType(SubType.scalar);
14033                                        variableTable.setCreateTable(true);
14034                                        TableColumn property = modelFactory.createVariableProperty(variableTable, variable);
14035
14036                                        for (int j = 0; j < jsonTable.getColumns().size(); j++) {
14037                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
14038                                                relation.setEffectType(EffectType.select);
14039                                                relation.setTarget(new TableColumnRelationshipElement(jsonTable.getColumns().get(j)));
14040                                                relation.addSource(new TableColumnRelationshipElement(property));
14041                                        }
14042                                }
14043                                return true;
14044                        }
14045                }
14046                return false;
14047        }
14048
14049        /**
14050         * True when the statement has a BigQuery external table option, {@code OPTIONS (uris = [...])}. Other table
14051         * options, such as the Databricks {@code USING DELTA}, leave the statement to the {@code LOCATION} path.
14052         */
14053        private static boolean hasBigQueryExternalOption(TCreateTableSqlStatement stmt) {
14054                if (stmt.getTableOptions() == null) {
14055                        return false;
14056                }
14057                for (TCreateTableOption option : stmt.getTableOptions()) {
14058                        if (option.getCreateTableOptionType() == ECreateTableOption.etoBigQueryExternal) {
14059                                return true;
14060                        }
14061                }
14062                return false;
14063        }
14064
14065        private void analyzeCreateTableStmt(TCreateTableSqlStatement stmt) {
14066                Table declaring = null;
14067                try {
14068                        declaring = analyzeCreateTableStmtDeclaring(stmt);
14069                } finally {
14070                        if (declaring != null) {
14071                                declaring.endFieldDeclaration();
14072                        }
14073                }
14074        }
14075
14076        /** The table whose nested-field declaration this CREATE TABLE opened, set while the statement is analyzed. */
14077        private Table fieldDeclaringTable;
14078
14079        private Table analyzeCreateTableStmtDeclaring(TCreateTableSqlStatement stmt) {
14080                fieldDeclaringTable = null;
14081                analyzeCreateTableStmtBody(stmt);
14082                Table declaring = fieldDeclaringTable;
14083                fieldDeclaringTable = null;
14084                return declaring;
14085        }
14086
14087        /**
14088         * Plan rule R10: a CREATE TABLE that declares columns opens the nested-field declaration of its table, ended as
14089         * {@link #recordStatementOrdinals} paired it. False, and nothing opened, for a CREATE ... IF NOT EXISTS of a table
14090         * that exists, which declares nothing.
14091         */
14092        private boolean openFieldDeclaration(Table tableModel, TTable table) {
14093                if (fieldDeclaringTable != null || table == null || table.getTableName() == null
14094                                || !nestedProfile().buildsDeclaredTree()) {
14095                        return true;
14096                }
14097                long created = modelManager.getCurrentStatementOrdinal();
14098                if (noOpCreates.contains(created)) {
14099                        return false;
14100                }
14101                tableModel.openFieldDeclaration(created, declarationEnd(created));
14102                fieldDeclaringTable = tableModel;
14103                return true;
14104        }
14105
14106        private void analyzeCreateTableStmtBody(TCreateTableSqlStatement stmt) {
14107                createTableDeclaredFields.clear();
14108                if (stmt.getCloneSourceTable() != null) {
14109                        analyzeCloneTableStmt(stmt);
14110                        return;
14111                }
14112
14113                TTable table = stmt.getTargetTable();
14114
14115                boolean hasDefinition = false;
14116
14117                if (stmt.getColumnList() != null && stmt.getColumnList().size() > 0) {
14118                        hasDefinition = true;
14119                }
14120
14121                if (table != null) {
14122                        Table tableModel = modelFactory.createTableFromCreateDDL(table, hasDefinition || (stmt.getSubQuery() == null && hasDefinition)
14123                                        || (stmt.getSubQuery()!=null && stmt.getSubQuery().getSetOperatorType() == ESetOperatorType.none && !hasStarItem(stmt.getSubQuery().getResultColumnList())));
14124                        // Authoritative create-effect classification (parse fact, NOT gated on
14125                        // isDetermined() like fromDDL below). CTAS when there is a defining query,
14126                        // plain DDL otherwise. See dlineage-authoritative-endpoint-classification.md.
14127                        tableModel.setCreatedInSql(true);
14128                        tableModel.setEndpointIntroduction(stmt.getSubQuery() != null
14129                                        ? EndpointIntroduction.CTAS : EndpointIntroduction.CREATE_TABLE_DDL);
14130                        if (stmt.isExternal()) {
14131                                tableModel.setExternal(true);
14132                        }
14133
14134                        if (stmt.getSubQuery() != null) {
14135                                Process process = modelFactory.createProcess(stmt);
14136                                tableModel.addProcess(process);
14137                        }
14138
14139                        String procedureParent = getProcedureParentName(stmt);
14140                        if (procedureParent != null) {
14141                                tableModel.setParent(procedureParent);
14142                        }
14143
14144                        if (stmt.isUsingTemplate()) {
14145                                // Snowflake CREATE TABLE ... USING TEMPLATE <query>: the query only
14146                                // infers the column definitions (e.g. ARRAY_AGG(OBJECT_CONSTRUCT(*))
14147                                // over INFER_SCHEMA); it does NOT populate the table. Analyze it so its
14148                                // own sources (the stage / INFER_SCHEMA) still resolve, but do not
14149                                // project its result columns onto the target table or emit CTAS-style
14150                                // data-flow edges that would misrepresent it as data population.
14151                                if (stmt.getSubQuery() != null) {
14152                                        analyzeSelectStmt(stmt.getSubQuery());
14153                                }
14154                                return;
14155                        }
14156
14157                        if (hasDefinition) {
14158                                if(stmt.getSubQuery()!=null) {
14159                                        TSelectSqlStatement subquery = stmt.getSubQuery();
14160                                        analyzeSelectStmt(subquery);
14161                                        Process process = modelFactory.createProcess(stmt);
14162                                        ResultSet resultSetModel = (ResultSet) modelManager.getModel(stmt.getSubQuery());
14163                                        if(resultSetModel.isDetermined()){
14164                                                tableModel.setFromDDL(true);
14165                                        }
14166                                        if (resultSetModel != null) {
14167                                                int resultSetSize = resultSetModel.getColumns().size();
14168                                                int stmtColumnSize = stmt.getColumnList().size();
14169                                                int j = 0;
14170                                                int tableColumnSize = stmtColumnSize;
14171                                                if (resultSetModel.isDetermined() && resultSetSize > tableColumnSize) {
14172                                                        tableColumnSize = resultSetSize;
14173                                                }
14174                                                // a set operation with a star of unknown width: each listed column reads, from each select, the
14175                                                // items that can stand at its position (Mantis 4914)
14176                                                List<TObjectName> aliases = new ArrayList<TObjectName>();
14177                                                for (int i = 0; i < stmtColumnSize; i++) {
14178                                                        if (stmt.getColumnList().getColumn(i).getColumnName() != null) {
14179                                                                aliases.add(stmt.getColumnList().getColumn(i).getColumnName());
14180                                                        }
14181                                                }
14182                                                List<SelectPositions> setSelects = subquery.isCombinedQuery()
14183                                                                ? setOperationPositions(subquery, aliases.size(), shorterList(aliases)) : null;
14184                                                if (setSelects != null) {
14185                                                        mapViewColumnsByCandidates(tableModel, aliases, setSelects, EffectType.create_table, process);
14186                                                }
14187                                                for (int i = 0; setSelects == null && i < tableColumnSize && j < resultSetSize; i++) {
14188                                                        ResultColumn resultColumn = resultSetModel.getColumns().get(j);
14189                                                        if (i < stmtColumnSize) {
14190                                                                TObjectName alias = stmt.getColumnList().getColumn(i).getColumnName();
14191                                                                List<ResultColumn> structItem = null;
14192                                                                
14193                                                                if (!resultSetModel.getColumns().get(j).isStarColumn()) {
14194                                                                        // a select item that builds a STRUCT is one position, all of its fields (rule R8,
14195                                                                        // Codex review of phase 3)
14196                                                                        int width = selectItemWidth(resultSetModel.getColumns(), j);
14197                                                                        if (width > 1 || structFieldPaths.containsKey(resultColumn)) {
14198                                                                                structItem = new ArrayList<ResultColumn>(
14199                                                                                                resultSetModel.getColumns().subList(j, j + width));
14200                                                                        }
14201                                                                        j += width;
14202                                                                } else {
14203                                                                        if (selectItemsFrom(resultSetModel.getColumns(), j, resultSetSize) == stmtColumnSize - i) {
14204                                                                                j++;
14205                                                                        }
14206                                                                }
14207
14208                                                                if (alias != null && structItem != null) {
14209                                                                        TableColumn viewColumn = modelFactory.createTableColumn(tableModel, alias, true);
14210                                                                        if (viewColumn != null) {
14211                                                                                feedFromStructItem(viewColumn, structItem, alias, EffectType.create_table, process,
14212                                                                                                true);
14213                                                                        }
14214                                                                } else if (alias != null) {
14215                                                                        TableColumn viewColumn = modelFactory.createTableColumn(tableModel, alias, true);
14216                                                                        if (resultColumn != null) {
14217                                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
14218                                                                                relation.setEffectType(EffectType.create_table);
14219                                                                                relation.setTarget(new ViewColumnRelationshipElement(viewColumn));
14220                                                                                relation.addSource(positionSource(resultColumn));
14221                                                                                relation.setProcess(process);
14222                                                                                feedBuiltFieldNodes(viewColumn, resultColumn, EffectType.create_table, process, true);
14223                                                                        }
14224                                                                } else if (resultColumn.getColumnObject() instanceof TObjectName) {
14225                                                                        TableColumn viewColumn = modelFactory.createTableColumn(tableModel,
14226                                                                                        (TObjectName) resultColumn.getColumnObject(), true);
14227                                                                        if (resultColumn != null) {
14228                                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
14229                                                                                relation.setEffectType(EffectType.create_table);
14230                                                                                relation.setTarget(new ViewColumnRelationshipElement(viewColumn));
14231                                                                                relation.addSource(new ResultColumnRelationshipElement(resultColumn));
14232                                                                                relation.setProcess(process);
14233                                                                        }
14234                                                                } else if (resultColumn.getColumnObject() instanceof TResultColumn) {
14235                                                                        TableColumn viewColumn = modelFactory.createTableColumn(tableModel,
14236                                                                                        ((TResultColumn) resultColumn.getColumnObject()).getFieldAttr(), true);
14237                                                                        ResultColumn column = (ResultColumn) modelManager
14238                                                                                        .getModel(resultColumn.getColumnObject());
14239                                                                        if (column != null && column.hasStarLinkColumn()) {
14240                                                                                viewColumn.shareStarLinksFrom(column);
14241                                                                        }
14242                                                                        if (resultColumn != null) {
14243                                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
14244                                                                                relation.setEffectType(EffectType.create_table);
14245                                                                                relation.setTarget(new ViewColumnRelationshipElement(viewColumn));
14246                                                                                relation.addSource(new ResultColumnRelationshipElement(resultColumn));
14247                                                                                relation.setProcess(process);
14248                                                                        }
14249                                                                }
14250                                                        }
14251                                                        else if(resultSetModel.isDetermined()){
14252                                                                TObjectName tableName = new TObjectName();
14253                                                                tableName.setString(resultColumn.getName());
14254                                                                TableColumn viewColumn = modelFactory.createTableColumn(tableModel, tableName, true);
14255                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
14256                                                                relation.setEffectType(EffectType.create_table);
14257                                                                relation.setTarget(new ViewColumnRelationshipElement(viewColumn));
14258                                                                relation.addSource(new ResultColumnRelationshipElement(resultColumn));
14259                                                                relation.setProcess(process);
14260                                                                j++;
14261                                                        }
14262                                                }
14263                                                if (resultSetModel != null && !resultSetModel.getRelationRows().getHoldRelations().isEmpty()) {
14264                                                        ImpactRelationship impactRelation = modelFactory.createImpactRelation();
14265                                                        impactRelation.setEffectType(EffectType.create_table);
14266                                                        impactRelation.addSource(new RelationRowsRelationshipElement<ResultSetRelationRows>(
14267                                                                        resultSetModel.getRelationRows()));
14268                                                        impactRelation.setTarget(
14269                                                                        new RelationRowsRelationshipElement<TableRelationRows>(tableModel.getRelationRows()));
14270                                                }
14271                                        }
14272
14273                                        if (subquery.getResultColumnList() == null && subquery.getValueClause() != null
14274                                                        && subquery.getValueClause().getValueRows().size() == stmt.getColumnList().size()) {
14275                                                for (int i = 0; i < stmt.getColumnList().size(); i++) {
14276                                                        TObjectName alias = stmt.getColumnList().getColumn(i).getColumnName();
14277
14278                                                        if (alias != null) {
14279                                                                TableColumn viewColumn = modelFactory.createTableColumn(tableModel, alias, true);
14280
14281                                                                TExpression expression = subquery.getValueClause().getValueRows().getValueRowItem(i).getExpr();
14282
14283                                                                columnsInExpr visitor = new columnsInExpr();
14284                                                                expression.inOrderTraverse(visitor);
14285                                                                List<TObjectName> objectNames = visitor.getObjectNames();
14286                                                                List<TParseTreeNode> functions = visitor.getFunctions();
14287
14288                                                                if (functions != null && !functions.isEmpty()) {
14289                                                                        analyzeFunctionDataFlowRelation(viewColumn, functions, EffectType.select);
14290
14291                                                                }
14292
14293                                                                List<TSelectSqlStatement> subquerys = visitor.getSubquerys();
14294                                                                if (subquerys != null && !subquerys.isEmpty()) {
14295                                                                        analyzeSubqueryDataFlowRelation(viewColumn, subquerys, EffectType.select);
14296                                                                }
14297
14298                                                                analyzeDataFlowRelation(viewColumn, objectNames, EffectType.select, functions);
14299                                                                List<TParseTreeNode> constants = visitor.getConstants();
14300                                                                analyzeConstantDataFlowRelation(viewColumn, constants, EffectType.select, functions);
14301                                                        }
14302                                                }
14303                                        }
14304                                        
14305                                        return;
14306                                }
14307                                else {
14308                                        boolean declares = openFieldDeclaration(tableModel, table);
14309                                        for (int i = 0; i < stmt.getColumnList().size(); i++) {
14310                                                TColumnDefinition column = stmt.getColumnList().getColumn(i);
14311                                                if (modelsNestedFieldsAsColumns() && column.getDatatype() != null
14312                                                                && column.getDatatype().getTypeOfList() != null
14313                                                                && column.getDatatype().getTypeOfList().getColumnDefList() != null) {
14314                                                        for (int j = 0; j < column.getDatatype().getTypeOfList().getColumnDefList().size(); j++) {
14315                                                                TObjectName columnName = new TObjectName();
14316                                                                if (column.getDatatype().getDataType() == EDataType.array_t) {
14317//                                                                      columnName.setString(column.getColumnName().getColumnNameOnly() + ".array."
14318//                                                                                      + column.getDatatype().getTypeOfList().getColumnDefList().getColumn(j)
14319//                                                                                                      .getColumnName().getColumnNameOnly());
14320                                                                        columnName.setString(column.getColumnName().getColumnNameOnly() + "."
14321                                                                                        + column.getDatatype().getTypeOfList().getColumnDefList().getColumn(j)
14322                                                                                        .getColumnName().getColumnNameOnly());
14323                                                                } else {
14324                                                                        columnName.setString(column.getColumnName().getColumnNameOnly() + "."
14325                                                                                        + column.getDatatype().getTypeOfList().getColumnDefList().getColumn(j)
14326                                                                                                        .getColumnName().getColumnNameOnly());
14327                                                                }
14328                                                                TableColumn tableColumn = modelFactory.createTableColumn(tableModel, columnName,
14329                                                                                hasDefinition);
14330                                                                tableColumn.setStruct(true);
14331                                                                tableColumn.setColumnIndex(i);
14332                                                                appendTableColumnToSQLEnv(tableModel, tableColumn);
14333                                                                
14334                                                                if(option.getAnalyzeMode() == AnalyzeMode.crud) {
14335                                                                        CrudRelationship crudRelationship = modelFactory.createCrudRelation();
14336                                                                        crudRelationship.setTarget(new TableColumnRelationshipElement(tableColumn));
14337                                                                        crudRelationship.setEffectType(EffectType.create_table);
14338                                                                }
14339                                                        }
14340                                                        continue;
14341                                                }
14342                                                if (modelsNestedFieldsAsColumns() && column.getDatatype() != null
14343                                                                && column.getDatatype().getColumnDefList() != null) {
14344                                                        Stack<TColumnDefinition> columnPaths = new Stack<TColumnDefinition>();
14345                                                        flattenStructColumns(hasDefinition, tableModel, column, columnPaths, i);
14346                                                        continue;
14347                                                }
14348                                                TableColumn tableColumn = modelFactory.createTableColumn(tableModel, column.getColumnName(),
14349                                                                hasDefinition);
14350                                                if (tableColumn == null) {
14351                                                        continue;
14352                                                }
14353
14354                                                if(option.getAnalyzeMode() == AnalyzeMode.crud) {
14355                                                        CrudRelationship crudRelationship = modelFactory.createCrudRelation();
14356                                                        crudRelationship.setTarget(new TableColumnRelationshipElement(tableColumn));
14357                                                        crudRelationship.setEffectType(EffectType.create_table);
14358                                                }
14359
14360                                                if (nestedProfile().buildsDeclaredTree() && declares) {
14361                                                        List<TableColumn> declaredFields = new ArrayList<TableColumn>();
14362                                                        createTableDeclaredFields.put(tableColumn, declaredFields);
14363                                                        createStructFieldNodes(tableColumn, column.getDatatype(),
14364                                                                        new ArrayList<gudusoft.gsqlparser.common.structured.StructuredPathSegment>(),
14365                                                                        column.getColumnName(), declaredFields);
14366                                                }
14367
14368                                                if (column.getDatatype() != null && column.getDatatype().getDataType() == EDataType.variant_t) {
14369                                                        if (tableColumn != null) {
14370                                                                tableColumn.setVariant(true);
14371                                                        }
14372                                                }
14373                                                if (column.getDatatype() != null) {
14374                                                        String dType = column.getDatatype().getDataTypeName();
14375                                                        if ((!SQLUtil.isEmpty(dType)) && (dType.indexOf("_") > 0)) {
14376                                                                dType = dType.split("_")[0];
14377                                                        }
14378                                                        tableColumn.setDataType(dType);
14379                                                }
14380
14381                                                // Inline (column-level) PK / FK constraint FLAGS. The
14382                                                // table-level pass below (stmt.getTableConstraints())
14383                                                // never sees inline constraints, so set the flags here.
14384                                                // Without this, "id INTEGER PRIMARY KEY" or
14385                                                // "fk INTEGER REFERENCES t (c)" produced no
14386                                                // isPrimaryKey()/isForeignKey() flag, unlike the
14387                                                // equivalent table-level CONSTRAINT form.
14388                                                // EConstraintType.reference is the inline REFERENCES
14389                                                // form; foreign_key is the (rare) inline FOREIGN KEY.
14390                                                // Only flags are set here (no model objects allocated)
14391                                                // so column ids are unchanged; the FK relationship
14392                                                // edges are emitted in a deferred pass after the column
14393                                                // loop (see "inline FK relationships" below), matching
14394                                                // the table-level pass ordering so ids stay stable.
14395                                                TConstraintList inlineConstraints = column.getConstraints();
14396                                                if (inlineConstraints != null) {
14397                                                        for (int c = 0; c < inlineConstraints.size(); c++) {
14398                                                                EConstraintType inlineType = inlineConstraints.getConstraint(c)
14399                                                                                .getConstraint_type();
14400                                                                if (inlineType == EConstraintType.primary_key) {
14401                                                                        tableColumn.setPrimaryKey(true);
14402                                                                } else if (inlineType == EConstraintType.foreign_key
14403                                                                                || inlineType == EConstraintType.reference) {
14404                                                                        tableColumn.setForeignKey(true);
14405                                                                }
14406                                                        }
14407                                                }
14408
14409                                                appendTableColumnToSQLEnv(tableModel, tableColumn);
14410                                        }
14411                                }
14412                        }
14413
14414                        if (stmt.getExternalTableOption("DATA_SOURCE") != null) {
14415                                String dataSourceName = stmt.getExternalTableOption("DATA_SOURCE");
14416                                Table dataSource = modelManager.getTableByName(DlineageUtil.getTableIdentityKey(dataSourceName));
14417                                if (dataSource != null) {
14418                                        TableColumn dataSourceColumn = dataSource.getColumns().get(0);
14419                                        for (int i = 0; i < tableModel.getColumns().size(); i++) {
14420                                                TableColumn column = tableModel.getColumns().get(i);
14421                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
14422                                                relation.setTarget(new TableColumnRelationshipElement(column));
14423                                                relation.addSource(new TableColumnRelationshipElement(dataSourceColumn));
14424
14425                                                appendTableColumnToSQLEnv(tableModel, column);
14426                                        }
14427                                }
14428                        }
14429
14430                        if (stmt.getSubQuery() != null) {
14431
14432                                analyzeSelectStmt(stmt.getSubQuery());
14433
14434                                ResultSet resultSetModel = (ResultSet) modelManager.getModel(stmt.getSubQuery());
14435                                if (resultSetModel != null && !resultSetModel.getRelationRows().getHoldRelations().isEmpty()) {
14436                                        ImpactRelationship impactRelation = modelFactory.createImpactRelation();
14437                                        impactRelation.setEffectType(EffectType.create_table);
14438                                        impactRelation.addSource(new RelationRowsRelationshipElement<ResultSetRelationRows>(
14439                                                        resultSetModel.getRelationRows()));
14440                                        impactRelation.setTarget(
14441                                                        new RelationRowsRelationshipElement<TableRelationRows>(tableModel.getRelationRows()));
14442                                }
14443                        }
14444
14445                        if (stmt.getSubQuery() != null && !stmt.getSubQuery().isCombinedQuery()) {
14446                                SelectResultSet resultSetModel = (SelectResultSet) modelManager
14447                                                .getModel(stmt.getSubQuery().getResultColumnList());
14448                                if(resultSetModel.isDetermined()){
14449                                        tableModel.setDetermined(true);
14450                                        tableModel.setFromDDL(true);
14451                                }
14452                                for (int i = 0; i < resultSetModel.getColumns().size(); i++) {
14453                                        ResultColumn resultColumn = resultSetModel.getColumns().get(i);
14454                                        if (resultSetModel.isDetermined() && resultColumn.isStarColumn()) {
14455                                                continue;
14456                                        }
14457
14458                                        if (resultColumn.getColumnObject() instanceof TResultColumn) {
14459                                                TResultColumn columnObject = (TResultColumn) resultColumn.getColumnObject();
14460
14461                                                TAliasClause alias = columnObject.getAliasClause();
14462                                                if (alias != null && alias.getAliasName() != null) {
14463                                                        TableColumn tableColumn = null;
14464                                                        if (!hasDefinition) {
14465                                                                tableColumn = modelFactory.createTableColumn(tableModel, alias.getAliasName(),
14466                                                                                !hasDefinition);
14467                                                        } else {
14468                                                                tableColumn = tableModel.getColumns().get(i);
14469                                                        }
14470
14471                                                        if (!tableColumn.isStarColumn() && tableModel.isDetermined()) {
14472                                                                appendTableColumnToSQLEnv(tableModel, tableColumn);
14473                                                        }
14474
14475                                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
14476                                                        relation.setEffectType(EffectType.create_table);
14477                                                        relation.setTarget(new TableColumnRelationshipElement(tableColumn));
14478                                                        relation.addSource(new ResultColumnRelationshipElement(resultColumn));
14479                                                        Process process = modelFactory.createProcess(stmt);
14480                                                        relation.setProcess(process);
14481                                                } else if (columnObject.getFieldAttr() != null || (columnObject.getExpr()!=null && columnObject.getExpr().getExpressionType() == EExpressionType.typecast_t)) {
14482                                                        TableColumn tableColumn = null;
14483                                                        TObjectName columnObj = columnObject.getFieldAttr();
14484                                                        if(columnObj == null) {
14485                                                                columnObj = columnObject.getExpr().getLeftOperand().getObjectOperand();
14486                                                        }
14487                                                        if ((columnObj == null || DlineageUtil.isStarName(columnObj)) && !resultColumn.isStarColumn()) {
14488                                                                columnObj = new TObjectName();
14489                                                                columnObj.setString(resultColumn.getName());
14490                                                        }
14491
14492                                                        if(columnObj == null){
14493                                                                logger.info("Can't handle column " + resultColumn.getName());
14494                                                                continue;
14495                                                        }
14496
14497                                                        if (!hasDefinition) {
14498                                                                tableColumn = modelFactory.createTableColumn(tableModel, columnObj,
14499                                                                                !hasDefinition);
14500                                                                if (tableColumn != null) tableColumn.inheritIdentifier(resultColumn);
14501                                                                if (tableColumn == null) {
14502                                                                        if (tableModel.getColumns().isEmpty()) {
14503                                                                                logger.info("Add table " + tableModel.getName() + " column " + columnObj.toString() + " failed");
14504                                                                        }
14505                                                                        else if (resultColumn.isStarColumn()) {
14506                                                                                for (int j = 0; j < tableModel.getColumns().size(); j++) {
14507                                                                                        tableColumn = tableModel.getColumns().get(j);
14508                                                                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
14509                                                                                        relation.setEffectType(EffectType.create_table);
14510                                                                                        relation.setTarget(new TableColumnRelationshipElement(tableColumn));
14511                                                                                        relation.addSource(new ResultColumnRelationshipElement(resultColumn));
14512                                                                                        if (tableColumn.isStarColumn()
14513                                                                                                        && resultColumn.isStarColumn()) {
14514                                                                                                tableModel.setStarStmt("create_table");
14515                                                                                        }
14516                                                                                        Process process = modelFactory.createProcess(stmt);
14517                                                                                        relation.setProcess(process);
14518                                                                                }
14519                                                                        }
14520                                                                        continue;
14521                                                                }
14522                                                                if (!tableColumn.isStarColumn() && tableModel.isDetermined()) {
14523                                                                        appendTableColumnToSQLEnv(tableModel, tableColumn);
14524                                                                }
14525
14526                                                                Object model = modelManager
14527                                                                                .getModel(resultColumn.getColumnObject());
14528                                                                if (model instanceof ResultColumn) {
14529                                                                        ResultColumn column = (ResultColumn) model;
14530                                                                        if (tableColumn.isStarColumn() && column != null
14531                                                                                        && column.hasStarLinkColumn()) {
14532                                                                                tableColumn.shareStarLinksFrom(column);
14533                                                                        }
14534
14535                                                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
14536                                                                        relation.setEffectType(EffectType.create_table);
14537                                                                        relation.setTarget(new TableColumnRelationshipElement(tableColumn));
14538                                                                        relation.addSource(new ResultColumnRelationshipElement(resultColumn));
14539                                                                        if (tableColumn.isStarColumn() && resultColumn.isStarColumn()) {
14540                                                                                tableModel.setStarStmt("create_table");
14541                                                                        }
14542                                                                        Process process = modelFactory.createProcess(stmt);
14543                                                                        relation.setProcess(process);
14544                                                                }
14545                                                                else if(model instanceof LinkedHashMap) {
14546                                                                        String columnName = getColumnNameOnly(resultColumn.getName());
14547                                                                        LinkedHashMap<String, ResultColumn> resultColumns =  (LinkedHashMap<String, ResultColumn>)model;
14548                                                                        if (isStarNode(columnObj)) {
14549                                                                                for (String key : resultColumns.keySet()) {
14550                                                                                        tableColumn = itemColumnTarget(tableModel, resultColumns.get(key));
14551                                                                                        DataFlowRelationship relation = modelFactory
14552                                                                                                        .createDataFlowRelation();
14553                                                                                        relation.setEffectType(EffectType.create_table);
14554                                                                                        relation.setTarget(new TableColumnRelationshipElement(tableColumn));
14555                                                                                        relation.addSource(
14556                                                                                                        new ResultColumnRelationshipElement(resultColumns.get(key)));
14557                                                                                        Process process = modelFactory.createProcess(stmt);
14558                                                                                        relation.setProcess(process);
14559                                                                                }
14560                                                                        } else if (resultColumns.containsKey(columnName)) {
14561                                                                                ResultColumn column = resultColumns.get(columnName);
14562                                                                                tableColumn = modelFactory.createInsertTableColumn(tableModel, column);
14563                                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
14564                                                                                relation.setEffectType(EffectType.create_table);
14565                                                                                relation.setTarget(new TableColumnRelationshipElement(tableColumn));
14566                                                                                relation.addSource(new ResultColumnRelationshipElement(column));
14567                                                                                Process process = modelFactory.createProcess(stmt);
14568                                                                                relation.setProcess(process);
14569                                                                        }
14570                                                                }
14571                                                        } else {
14572                                                                if (resultColumn.isStarColumn()) {
14573                                                                        for (int j = 0; j < tableModel.getColumns().size(); j++) {
14574                                                                                tableColumn = tableModel.getColumns().get(j);
14575                                                                                ResultColumn column = (ResultColumn) modelManager
14576                                                                                                .getModel(resultColumn.getColumnObject());
14577                                                                                if (tableColumn.isStarColumn() && column != null
14578                                                                                                && column.hasStarLinkColumn()) {
14579                                                                                        tableColumn.shareStarLinksFrom(column);
14580                                                                                }
14581
14582                                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
14583                                                                                relation.setEffectType(EffectType.create_table);
14584                                                                                relation.setTarget(new TableColumnRelationshipElement(tableColumn));
14585                                                                                relation.addSource(new ResultColumnRelationshipElement(resultColumn));
14586                                                                                if (tableColumn.isStarColumn()
14587                                                                                                && resultColumn.isStarColumn()) {
14588                                                                                        tableModel.setStarStmt("create_table");
14589                                                                                        ;
14590                                                                                }
14591                                                                                Process process = modelFactory.createProcess(stmt);
14592                                                                                relation.setProcess(process);
14593                                                                        }
14594                                                                } else {
14595                                                                        tableColumn = tableModel.getColumns().get(i);
14596                                                                        Object model = modelManager
14597                                                                                        .getModel(resultColumn.getColumnObject());
14598                                                                        String columnName = getColumnNameOnly(resultColumn.getName());
14599                                                                        if(model instanceof LinkedHashMap) {
14600                                                                                LinkedHashMap<String, ResultColumn> resultColumns =  (LinkedHashMap<String, ResultColumn>)model;
14601                                                                                if (resultColumns.size() == tableModel.getColumns().size()) {
14602                                                                                        int j = 0;
14603                                                                                        for (String key : resultColumns.keySet()) {
14604                                                                                                if (j == i) {
14605                                                                                                        ResultColumn column = resultColumns.get(key);
14606                                                                                                        DataFlowRelationship relation = modelFactory
14607                                                                                                                        .createDataFlowRelation();
14608                                                                                                        relation.setEffectType(EffectType.create_table);
14609                                                                                                        relation.setTarget(new TableColumnRelationshipElement(tableColumn));
14610                                                                                                        relation.addSource(new ResultColumnRelationshipElement(column));
14611                                                                                                        Process process = modelFactory.createProcess(stmt);
14612                                                                                                        relation.setProcess(process);
14613                                                                                                }
14614                                                                                                j++;
14615                                                                                        }
14616                                                                                } else if (resultColumns.containsKey(columnName)) {
14617                                                                                        ResultColumn column = resultColumns.get(columnName);
14618                                                                                        tableColumn = modelFactory.createInsertTableColumn(tableModel, column);
14619                                                                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
14620                                                                                        relation.setEffectType(EffectType.create_table);
14621                                                                                        relation.setTarget(new TableColumnRelationshipElement(tableColumn));
14622                                                                                        relation.addSource(new ResultColumnRelationshipElement(column));
14623                                                                                        Process process = modelFactory.createProcess(stmt);
14624                                                                                        relation.setProcess(process);
14625                                                                                } else {
14626                                                                                        throw new UnsupportedOperationException("Can't handle this star case.");
14627                                                                                }
14628                                                                        }
14629                                                                        else if (model instanceof ResultColumn) {
14630                                                                                ResultColumn column = (ResultColumn) modelManager
14631                                                                                                .getModel(resultColumn.getColumnObject());
14632                                                                                if (tableColumn.isStarColumn() && column != null
14633                                                                                                && column.hasStarLinkColumn()) {
14634                                                                                        tableColumn.shareStarLinksFrom(column);
14635                                                                                }
14636
14637                                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
14638                                                                                relation.setEffectType(EffectType.create_table);
14639                                                                                relation.setTarget(new TableColumnRelationshipElement(tableColumn));
14640                                                                                relation.addSource(new ResultColumnRelationshipElement(resultColumn));
14641                                                                                if (tableColumn.isStarColumn()
14642                                                                                                && resultColumn.isStarColumn()) {
14643                                                                                        tableModel.setStarStmt("create_table");
14644                                                                                }
14645                                                                                Process process = modelFactory.createProcess(stmt);
14646                                                                                relation.setProcess(process);
14647                                                                        }
14648                                                                }
14649                                                        }
14650                                                } else {
14651                                                        TableColumn tableColumn = null;
14652                                                        if (!hasDefinition) {
14653                                                                TObjectName columnName = new TObjectName();
14654                                                                columnName.setString(resultColumn.getColumnObject().toString());
14655                                                                tableColumn = modelFactory.createTableColumn(tableModel, columnName, !hasDefinition);
14656                                                                if(tableColumn == null){
14657                                                                        continue;
14658                                                                }
14659                                                        } else {
14660                                                                tableColumn = tableModel.getColumns().get(i);
14661                                                        }
14662                                                        ResultColumn column = (ResultColumn) modelManager.getModel(resultColumn.getColumnObject());
14663                                                        if (column != null && column.hasStarLinkColumn()) {
14664                                                                tableColumn.shareStarLinksFrom(column);
14665                                                        }
14666
14667                                                        if (!tableColumn.isStarColumn() && tableModel.isDetermined()) {
14668                                                                appendTableColumnToSQLEnv(tableModel, tableColumn);
14669                                                        }
14670
14671                                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
14672                                                        relation.setEffectType(EffectType.create_table);
14673                                                        relation.setTarget(new TableColumnRelationshipElement(tableColumn));
14674                                                        relation.addSource(new ResultColumnRelationshipElement(resultColumn));
14675                                                        Process process = modelFactory.createProcess(stmt);
14676                                                        relation.setProcess(process);
14677                                                }
14678                                        } else if (resultColumn.getColumnObject() instanceof TObjectName) {
14679                                                StructFieldPath structField = hasDefinition ? null : structFieldPaths.get(resultColumn);
14680                                                if (structField != null && structField.alias != null) {
14681                                                        // STRUCT tree: a field of a STRUCT or ROW the query builds, payload.a of
14682                                                        // named_struct('a', x) AS payload, is the field node payload.a below the table's column
14683                                                        // payload, as for a view (Mantis 4847, 4834); a nested one is payload.a.b (Mantis 4892)
14684                                                        TObjectName alias = structField.alias;
14685                                                        boolean first = modelManager.getModel(new Pair<Table, TObjectName>(tableModel, alias)) == null;
14686                                                        TableColumn declared = modelFactory.createTableColumn(tableModel, alias, true);
14687                                                        TableColumn node = declared == null ? null
14688                                                                        : modelFactory.createFieldPath(declared, structField.segments(), alias);
14689                                                        if (node != null) {
14690                                                                if (tableModel.isDetermined() && first) {
14691                                                                        appendTableColumnToSQLEnv(tableModel, declared);
14692                                                                }
14693                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
14694                                                                relation.setEffectType(EffectType.create_table);
14695                                                                relation.setTarget(new TableColumnRelationshipElement(node));
14696                                                                relation.addSource(new ResultColumnRelationshipElement(resultColumn));
14697                                                                Process process = modelFactory.createProcess(stmt);
14698                                                                relation.setProcess(process);
14699                                                                continue;
14700                                                        }
14701                                                }
14702                                                TableColumn tableColumn = null;
14703                                                if (!hasDefinition) {
14704                                                        tableColumn = modelFactory.createTableColumn(tableModel,
14705                                                                        (TObjectName) resultColumn.getColumnObject(), !hasDefinition);
14706                                                } else {
14707                                                        tableColumn = tableModel.getColumns().get(i);
14708                                                }
14709
14710                                                if (!tableColumn.isStarColumn() && tableModel.isDetermined()) {
14711                                                        appendTableColumnToSQLEnv(tableModel, tableColumn);
14712                                                }
14713
14714                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
14715                                                relation.setEffectType(EffectType.create_table);
14716                                                relation.setTarget(new TableColumnRelationshipElement(tableColumn));
14717                                                relation.addSource(new ResultColumnRelationshipElement(resultColumn));
14718                                                Process process = modelFactory.createProcess(stmt);
14719                                                relation.setProcess(process);
14720                                        }
14721                                }
14722                        } else if (stmt.getSubQuery() != null) {
14723                                SelectSetResultSet resultSetModel = (SelectSetResultSet) modelManager.getModel(stmt.getSubQuery());
14724                                if(resultSetModel.isDetermined()){
14725                                        tableModel.setDetermined(true);
14726                                        tableModel.setFromDDL(true);
14727                                }
14728                                for (int i = 0; i < resultSetModel.getColumns().size(); i++) {
14729                                        ResultColumn resultColumn = resultSetModel.getColumns().get(i);
14730                                        if (resultColumn.getColumnObject() instanceof TResultColumn) {
14731                                                TResultColumn columnObject = (TResultColumn) resultColumn.getColumnObject();
14732
14733                                                TAliasClause alias = columnObject.getAliasClause();
14734                                                if (alias != null && alias.getAliasName() != null) {
14735                                                        TableColumn tableColumn = null;
14736                                                        if (!hasDefinition) {
14737                                                                tableColumn = modelFactory.createTableColumn(tableModel, alias.getAliasName(),
14738                                                                                !hasDefinition);
14739                                                                if (tableColumn == null) {
14740                                                                        continue;
14741                                                                }
14742                                                        } else {
14743                                                                tableColumn = tableModel.getColumns().get(i);
14744                                                        }
14745
14746                                                        if (!tableColumn.isStarColumn() && tableModel.isDetermined()) {
14747                                                                appendTableColumnToSQLEnv(tableModel, tableColumn);
14748                                                        }
14749
14750                                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
14751                                                        relation.setEffectType(EffectType.create_table);
14752                                                        relation.setTarget(new TableColumnRelationshipElement(tableColumn));
14753                                                        relation.addSource(new ResultColumnRelationshipElement(resultColumn));
14754                                                        Process process = modelFactory.createProcess(stmt);
14755                                                        relation.setProcess(process);
14756                                                        feedBuiltFieldNodes(tableColumn, resultColumn, EffectType.create_table, process, false);
14757                                                } else if (columnObject.getFieldAttr() != null || (columnObject.getExpr()!=null && columnObject.getExpr().getExpressionType() == EExpressionType.typecast_t)) {
14758                                                        TableColumn tableColumn = null;
14759                                                        TObjectName columnObj = columnObject.getFieldAttr();
14760                                                        if(columnObj == null) {
14761                                                                columnObj = columnObject.getExpr().getLeftOperand().getObjectOperand();
14762                                                        }
14763                                                        if (!hasDefinition) {
14764                                                                tableColumn = modelFactory.createTableColumn(tableModel, columnObj,
14765                                                                                !hasDefinition);
14766                                                                if (tableColumn == null) {
14767                                                                        continue;
14768                                                                }
14769                                                        } else {
14770                                                                tableColumn = tableModel.getColumns().get(i);
14771                                                        }
14772                                                        ResultColumn column = (ResultColumn) modelManager.getModel(resultColumn.getColumnObject());
14773                                                        if (column != null && column.hasStarLinkColumn()) {
14774                                                                tableColumn.shareStarLinksFrom(column);
14775                                                        }
14776
14777                                                        if (!tableColumn.isStarColumn() && tableModel.isDetermined()) {
14778                                                                appendTableColumnToSQLEnv(tableModel, tableColumn);
14779                                                        }
14780
14781                                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
14782                                                        relation.setEffectType(EffectType.create_table);
14783                                                        relation.setTarget(new TableColumnRelationshipElement(tableColumn));
14784                                                        relation.addSource(new ResultColumnRelationshipElement(resultColumn));
14785                                                        Process process = modelFactory.createProcess(stmt);
14786                                                        relation.setProcess(process);
14787                                                        feedBuiltFieldNodes(tableColumn, resultColumn, EffectType.create_table, process, false);
14788                                                } else {
14789                                                        ErrorInfo errorInfo = new ErrorInfo();
14790                                                        errorInfo.setErrorType(ErrorInfo.ANALYZE_ERROR);
14791                                                        errorInfo.setErrorMessage("Can't handle the table column " + columnObject.toString());
14792                                                        errorInfo.setStartPosition(new Pair3<Long, Long, String>(
14793                                                                        columnObject.getStartToken().lineNo, columnObject.getStartToken().columnNo,
14794                                                                        ModelBindingManager.getGlobalHash()));
14795                                                        errorInfo.setEndPosition(new Pair3<Long, Long, String>(columnObject.getEndToken().lineNo,
14796                                                                        columnObject.getEndToken().columnNo + columnObject.getEndToken().getAstext().length(),
14797                                                                        ModelBindingManager.getGlobalHash()));
14798                                                        errorInfos.add(errorInfo);
14799                                                        continue;
14800                                                }
14801                                        } else if (resultColumn.getColumnObject() instanceof TObjectName) {
14802                                                TableColumn tableColumn = null;
14803                                                if (!hasDefinition) {
14804                                                        tableColumn = modelFactory.createTableColumn(tableModel,
14805                                                                        (TObjectName) resultColumn.getColumnObject(), !hasDefinition);
14806                                                } else {
14807                                                        tableColumn = tableModel.getColumns().get(i);
14808                                                }
14809
14810                                                if (!tableColumn.isStarColumn() && tableModel.isDetermined()) {
14811                                                        appendTableColumnToSQLEnv(tableModel, tableColumn);
14812                                                }
14813
14814                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
14815                                                relation.setEffectType(EffectType.create_table);
14816                                                relation.setTarget(new TableColumnRelationshipElement(tableColumn));
14817                                                relation.addSource(new ResultColumnRelationshipElement(resultColumn));
14818                                                Process process = modelFactory.createProcess(stmt);
14819                                                relation.setProcess(process);
14820                                                feedBuiltFieldNodes(tableColumn, resultColumn, EffectType.create_table, process, false);
14821                                        }
14822                                }
14823                        } else if (stmt.getLikeTableName() != null) {
14824                                Table likeTableModel = modelFactory.createTableByName(stmt.getLikeTableName());
14825
14826                                if(likeTableModel.isCreateTable()){
14827                                        for(TableColumn column: likeTableModel.getColumns()){
14828                                                TObjectName tableColumn = new TObjectName();
14829                                                tableColumn.setString(column.getName());
14830                                                TableColumn createTableColumn = modelFactory.createTableColumn(tableModel, tableColumn, true);
14831                                                createTableColumn.setPrimaryKey(column.getPrimaryKey());
14832                                                createTableColumn.setForeignKey(column.getForeignKey());
14833                                                createTableColumn.setIndexKey(column.getIndexKey());
14834                                                createTableColumn.setUnqiueKey(column.getUnqiueKey());
14835                                                createTableColumn.setDataType(column.getDataType());
14836                                        }
14837                                        tableModel.setCreateTable(true);
14838                                }
14839
14840//                              Process process = modelFactory.createProcess(stmt);
14841//                              process.setType("Like Table");
14842//                              tableModel.addProcess(process);
14843//
14844//
14845//                              DataFlowRelationship relation = modelFactory.createDataFlowRelation();
14846//                              relation.setEffectType(EffectType.like_table);
14847//                              relation.setTarget(
14848//                                              new RelationRowsRelationshipElement<TableRelationRows>(tableModel.getRelationRows()));
14849//                              relation.addSource(
14850//                                              new RelationRowsRelationshipElement<TableRelationRows>(likeTableModel.getRelationRows()));
14851//                              relation.setProcess(process);
14852                        }
14853
14854                        if (stmt.getStageLocation() != null && stmt.getStageLocation().getStageName() != null) {
14855                                // The stage the table reads, by its name as written: found and compared by identity.
14856                                tableModel.setLocation(stmt.getStageLocation().getStageName().toString());
14857                                Process process = modelFactory.createProcess(stmt);
14858                                process.setType("Create External Table");
14859                                tableModel.addProcess(process);
14860                                Table stage = modelManager.getTableByName(DlineageUtil.getTableIdentityKey(tableModel.getLocation()));
14861                                if (stage == null) {
14862                                        stage = modelManager.getTableByName(
14863                                                        DlineageUtil.getTableIdentityKey(stmt.getStageLocation().toString().replaceFirst("@", "")));
14864                                }
14865                                if (stage == null) {
14866                                        stage = modelFactory.createTableByName(stmt.getStageLocation().toString().replaceFirst("@", ""),
14867                                                        false);
14868                                        stage.setCreateTable(false);
14869                                        stage.setStage(true);
14870                                        if (stmt.getStageLocation().getPath() != null) {
14871                                                stage.setLocation(stmt.getStageLocation().getPath().toString());
14872                                                TObjectName location = new TObjectName();
14873                                                location.setString(stmt.getStageLocation().getPath().toString());
14874                                                modelFactory.createStageLocation(stage, location);
14875                                        } else if (stmt.getRegex_pattern() != null) {
14876                                                stage.setLocation(stmt.getRegex_pattern());
14877                                                TObjectName location = new TObjectName();
14878                                                location.setString(stmt.getRegex_pattern());
14879                                                modelFactory.createStageLocation(stage, location);
14880                                        } else {
14881                                                stage.setLocation("unknownPath");
14882                                                TObjectName location = new TObjectName();
14883                                                location.setString("unknownPath");
14884                                                modelFactory.createStageLocation(stage, location);
14885                                        }
14886                                }
14887                                if (stage != null && !stage.getColumns().isEmpty()) {
14888                                        if (!tableModel.getColumns().isEmpty()) {
14889                                                for (int i = 0; i < tableModel.getColumns().size(); i++) {
14890                                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
14891                                                        relation.addSource(new TableColumnRelationshipElement(stage.getColumns().get(0)));
14892                                                        relation.setTarget(new TableColumnRelationshipElement(tableModel.getColumns().get(i)));
14893                                                        relation.setProcess(process);
14894                                                }
14895                                        } else {
14896                                                TObjectName starColumn = DlineageUtil.syntheticStarName();
14897                                                TableColumn tableColumn = modelFactory.createTableColumn(tableModel, starColumn, true);
14898                                                tableColumn.setExpandStar(false);
14899                                                tableColumn.setShowStar(true);
14900                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
14901                                                relation.addSource(new TableColumnRelationshipElement(stage.getColumns().get(0)));
14902                                                relation.setTarget(new TableColumnRelationshipElement(tableColumn));
14903                                                relation.setProcess(process);
14904                                        }
14905                                }
14906                        } else if (hasBigQueryExternalOption(stmt)) {
14907                                for (int i = 0; i < stmt.getTableOptions().size(); i++) {
14908                                        TCreateTableOption createTableOption = stmt.getTableOptions().get(i);
14909                                        if (createTableOption.getCreateTableOptionType() != ECreateTableOption.etoBigQueryExternal)
14910                                                continue;
14911                                        List<String> uris = createTableOption.getUris();
14912                                        if (uris == null || uris.isEmpty())
14913                                                continue;
14914                                        Process process = modelFactory.createProcess(stmt);
14915                                        process.setType("Create External Table");
14916                                        tableModel.addProcess(process);
14917                                        if (tableModel.getColumns().isEmpty()) {
14918                                                TObjectName starColumn = DlineageUtil.syntheticStarName();
14919                                                TableColumn tableColumn = modelFactory.createTableColumn(tableModel, starColumn, true);
14920                                                tableColumn.setExpandStar(false);
14921                                                tableColumn.setShowStar(true);
14922                                        }
14923                                        for (String uri : uris) {
14924                                                Table uriFile = modelFactory.createTableByName(uri, true);
14925                                                uriFile.setPath(true);
14926                                                uriFile.setFileFormat(createTableOption.getFormat());
14927                                                for (int j = 0; j < tableModel.getColumns().size(); j++) {
14928                                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
14929                                                        if (stmt.getColumnList() != null) {
14930                                                                // The file's field for the table's column; the table's star stays a star.
14931                                                                TableColumn tableColumn = tableModel.getColumns().get(j);
14932                                                                TObjectName fileUri = tableColumn.isStarColumn() ? DlineageUtil.syntheticStarName()
14933                                                                                : new TObjectName();
14934                                                                if (!tableColumn.isStarColumn()) {
14935                                                                        fileUri.setString(tableColumn.getColumnObject().toString());
14936                                                                }
14937                                                                TableColumn fileUriColumn = modelFactory.createFileUri(uriFile, fileUri);
14938                                                                relation.addSource(new TableColumnRelationshipElement(fileUriColumn));
14939                                                        } else {
14940                                                                TObjectName fileUri = DlineageUtil.syntheticStarName();
14941                                                                TableColumn fileUriColumn = modelFactory.createFileUri(uriFile, fileUri);
14942                                                                relation.addSource(new TableColumnRelationshipElement(fileUriColumn));
14943                                                        }
14944                                                        relation.setTarget(new TableColumnRelationshipElement(tableModel.getColumns().get(j)));
14945                                                        relation.setProcess(process);
14946                                                }
14947                                                if (tableModel.getColumns().isEmpty()) {
14948                                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
14949                                                        TObjectName fileUri = DlineageUtil.syntheticStarName();
14950                                                        TableColumn fileUriColumn = modelFactory.createFileUri(uriFile, fileUri);
14951                                                        relation.addSource(new TableColumnRelationshipElement(fileUriColumn));
14952                                                        relation.setTarget(new RelationRowsRelationshipElement<TableRelationRows>(
14953                                                                        tableModel.getRelationRows()));
14954                                                        relation.setProcess(process);
14955                                                }
14956                                        }
14957                                }
14958                        } else if (stmt.getSubQuery() == null && stmt.getTableLocation() != null) {
14959                                Process process = modelFactory.createProcess(stmt);
14960                                process.setType("Create External Table");
14961                                tableModel.addProcess(process);
14962                                if (tableModel.getColumns().isEmpty()) {
14963                                        // No column list, as in CREATE TABLE t USING DELTA LOCATION '...': the files give the table's star.
14964                                        TObjectName starColumn = DlineageUtil.syntheticStarName();
14965                                        TableColumn tableColumn = modelFactory.createTableColumn(tableModel, starColumn, true);
14966                                        tableColumn.setExpandStar(false);
14967                                        tableColumn.setShowStar(true);
14968                                }
14969                                Table uriFile = modelFactory.createTableByName(stmt.getTableLocation(), true);
14970                                uriFile.setPath(true);
14971                                for (int j = 0; j < tableModel.getColumns().size(); j++) {
14972                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
14973                                        if (stmt.getColumnList() != null) {
14974                                                // The file's field for the table's column; the table's star stays a star.
14975                                                TableColumn tableColumn = tableModel.getColumns().get(j);
14976                                                TObjectName fileUri = tableColumn.isStarColumn() ? DlineageUtil.syntheticStarName()
14977                                                                : new TObjectName();
14978                                                if (!tableColumn.isStarColumn()) {
14979                                                        fileUri.setString(tableColumn.getColumnObject().toString());
14980                                                }
14981                                                TableColumn fileUriColumn = modelFactory.createFileUri(uriFile, fileUri);
14982                                                relation.addSource(new TableColumnRelationshipElement(fileUriColumn));
14983                                                linkDeclaredFieldsToFile(tableColumn, fileUriColumn, process);
14984                                        } else {
14985                                                TObjectName fileUri = DlineageUtil.syntheticStarName();
14986                                                TableColumn fileUriColumn = modelFactory.createFileUri(uriFile, fileUri);
14987                                                relation.addSource(new TableColumnRelationshipElement(fileUriColumn));
14988                                        }
14989                                        relation.setTarget(new TableColumnRelationshipElement(tableModel.getColumns().get(j)));
14990                                        relation.setProcess(process);
14991                                }
14992                        }
14993
14994                        // Inline (column-level) FK relationships. Emitted here, after the
14995                        // column loop, rather than inline in it, so the referenced-column /
14996                        // relation / process model objects allocate their ids AFTER all
14997                        // column ids (matching the table-level pass below) and column ids
14998                        // stay stable. Mirrors the table-level FK emission: source =
14999                        // referenced column, target = this FK column, effect = foreign_key,
15000                        // plus the optional ER relationship. EConstraintType.reference is
15001                        // the inline REFERENCES form; foreign_key the inline FOREIGN KEY.
15002                        if (stmt.getColumnList() != null) {
15003                                for (int i = 0; i < stmt.getColumnList().size(); i++) {
15004                                        TColumnDefinition fkColumnDef = stmt.getColumnList().getColumn(i);
15005                                        TConstraintList fkConstraints = fkColumnDef.getConstraints();
15006                                        if (fkConstraints == null) {
15007                                                continue;
15008                                        }
15009                                        for (int c = 0; c < fkConstraints.size(); c++) {
15010                                                TConstraint inlineConstraint = fkConstraints.getConstraint(c);
15011                                                EConstraintType inlineType = inlineConstraint.getConstraint_type();
15012                                                if (inlineType != EConstraintType.foreign_key
15013                                                                && inlineType != EConstraintType.reference) {
15014                                                        continue;
15015                                                }
15016                                                TObjectName referencedTableName = inlineConstraint.getReferencedObject();
15017                                                TObjectNameList referencedTableColumns = inlineConstraint.getReferencedColumnList();
15018                                                if (referencedTableName == null || referencedTableColumns == null) {
15019                                                        continue;
15020                                                }
15021                                                TableColumn fkColumn = modelFactory.createTableColumn(tableModel,
15022                                                                fkColumnDef.getColumnName(), true);
15023                                                if (fkColumn == null) {
15024                                                        continue;
15025                                                }
15026                                                Table referencedTable = modelManager.getTableByName(
15027                                                                DlineageUtil.getTableIdentityKey(referencedTableName.toString()));
15028                                                if (referencedTable == null) {
15029                                                        referencedTable = modelFactory.createTableByName(referencedTableName);
15030                                                }
15031                                                for (int j = 0; j < referencedTableColumns.size(); j++) {
15032                                                        TableColumn referencedColumn = modelFactory.createTableColumn(referencedTable,
15033                                                                        referencedTableColumns.getObjectName(j), false);
15034                                                        if (referencedColumn != null) {
15035                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
15036                                                                relation.addSource(new TableColumnRelationshipElement(referencedColumn));
15037                                                                relation.setTarget(new TableColumnRelationshipElement(fkColumn));
15038                                                                relation.setEffectType(EffectType.foreign_key);
15039                                                                Process process = modelFactory.createProcess(stmt);
15040                                                                relation.setProcess(process);
15041                                                                if (this.option.isShowERDiagram()) {
15042                                                                        ERRelationship erRelation = modelFactory.createERRelation();
15043                                                                        erRelation.addSource(new TableColumnRelationshipElement(referencedColumn));
15044                                                                        erRelation.setTarget(new TableColumnRelationshipElement(fkColumn));
15045                                                                }
15046                                                        }
15047                                                }
15048                                        }
15049                                }
15050                        }
15051
15052                        if (stmt.getTableConstraints() != null && stmt.getTableConstraints().size() > 0) {
15053                                for (int i = 0; i < stmt.getTableConstraints().size(); i++) {
15054                                        TConstraint createTableConstraint = stmt.getTableConstraints().getConstraint(i);
15055                                        TPTNodeList<TColumnWithSortOrder> keyNames = createTableConstraint.getColumnList();
15056                                        if (keyNames != null) {
15057                                                for (int k = 0; k < keyNames.size(); k++) {
15058                                                        TObjectName keyName = keyNames.getElement(k).getColumnName();
15059                                                        // Skip functional indexes (expression-based indexes) where columnName is null
15060                                                        if (keyName == null) {
15061                                                                continue;
15062                                                        }
15063                                                        TObjectName referencedTableName = createTableConstraint.getReferencedObject();
15064                                                        TObjectNameList referencedTableColumns = createTableConstraint.getReferencedColumnList();
15065
15066                                                        TableColumn tableConstraint = modelFactory.createTableColumn(tableModel, keyName, true);
15067                                                        if (createTableConstraint.getConstraint_type() == EConstraintType.primary_key) {
15068                                                                tableConstraint.setPrimaryKey(true);
15069                                                        } else if (createTableConstraint.getConstraint_type() == EConstraintType.table_index) {
15070                                                                tableConstraint.setIndexKey(true);
15071                                                        } else if (createTableConstraint.getConstraint_type() == EConstraintType.unique) {
15072                                                                tableConstraint.setUnqiueKey(true);
15073                                                        } else if (createTableConstraint.getConstraint_type() == EConstraintType.foreign_key) {
15074                                                                tableConstraint.setForeignKey(true);
15075                                                                Table referencedTable = modelManager
15076                                                                                .getTableByName(DlineageUtil.getTableIdentityKey(referencedTableName.toString()));
15077                                                                if (referencedTable == null) {
15078                                                                        referencedTable = modelFactory.createTableByName(referencedTableName);
15079                                                                }
15080                                                                if (k == keyNames.size() - 1) {
15081                                                                        captureCompositeForeignKey(tableModel, keyNames, referencedTable,
15082                                                                                        referencedTableColumns);
15083                                                                }
15084
15085                                                                if (referencedTableColumns != null) {
15086                                                                        for (int j = 0; j < referencedTableColumns.size(); j++) {
15087                                                                                TableColumn tableColumn = modelFactory.createTableColumn(referencedTable,
15088                                                                                                referencedTableColumns.getObjectName(j), false);
15089                                                                                if (tableColumn != null) {
15090                                                                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
15091                                                                                        relation.addSource(new TableColumnRelationshipElement(tableColumn));
15092                                                                                        relation.setTarget(new TableColumnRelationshipElement(tableConstraint));
15093                                                                                        relation.setEffectType(EffectType.foreign_key);
15094                                                                                        Process process = modelFactory.createProcess(stmt);
15095                                                                                        relation.setProcess(process);
15096                                                                                        if (this.option.isShowERDiagram()) {
15097                                                                                                ERRelationship erRelation = modelFactory.createERRelation();
15098                                                                                                erRelation.addSource(new TableColumnRelationshipElement(tableColumn));
15099                                                                                                erRelation
15100                                                                                                                .setTarget(new TableColumnRelationshipElement(tableConstraint));
15101                                                                                        }
15102                                                                                }
15103                                                                        }
15104                                                                }
15105                                                        }
15106                                                }
15107                                        }
15108                                }
15109                        }
15110
15111                        if (stmt.getHiveTablePartition() != null && stmt.getHiveTablePartition().getColumnDefList() != null) {
15112                                for (int i = 0; i < stmt.getHiveTablePartition().getColumnDefList().size(); i++) {
15113                                        TColumnDefinition column = stmt.getHiveTablePartition().getColumnDefList().getColumn(i);
15114                                        modelFactory.createTableColumn(tableModel, column.getColumnName(), true);
15115                                        appendTableColumnToSQLEnv(tableModel, column.getColumnName());
15116                                }
15117                        }
15118
15119                        analyzeDynamicTableRefresh(stmt, tableModel);
15120
15121                } else {
15122                        ErrorInfo errorInfo = new ErrorInfo();
15123                        errorInfo.setErrorType(ErrorInfo.ANALYZE_ERROR);
15124                        errorInfo.setErrorMessage("Can't get target table. CreateTableSqlStatement is " + stmt.toString());
15125                        errorInfo.setStartPosition(new Pair3<Long, Long, String>(stmt.getStartToken().lineNo,
15126                                        stmt.getStartToken().columnNo, ModelBindingManager.getGlobalHash()));
15127                        errorInfo.setEndPosition(new Pair3<Long, Long, String>(stmt.getEndToken().lineNo,
15128                                        stmt.getEndToken().columnNo + stmt.getEndToken().getAstext().length(),
15129                                        ModelBindingManager.getGlobalHash()));
15130                        errorInfos.add(errorInfo);
15131                }
15132        }
15133
15134        private void analyzeDynamicTableRefresh(TCreateTableSqlStatement stmt,
15135                        Table dynamicTable) {
15136                TCustomSqlStatement refreshStatement = stmt.getRefreshUsingStatement();
15137                if (refreshStatement == null || refreshStatement.getTargetTable() == null) {
15138                        return;
15139                }
15140                String targetToken = refreshStatement.getTargetTable().getTableName()
15141                                .toString();
15142                if (!"SELF".equalsIgnoreCase(targetToken)) { // non-identifier-compare: Snowflake pseudo-target keyword
15143                        return;
15144                }
15145
15146                // Snowflake defines SELF as the dynamic table currently being created.
15147                // Bind the lexical SELF target to that existing model before running the
15148                // normal INSERT/MERGE analyzer, so all emitted target edges use the real
15149                // dynamic-table identity and its declared columns.
15150                modelManager.bindModel(refreshStatement.getTargetTable(), dynamicTable);
15151                analyzeCustomSqlStmt(refreshStatement);
15152        }
15153
15154        private void captureCompositeForeignKey(Table foreignKeyTable,
15155                        TPTNodeList<TColumnWithSortOrder> foreignKeyNames, Table referencedTable,
15156                        TObjectNameList referencedNames) {
15157                if (!shouldCollectAuthoritativeLineageEvidence() || foreignKeyTable == null
15158                                || referencedTable == null || foreignKeyNames == null
15159                                || referencedNames == null || foreignKeyNames.size() < 2
15160                                || foreignKeyNames.size() != referencedNames.size()) return;
15161                List<TableColumn> foreignKeyFields = new ArrayList<TableColumn>();
15162                List<TableColumn> referencedFields = new ArrayList<TableColumn>();
15163                for (int ordinal = 0; ordinal < foreignKeyNames.size(); ordinal++) {
15164                        TColumnWithSortOrder foreignKey = foreignKeyNames.getElement(ordinal);
15165                        if (foreignKey == null || foreignKey.getColumnName() == null
15166                                        || referencedNames.getObjectName(ordinal) == null) return;
15167                        TableColumn local = findExistingTableColumn(foreignKeyTable,
15168                                        foreignKey.getColumnName());
15169                        TableColumn referenced = findExistingTableColumn(referencedTable,
15170                                        referencedNames.getObjectName(ordinal));
15171                        if (local == null || referenced == null) return;
15172                        foreignKeyFields.add(local);
15173                        referencedFields.add(referenced);
15174                }
15175                authoritativeEvidenceCollector.addCompositeForeignKey(foreignKeyFields, referencedFields);
15176        }
15177
15178        /** Lookup-only: evidence capture must never create or mutate legacy model nodes. */
15179        private TableColumn findExistingTableColumn(Table table, TObjectName columnName) {
15180                Object exact = modelManager.getModel(new Pair<Table, TObjectName>(table, columnName));
15181                if (exact instanceof TableColumn && ((TableColumn) exact).getTable() == table) {
15182                        return (TableColumn) exact;
15183                }
15184                if (table.getColumns() == null) return null;
15185                for (TableColumn candidate : table.getColumns()) {
15186                        if (candidate != null && DlineageUtil.sameColumnName(candidate.getName(), columnName)) {
15187                                return candidate;
15188                        }
15189                }
15190                return null;
15191        }
15192
15193        /**
15194         * Whether a CREATE TABLE {@code STRUCT} or {@code ARRAY<STRUCT>} column is modelled as one column per field
15195         * path ({@code payload.name}). The Trino family (Athena, Presto, Trino) models the declared column itself:
15196         * TSQLResolver2 reads {@code payload.items[1].name} and {@code payload.address.city} there as the column
15197         * {@code payload} with a field path, so its read lands on the declared column, while the flattened names,
15198         * published by their last segment ({@code items}, {@code name}), were columns the table never had and every
15199         * read of {@code payload} was dropped from the determined table (Mantis 4811, 4822).
15200         */
15201        private boolean modelsNestedFieldsAsColumns() {
15202                return !nestedProfile().buildsDeclaredTree();
15203        }
15204
15205        /**
15206         * STRUCT tree, plan rule R3: whether TSQLResolver2 read {@code column} as a field of a column that several
15207         * relations in scope provide, such as {@code customer.name} in {@code FROM orders, items} when both declare
15208         * {@code customer}. Such a read has no proven owner.
15209         */
15210        private boolean isUndecidedFieldRead(TObjectName column) {
15211                if (!nestedProfile().buildsDeclaredTree() || column.getResolution() == null
15212                                || !column.getResolution().isAmbiguous() || column.getResolution().getAmbiguousSource() == null) {
15213                        return false;
15214                }
15215                List<gudusoft.gsqlparser.resolver2.model.ColumnSource> candidates =
15216                                column.getResolution().getAmbiguousSource().getCandidates();
15217                return candidates != null && !candidates.isEmpty() && candidates.get(0).isStructFieldAccess();
15218        }
15219
15220        /**
15221         * STRUCT tree, plan section 8: {@code customer.*} reads every field of {@code customer}. When its declared STRUCT
15222         * type lists the fields, the item is one result column per field, each from that field's node; otherwise it is
15223         * one {@code *} column from the node of {@code customer}, which stands for all of them. When several relations
15224         * could provide {@code customer} the column has no source (plan rule R3).
15225         *
15226         * @return null when the item is not a wildcard below a column; otherwise whether its result columns are known
15227         */
15228        /**
15229         * STRUCT tree: whether a select item {@code customer.*} stands for the unknown fields of a column, published as
15230         * one non-wildcard result column by {@link #analyzeStructStar} (Mantis 4827).
15231         */
15232        private boolean structStarOfUnknownFields(TObjectName item, ResultColumn resultColumn) {
15233                return nestedProfile().buildsDeclaredTree() && item.getStructStarPath() != null
15234                                && !resultColumn.isStarColumn() && !resultColumn.isStruct();
15235        }
15236
15237        private Boolean analyzeStructStar(ResultSet resultSetModel, TResultColumn column) {
15238                if (!nestedProfile().buildsDeclaredTree() || column.getExpr() == null
15239                                || column.getExpr().getExpressionType() != EExpressionType.simple_object_name_t
15240                                || column.getExpr().getObjectOperand() == null) {
15241                        return null;
15242                }
15243                TObjectName star = column.getExpr().getObjectOperand();
15244                if (star.getStructStarPath() == null) {
15245                        if (star.getStarQualifierChain().isEmpty() || !isUndecidedFieldRead(star)) {
15246                                return null;
15247                        }
15248                        modelFactory.createStructStarResultColumn(resultSetModel, column);
15249                        return Boolean.FALSE;
15250                }
15251                TableColumn node = null;
15252                // the relation TSQLResolver2 found the column in; the parse may have bound the wildcard to a table named like a
15253                // field (payload.events.* FROM source, events)
15254                TTable owner = star.getColumnSource() != null && star.getColumnSource().getFinalTable() != null
15255                                ? star.getColumnSource().getFinalTable() : star.getSourceTable();
15256                if (owner != null && owner.getCTE() == null && !owner.isCTEName() && owner.getSubquery() == null) {
15257                        Object model = modelManager.getModel(owner);
15258                        if (model == null && owner.getTableType() == ETableSource.objectname) {
15259                                model = modelFactory.createTable(owner);
15260                        }
15261                        if (model instanceof Table) {
15262                                node = modelFactory.createTableColumn((Table) model, star, false);
15263                        }
15264                }
15265                if (node == null) {
15266                        return null;
15267                }
15268                List<TableColumn> fields = node.getDeclaredFields();
15269                if (!fields.isEmpty()) {
15270                        for (TableColumn field : fields) {
15271                                List<gudusoft.gsqlparser.common.structured.StructuredPathSegment> path =
15272                                                field.getFieldPath().getSegments();
15273                                ResultColumn resultColumn = modelFactory.createStructFieldResultColumn(resultSetModel, column,
15274                                                path.get(path.size() - 1).getName());
15275                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
15276                                relation.setEffectType(EffectType.select);
15277                                relation.setTarget(new ResultColumnRelationshipElement(resultColumn));
15278                                relation.addSource(new TableColumnRelationshipElement(field));
15279                        }
15280                        return Boolean.TRUE;
15281                }
15282                ResultColumn resultColumn = modelFactory.createStructStarResultColumn(resultSetModel, column);
15283                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
15284                relation.setEffectType(EffectType.select);
15285                relation.setTarget(new ResultColumnRelationshipElement(resultColumn));
15286                relation.addSource(new TableColumnRelationshipElement(node));
15287                return Boolean.FALSE;
15288        }
15289
15290        /**
15291         * Plan rule R8: a field of a STRUCT a select item builds. The item publishes one result column per field (a leaf
15292         * of a nested STRUCT included); this record says which item it belongs to, under which alias (null for an item
15293         * without one), at which path below it. CTAS and CREATE VIEW make each field a field node of the declared column {@code alias}; positional mappers
15294         * pair the item, all of its fields, with one target column (Mantis 4834, 4847, 4891, 4892).
15295         */
15296        static final class StructFieldPath {
15297                final TObjectName alias;
15298                final List<String> path;
15299                final TResultColumn item;
15300
15301                StructFieldPath(TObjectName alias, List<String> path, TResultColumn item) {
15302                        this.alias = alias;
15303                        this.path = Collections.unmodifiableList(new ArrayList<String>(path));
15304                        this.item = item;
15305                }
15306
15307                List<gudusoft.gsqlparser.common.structured.StructuredPathSegment> segments() {
15308                        List<gudusoft.gsqlparser.common.structured.StructuredPathSegment> segments =
15309                                        new ArrayList<gudusoft.gsqlparser.common.structured.StructuredPathSegment>(path.size());
15310                        for (String field : path) {
15311                                segments.add(gudusoft.gsqlparser.common.structured.StructuredPathSegment.field(field));
15312                        }
15313                        return segments;
15314                }
15315        }
15316
15317        /**
15318         * Plan rule R8: how many result columns, from {@code columns.get(j)}, the select item of that column publishes:
15319         * every field of a STRUCT it builds ({@link #structFieldPaths}), else one.
15320         */
15321        private int selectItemWidth(List<ResultColumn> columns, int j) {
15322                StructFieldPath first = j < columns.size() ? structFieldPaths.get(columns.get(j)) : null;
15323                if (first == null) {
15324                        return 1;
15325                }
15326                int width = 1;
15327                while (j + width < columns.size()) {
15328                        StructFieldPath next = structFieldPaths.get(columns.get(j + width));
15329                        if (next == null || next.item != first.item) {
15330                                break;
15331                        }
15332                        width++;
15333                }
15334                return width;
15335        }
15336
15337        /**
15338         * Plan rule R8, Mantis 4891: {@code target}, the column a list pairs with a select item that builds a STRUCT, takes
15339         * the item's fields. When the target declares as many leaf fields as the item has, leaf k takes field k, by position,
15340         * as BigQuery, Hive and Trino assign a STRUCT; otherwise each field feeds the target's node at its own path where
15341         * the target can have that field, and the whole target column where it cannot. A provable value is never dropped,
15342         * and no leaf is invented under a declared STRUCT.
15343         */
15344        private void feedFromStructItem(TableColumn target, List<ResultColumn> fields, TObjectName position,
15345                        EffectType effectType, Process process, boolean viewColumn) {
15346                List<TableColumn> leaves = new ArrayList<TableColumn>();
15347                addDeclaredLeaves(target, leaves);
15348                boolean positional = leaves.size() == fields.size() && !(leaves.size() == 1 && leaves.get(0) == target);
15349                for (int k = 0; k < fields.size(); k++) {
15350                        ResultColumn field = fields.get(k);
15351                        TableColumn node = positional ? leaves.get(k) : null;
15352                        StructFieldPath path = structFieldPaths.get(field);
15353                        if (node == null && path != null) {
15354                                node = modelFactory.createFieldPath(target, path.segments(), position);
15355                        }
15356                        if (node == null) {
15357                                node = target;
15358                        }
15359                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
15360                        relation.setEffectType(effectType);
15361                        relation.setTarget(viewColumn ? new ViewColumnRelationshipElement(node) : new TableColumnRelationshipElement(node));
15362                        relation.addSource(new ResultColumnRelationshipElement(field));
15363                        relation.setProcess(process);
15364                }
15365        }
15366
15367        /**
15368         * Plan section 4.4, Mantis 4893: a select item that only names a column, {@code payload} of a CTE or derived table
15369         * whose query built that column as a STRUCT ({@code named_struct('a', x, 'b', y) AS payload}), passes the fields
15370         * on: one result column per field, each fed by the field it passes on, registered as fields of this item, so a CTAS
15371         * or a view over it makes them field nodes. Only for fields the query built (a declared STRUCT column read whole
15372         * stays one column, rule 1 of the STRUCT page); false, and nothing done, otherwise.
15373         */
15374        private boolean propagateStructItem(ResultSet resultSet, TResultColumn column) {
15375                List<ResultColumn> fields = new ArrayList<ResultColumn>();
15376                List<StructFieldPath> paths = new ArrayList<StructFieldPath>();
15377                if (!passedStructFields(column, fields, paths)) {
15378                        return false;
15379                }
15380                TObjectName name = column.getFieldAttr();
15381                String base = name.getColumnNameOnly();
15382                TObjectName alias = column.getAliasClause() != null && column.getAliasClause().getAliasName() != null
15383                                ? column.getAliasClause().getAliasName() : name;
15384                String aliasName = column.getAliasClause() != null && column.getAliasClause().getAliasName() != null
15385                                ? alias.toString() : base;
15386                for (int k = 0; k < fields.size(); k++) {
15387                        StringBuilder fieldName = new StringBuilder(aliasName);
15388                        for (String step : paths.get(k).path) {
15389                                fieldName.append('.').append(step);
15390                        }
15391                        TObjectName columnName = new TObjectName();
15392                        columnName.setString(fieldName.toString());
15393                        ResultColumn field = modelFactory.createResultColumn(resultSet, columnName);
15394                        field.setStruct(true);
15395                        putStructField(field, new StructFieldPath(alias, paths.get(k).path, column));
15396                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
15397                        relation.setEffectType(EffectType.select);
15398                        relation.setTarget(new ResultColumnRelationshipElement(field));
15399                        relation.addSource(new ResultColumnRelationshipElement(fields.get(k)));
15400                }
15401                return true;
15402        }
15403
15404        /**
15405         * Rule R8: the STRUCT fields a select item that only names a column passes on, into {@code fields} and
15406         * {@code paths} in order: the field columns of a CTE or derived table whose query built that column as a STRUCT,
15407         * or the field nodes below a column of that name ({@link #addStructFieldNodes}). False, nothing added, when the
15408         * item names no such column (a declared STRUCT column read whole stays one column, rule 1 of the STRUCT page).
15409         */
15410        private boolean passedStructFields(TResultColumn column, List<ResultColumn> fields, List<StructFieldPath> paths) {
15411                if (!nestedProfile().buildsDeclaredTree() || column.getExpr() == null
15412                                || column.getExpr().getExpressionType() != EExpressionType.simple_object_name_t
15413                                || column.getFieldAttr() == null || column.getFieldAttr().getSourceTable() == null
15414                                || column.getFieldAttr().getFieldPathRead() != null || DlineageUtil.isStarName(column.getFieldAttr())) {
15415                        return false;
15416                }
15417                TObjectName name = column.getFieldAttr();
15418                Object source = modelManager.getModel(name.getSourceTable());
15419                if (!(source instanceof ResultSet)) {
15420                        return false;
15421                }
15422                String base = name.getColumnNameOnly();
15423                List<ResultColumn> found = new ArrayList<ResultColumn>();
15424                List<StructFieldPath> foundPaths = new ArrayList<StructFieldPath>();
15425                for (ResultColumn candidate : ((ResultSet) source).getColumns()) {
15426                        if (!candidate.isStruct()) {
15427                                if (DlineageUtil.sameName(ESQLDataObjectType.dotColumn, candidate.getName(), base)) {
15428                                        // a column of that name itself: the item reads it, and the fields built below it
15429                                        List<ResultColumn> nodes = builtFieldNodes.get(candidate);
15430                                        if (nodes == null) {
15431                                                return false;
15432                                        }
15433                                        for (ResultColumn node : nodes) {
15434                                                fields.add(node);
15435                                                paths.add(structFieldPaths.get(node));
15436                                        }
15437                                        return true;
15438                                }
15439                                continue;
15440                        }
15441                        StructFieldPath path = structFieldPathOf(candidate);
15442                        if (path != null && path.alias != null
15443                                        && DlineageUtil.sameName(ESQLDataObjectType.dotColumn, path.alias.toString(), base)) {
15444                                found.add(candidate);
15445                                foundPaths.add(path);
15446                        }
15447                }
15448                fields.addAll(found);
15449                paths.addAll(foundPaths);
15450                return !found.isEmpty();
15451        }
15452
15453        /**
15454         * Rule R8 for the query of a derived table, whose select items fill a relation made before them: the item keeps
15455         * its one column, as before, and a STRUCT it builds, or passes on ({@link #passedStructFields}), adds a field node
15456         * below that column per field, fed by the field's value. A projection or a field read of the column then reads the
15457         * fields (Mantis 4893); nothing the column published is taken away.
15458         */
15459        private void addStructFieldNodes(ResultSet resultSet, TResultColumn column) {
15460                if (!nestedProfile().buildsDeclaredTree()) {
15461                        return;
15462                }
15463                Object model = modelManager.getModel(column);
15464                if (!(model instanceof ResultColumn) || ((ResultColumn) model).getResultSet() != resultSet
15465                                || ((ResultColumn) model).isStarColumn() || builtFieldNodes.containsKey(model)) {
15466                        return;
15467                }
15468                ResultColumn whole = (ResultColumn) model;
15469                TObjectName alias = column.getAliasClause() != null && column.getAliasClause().getAliasName() != null
15470                                ? column.getAliasClause().getAliasName() : column.getFieldAttr();
15471                List<Object[]> built = structItemFields(column);
15472                if (built != null) {
15473                        for (Object[] field : built) {
15474                                @SuppressWarnings("unchecked")
15475                                List<String> path = (List<String>) field[0];
15476                                ResultColumn node = structFieldNode(whole, path, alias, column);
15477                                columnsInExpr visitor = new columnsInExpr();
15478                                ((TExpression) field[1]).inOrderTraverse(visitor);
15479                                List<TParseTreeNode> functions = visitor.getFunctions();
15480                                if (functions != null && !functions.isEmpty()) {
15481                                        analyzeFunctionDataFlowRelation(node, functions, EffectType.select);
15482                                }
15483                                List<TSelectSqlStatement> subquerys = visitor.getSubquerys();
15484                                if (subquerys != null && !subquerys.isEmpty()) {
15485                                        analyzeSubqueryDataFlowRelation(node, subquerys, EffectType.select);
15486                                }
15487                                analyzeDataFlowRelation(node, visitor.getObjectNames(), EffectType.select, functions);
15488                                analyzeConstantDataFlowRelation(node, visitor.getConstants(), EffectType.select, functions);
15489                        }
15490                        return;
15491                }
15492                List<ResultColumn> fields = new ArrayList<ResultColumn>();
15493                List<StructFieldPath> paths = new ArrayList<StructFieldPath>();
15494                if (alias != null && passedStructFields(column, fields, paths)) {
15495                        for (int k = 0; k < fields.size(); k++) {
15496                                ResultColumn node = structFieldNode(whole, paths.get(k).path, alias, column);
15497                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
15498                                relation.setEffectType(EffectType.select);
15499                                relation.setTarget(new ResultColumnRelationshipElement(node));
15500                                relation.addSource(new ResultColumnRelationshipElement(fields.get(k)));
15501                        }
15502                }
15503        }
15504
15505        /** The field node at {@code path} below {@code whole}, a field of the STRUCT the select item {@code item} builds. */
15506        private ResultColumn structFieldNode(ResultColumn whole, List<String> path, TObjectName alias, TResultColumn item) {
15507                StringBuilder name = new StringBuilder(whole.getName());
15508                for (String step : path) {
15509                        name.append('.').append(step);
15510                }
15511                TSourceToken token = new TSourceToken(name.toString());
15512                if (item.getStartToken() != null) {
15513                        token.lineNo = item.getStartToken().lineNo;
15514                        token.columnNo = item.getStartToken().columnNo;
15515                }
15516                // a node of its own: never name-matched to a column, as the field nodes of a field read (Mantis 4836)
15517                ResultColumn node = modelFactory.createDeterminedResultColumn(whole.getResultSet(),
15518                                TObjectName.createObjectName(option.getVendor(), EDbObjectType.column, token));
15519                node.setStruct(true);
15520                StructFieldPath fieldPath = new StructFieldPath(alias, path, item);
15521                whole.getResultSet().addFieldPathColumn(node, whole, fieldPath.segments());
15522                putStructField(node, fieldPath);
15523                List<ResultColumn> nodes = builtFieldNodes.get(whole);
15524                if (nodes == null) {
15525                        nodes = new ArrayList<ResultColumn>();
15526                        builtFieldNodes.put(whole, nodes);
15527                }
15528                nodes.add(node);
15529                return node;
15530        }
15531
15532        /**
15533         * The field node of {@code base} a field read reaches: the node at the read's path, or the leaf whose value the
15534         * read goes into; null when no node is, or when the read stops above the leaves.
15535         */
15536        private ResultColumn builtFieldNode(ResultColumn base,
15537                        gudusoft.gsqlparser.common.structured.StructuredColumnPath read) {
15538                List<ResultColumn> nodes = builtFieldNodes.get(base);
15539                if (nodes == null || read == null) {
15540                        return null;
15541                }
15542                for (ResultColumn node : nodes) {
15543                        List<String> path = structFieldPaths.get(node).path;
15544                        if (path.size() > read.getSegments().size()) {
15545                                continue;
15546                        }
15547                        boolean match = true;
15548                        for (int i = 0; i < path.size() && match; i++) {
15549                                gudusoft.gsqlparser.common.structured.StructuredPathSegment segment = read.getSegments().get(i);
15550                                match = segment.getKind() == gudusoft.gsqlparser.common.structured.StructuredPathSegmentKind.FIELD
15551                                                && DlineageUtil.sameName(ESQLDataObjectType.dotColumn, segment.getName(), path.get(i));
15552                        }
15553                        if (match) {
15554                                return node;
15555                        }
15556                }
15557                return null;
15558        }
15559
15560        /**
15561         * Rule R8: the fields of the STRUCT the select item builds, each {path below the item, value}, in source order, a
15562         * nested constructor's leaves below its field: a constructor of the registry ({@link #constructorFields}), aliased
15563         * or not; an unnamed field has no node. Null when the item builds none.
15564         */
15565        private List<Object[]> structItemFields(TResultColumn column) {
15566                if (column.getExpr() == null || column.getExpr().getExpressionType() != EExpressionType.function_t
15567                                || constructorFields(column.getExpr().getFunctionCall()) == null) {
15568                        return null;
15569                }
15570                List<Object[]> fields = new ArrayList<Object[]>();
15571                constructorLeaves(column.getExpr().getFunctionCall(), new ArrayList<String>(), fields);
15572                return fields.isEmpty() ? null : fields;
15573        }
15574
15575        /** The column at {@code index} of a branch of a set operation, or null for a wildcard or a missing one. */
15576        private ResultColumn setOperationBranchColumn(TSelectSqlStatement branch, int index) {
15577                ResultSet model = setOperationBranch(branch);
15578                if (model == null || model.getColumns().size() <= index) {
15579                        return null;
15580                }
15581                ResultColumn column = model.getColumns().get(index);
15582                return column.isStarColumn() ? null : column;
15583        }
15584
15585        /** The result set of a branch of a set operation, or null. */
15586        private ResultSet setOperationBranch(TSelectSqlStatement branch) {
15587                Object model = branch.isCombinedQuery() ? modelManager.getModel(branch)
15588                                : modelManager.getModel(branch.getResultColumnList());
15589                return model instanceof ResultSet ? (ResultSet) model : null;
15590        }
15591
15592        /** The result set of the branch the columns of the set operation {@code stmt} are named after, or null. */
15593        private ResultSet setOperationNamingBranch(TSelectSqlStatement stmt) {
15594                return setOperationBranch(getResultColumnList(stmt.getLeftStmt()) != null ? stmt.getLeftStmt() : stmt.getRightStmt());
15595        }
15596
15597        /** {@link #setOperationNamingBranch} of the set operation {@code resultSet} is the result of; null for another result set. */
15598        private ResultSet setOperationNamingBranch(ResultSet resultSet) {
15599                return resultSet instanceof SelectSetResultSet && resultSet.getGspObject() instanceof TSelectSqlStatement
15600                                ? setOperationNamingBranch((TSelectSqlStatement) resultSet.getGspObject()) : null;
15601        }
15602
15603        /**
15604         * Mantis 4914: for each branch of the set operation {@code stmt} and each of its {@code columns}, the columns of the
15605         * branch that can stand at the column's position; null when no branch and no column is a star of unknown width,
15606         * and the columns pair one by one. Every branch is as wide as the others, so a star spans what the items beside it
15607         * leave ({@link #setItemSpans}). Pairing by index put a column of the branch without a star at the position the
15608         * other branch's star covers, and fed a star with the columns after it.
15609         */
15610        private List<List<List<ResultColumn>>> setOperationSpanSources(TSelectSqlStatement stmt, List<ResultColumn> columns) {
15611                List<List<ResultColumn>> columnItems = new ArrayList<List<ResultColumn>>(columns.size());
15612                for (ResultColumn column : columns) {
15613                        columnItems.add(Collections.singletonList(column));
15614                }
15615                TSelectSqlStatement[] branchStmts = {stmt.getLeftStmt(), stmt.getRightStmt()};
15616                List<ResultSet> branches = new ArrayList<ResultSet>(2);
15617                List<List<List<ResultColumn>>> branchItems = new ArrayList<List<List<ResultColumn>>>(2);
15618                boolean star = fixedSetItems(columnItems) < columnItems.size();
15619                // the width of a branch without a star of unknown width, which is every branch's
15620                int width = -1;
15621                int fixed = fixedSetItems(columnItems);
15622                for (TSelectSqlStatement branchStmt : branchStmts) {
15623                        ResultSet branch = setOperationBranch(branchStmt);
15624                        List<List<ResultColumn>> items = branch == null ? null : selectItemGroups(branch.getColumns());
15625                        branches.add(branch);
15626                        branchItems.add(items);
15627                        if (items != null) {
15628                                int branchFixed = fixedSetItems(items);
15629                                star |= branchFixed < items.size();
15630                                if (branchFixed == items.size() && width < 0) {
15631                                        width = items.size();
15632                                }
15633                                fixed = Math.max(fixed, branchFixed);
15634                        }
15635                }
15636                if (star && width < 0) {
15637                        // a select deeper in the set operation, or in another branch of it, can prove the width (Codex review)
15638                        width = setOperationTreeWidth(stmt);
15639                }
15640                if (!star || width >= 0 && fixed > width) {
15641                        return null;
15642                }
15643                int minWidth = width >= 0 ? width : fixed;
15644                int maxWidth = width >= 0 ? width : Integer.MAX_VALUE;
15645                List<int[]> columnSpans = setItemSpans(columnItems);
15646                // the columns are named after one branch, item by item
15647                ResultSet namingBranch = setOperationNamingBranch(stmt);
15648                List<List<ResultColumn>> namingItems = namingBranch == null ? null : selectItemGroups(namingBranch.getColumns());
15649                List<List<List<ResultColumn>>> sources = new ArrayList<List<List<ResultColumn>>>(2);
15650                for (int b = 0; b < 2; b++) {
15651                        List<List<ResultColumn>> items = branchItems.get(b);
15652                        boolean itemByItem = items != null && branches.get(b) == namingBranch
15653                                        && namingBranch.getColumns().size() == columns.size();
15654                        // a branch laid out like the naming one, its stars reading the same tables, pairs item by item even
15655                        // between two stars, which are as wide as each other (Codex review)
15656                        boolean sameLayout = !itemByItem && items != null && namingItems != null
15657                                        && namingItems.size() == columns.size() && sameItemLayout(namingItems, items);
15658                        List<int[]> spans = items == null ? null : setItemSpans(items);
15659                        List<List<ResultColumn>> branchSources = new ArrayList<List<ResultColumn>>(columns.size());
15660                        for (int i = 0; i < columns.size(); i++) {
15661                                List<ResultColumn> candidates = new ArrayList<ResultColumn>();
15662                                if (itemByItem) {
15663                                        candidates.add(branches.get(b).getColumns().get(i));
15664                                } else if (sameLayout) {
15665                                        candidates.addAll(items.get(i));
15666                                } else if (items != null) {
15667                                        for (int k = 0; k < items.size(); k++) {
15668                                                if (spansMeet(columnSpans.get(i), spans.get(k), minWidth, maxWidth)) {
15669                                                        candidates.addAll(items.get(k));
15670                                                }
15671                                        }
15672                                }
15673                                branchSources.add(candidates);
15674                        }
15675                        sources.add(branchSources);
15676                }
15677                return sources;
15678        }
15679
15680        /**
15681         * The width of every select of the set operation tree {@code stmt} is part of, from a select of it without a star of
15682         * unknown width; -1 when none proves it. A set operation's columns collapse a star of its first select, so a select
15683         * deeper in a branch, or in another branch, can be the only one to prove the width (Codex review of Mantis 4914).
15684         */
15685        private int setOperationTreeWidth(TSelectSqlStatement stmt) {
15686                TSelectSqlStatement root = stmt;
15687                for (TSelectSqlStatement parent = getParentSetSelectStmt(root); parent != null
15688                                && parent.isCombinedQuery(); parent = getParentSetSelectStmt(root)) {
15689                        root = parent;
15690                }
15691                Deque<TSelectSqlStatement> stack = new ArrayDeque<TSelectSqlStatement>();
15692                stack.push(root);
15693                while (!stack.isEmpty()) {
15694                        TSelectSqlStatement current = stack.pop();
15695                        if (current.isCombinedQuery()) {
15696                                if (current.getRightStmt() != null) {
15697                                        stack.push(current.getRightStmt());
15698                                }
15699                                if (current.getLeftStmt() != null) {
15700                                        stack.push(current.getLeftStmt());
15701                                }
15702                                continue;
15703                        }
15704                        TResultColumnList list = current.getResultColumnList();
15705                        if (list == null) {
15706                                continue;
15707                        }
15708                        boolean starItem = false;
15709                        for (int i = 0; i < list.size() && !starItem; i++) {
15710                                starItem = isStarItem(list.getResultColumn(i));
15711                        }
15712                        if (!starItem) {
15713                                return list.size();
15714                        }
15715                        // a star the analysis expanded (DDL) has a known width
15716                        Object model = modelManager.getModel(list);
15717                        if (model instanceof ResultSet) {
15718                                List<List<ResultColumn>> items = selectItemGroups(((ResultSet) model).getColumns());
15719                                if (fixedSetItems(items) == items.size()) {
15720                                        return items.size();
15721                                }
15722                        }
15723                }
15724                return -1;
15725        }
15726
15727        /**
15728         * Whether two selects of a set operation lay out their items alike: as many items, and a star of unknown width at the
15729         * same place in both, reading the same tables without EXCEPT or REPLACE. Such stars are as wide as each other.
15730         */
15731        private boolean sameItemLayout(List<List<ResultColumn>> a, List<List<ResultColumn>> b) {
15732                if (a.size() != b.size()) {
15733                        return false;
15734                }
15735                for (int k = 0; k < a.size(); k++) {
15736                        boolean star = isUnknownWidthStar(a.get(k).get(0));
15737                        if (star != isUnknownWidthStar(b.get(k).get(0))) {
15738                                return false;
15739                        }
15740                        if (star) {
15741                                List<Object> tables = starTableModels(a.get(k).get(0));
15742                                List<Object> others = starTableModels(b.get(k).get(0));
15743                                if (tables == null || others == null || tables.size() != others.size()) {
15744                                        return false;
15745                                }
15746                                for (int t = 0; t < tables.size(); t++) {
15747                                        if (tables.get(t) != others.get(t)) {
15748                                                return false;
15749                                        }
15750                                }
15751                        }
15752                }
15753                return true;
15754        }
15755
15756        /** The models of the tables a star item reads, in FROM order; null for another item, or a star with EXCEPT or REPLACE. */
15757        private List<Object> starTableModels(ResultColumn column) {
15758                if (!(column.getColumnObject() instanceof TResultColumn) || !isStarItem((TResultColumn) column.getColumnObject())) {
15759                        return null;
15760                }
15761                TResultColumn star = (TResultColumn) column.getColumnObject();
15762                if (star.getExceptColumnList() != null
15763                                || (star.getReplaceExprAsIdentifiers() != null && !star.getReplaceExprAsIdentifiers().isEmpty())) {
15764                        return null;
15765                }
15766                List<TTable> tables = star.getFieldAttr().getSourceTableList();
15767                if (tables == null || tables.isEmpty()) {
15768                        return null;
15769                }
15770                List<Object> models = new ArrayList<Object>();
15771                for (TTable table : tables) {
15772                        Object model = modelManager.getModel(table);
15773                        if (!(model instanceof Table)) {
15774                                return null;
15775                        }
15776                        models.add(model);
15777                }
15778                return models;
15779        }
15780
15781        /** How many of {@code items} are not stars of unknown width, each one column wide. */
15782        private static int fixedSetItems(List<List<ResultColumn>> items) {
15783                int fixed = 0;
15784                for (List<ResultColumn> item : items) {
15785                        if (!isUnknownWidthStar(item.get(0))) {
15786                                fixed++;
15787                        }
15788                }
15789                return fixed;
15790        }
15791
15792        private static boolean isUnknownWidthStar(ResultColumn column) {
15793                return column.getName() != null && column.isStarColumn();
15794        }
15795
15796        /**
15797         * Mantis 4914: the positions each select item of a set operation's list can stand at, as {start, end}, each end
15798         * c + w * W for the width W of the list: {c, 0} counts from the first column, {-1 - c, 1} back from the last. An
15799         * item before every star of unknown width stands where it is written, an item after every star counts back from
15800         * the end, and a star, or an item between two stars, spans what the items beside it leave.
15801         */
15802        private static List<int[]> setItemSpans(List<List<ResultColumn>> items) {
15803                int fixed = fixedSetItems(items);
15804                int lastStar = -1;
15805                for (int k = 0; k < items.size(); k++) {
15806                        if (isUnknownWidthStar(items.get(k).get(0))) {
15807                                lastStar = k;
15808                        }
15809                }
15810                List<int[]> spans = new ArrayList<int[]>(items.size());
15811                int before = 0;
15812                boolean starBefore = false;
15813                for (int k = 0; k < items.size(); k++) {
15814                        boolean star = isUnknownWidthStar(items.get(k).get(0));
15815                        int after = fixed - before - (star ? 0 : 1);
15816                        if (!star && !starBefore) {
15817                                spans.add(new int[] {before, 0, before, 0});
15818                        } else if (!star && k > lastStar) {
15819                                spans.add(new int[] {-1 - after, 1, -1 - after, 1});
15820                        } else {
15821                                spans.add(new int[] {before, 0, -1 - after, 1});
15822                        }
15823                        if (star) {
15824                                starBefore = true;
15825                        } else {
15826                                before++;
15827                        }
15828                }
15829                return spans;
15830        }
15831
15832        /** Whether two spans of {@link #setItemSpans} can share a position for a width from {@code minWidth} to {@code maxWidth}. */
15833        private static boolean spansMeet(int[] a, int[] b, int minWidth, int maxWidth) {
15834                int[] widths = {minWidth, maxWidth};
15835                // neither span is empty, and each starts no later than the other ends
15836                return notAfter(a, 0, a, 2, widths) && notAfter(b, 0, b, 2, widths) && notAfter(a, 0, b, 2, widths)
15837                                && notAfter(b, 0, a, 2, widths);
15838        }
15839
15840        /**
15841         * Narrows {@code widths} to the widths W for which end {@code i} of span {@code a} is at most end {@code j} of span
15842         * {@code b}; false when no width is left.
15843         */
15844        private static boolean notAfter(int[] a, int i, int[] b, int j, int[] widths) {
15845                // a[i] + a[i + 1] * W <= b[j] + b[j + 1] * W
15846                int w = a[i + 1] - b[j + 1];
15847                int c = b[j] - a[i];
15848                if (w == 0 && c < 0) {
15849                        return false;
15850                }
15851                if (w > 0) {
15852                        widths[1] = Math.min(widths[1], c);
15853                } else if (w < 0) {
15854                        widths[0] = Math.max(widths[0], -c);
15855                }
15856                return widths[0] <= widths[1];
15857        }
15858
15859        /**
15860         * Rule R8 through a set operation: a field node below {@code target}, the set operation's column, per field node
15861         * below {@code branch}, the column of a branch at its position, fed by it; branches with the same field share the
15862         * node.
15863         */
15864        private void setOperationFieldNodes(ResultColumn target, ResultColumn branch) {
15865                List<ResultColumn> nodes = branch == null || target.isStarColumn() ? null : builtFieldNodes.get(branch);
15866                if (nodes == null) {
15867                        return;
15868                }
15869                for (ResultColumn node : nodes) {
15870                        StructFieldPath path = structFieldPaths.get(node);
15871                        ResultColumn targetNode = builtFieldNodeAt(target, path.path);
15872                        if (targetNode == null) {
15873                                targetNode = structFieldNode(target, path.path, path.alias, path.item);
15874                        }
15875                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
15876                        relation.setEffectType(EffectType.select);
15877                        relation.setTarget(new ResultColumnRelationshipElement(targetNode));
15878                        relation.addSource(new ResultColumnRelationshipElement(node));
15879                }
15880        }
15881
15882        /** The field node {@link #structFieldNode} built below {@code column} at exactly {@code path}, or null. */
15883        private ResultColumn builtFieldNodeAt(ResultColumn column, List<String> path) {
15884                List<ResultColumn> nodes = builtFieldNodes.get(column);
15885                if (nodes == null) {
15886                        return null;
15887                }
15888                for (ResultColumn node : nodes) {
15889                        List<String> nodePath = structFieldPaths.get(node).path;
15890                        if (nodePath.size() != path.size()) {
15891                                continue;
15892                        }
15893                        boolean same = true;
15894                        for (int i = 0; i < path.size() && same; i++) {
15895                                same = DlineageUtil.sameName(ESQLDataObjectType.dotColumn, nodePath.get(i), path.get(i));
15896                        }
15897                        if (same) {
15898                                return node;
15899                        }
15900                }
15901                return null;
15902        }
15903
15904        /**
15905         * Rule R8 at a write: {@code target}, the column {@code source} feeds, gets a field node per field node built
15906         * below {@code source} ({@link #addStructFieldNodes}, {@link #setOperationFieldNodes}), fed by it. A field the
15907         * target's DDL does not declare gets none: its value still reaches the whole column.
15908         */
15909        private void feedBuiltFieldNodes(TableColumn target, ResultColumn source, EffectType effectType, Process process,
15910                        boolean viewColumn) {
15911                List<ResultColumn> nodes = target == null || target.isStarColumn() ? null : builtFieldNodes.get(source);
15912                if (nodes == null) {
15913                        return;
15914                }
15915                for (ResultColumn node : nodes) {
15916                        TableColumn targetNode = modelFactory.createFieldPath(target, structFieldPaths.get(node).segments(),
15917                                        target.getColumnObject());
15918                        if (targetNode == null) {
15919                                continue;
15920                        }
15921                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
15922                        relation.setEffectType(effectType);
15923                        relation.setTarget(viewColumn ? new ViewColumnRelationshipElement(targetNode)
15924                                        : new TableColumnRelationshipElement(targetNode));
15925                        relation.addSource(new ResultColumnRelationshipElement(node));
15926                        relation.setProcess(process);
15927                }
15928        }
15929
15930        /** The field nodes {@link #addStructFieldNodes} built below a result column, in order. */
15931        private final Map<ResultColumn, List<ResultColumn>> builtFieldNodes = new IdentityHashMap<ResultColumn, List<ResultColumn>>();
15932
15933        /**
15934         * The STRUCT field a result column stands for: its own record, or, for a column of a CTE or derived table that
15935         * copies one with a single select edge, the record of the column it copies (depth-bounded).
15936         */
15937        private StructFieldPath structFieldPathOf(ResultColumn column) {
15938                ResultColumn current = column;
15939                for (int depth = 0; current != null && depth < 16; depth++) {
15940                        StructFieldPath path = structFieldPaths.get(current);
15941                        if (path != null) {
15942                                return path;
15943                        }
15944                        ResultColumn upstream = null;
15945                        for (Relationship relation : modelManager.getRelations()) {
15946                                if (!(relation instanceof DataFlowRelationship) || relation.getTarget() == null
15947                                                || relation.getTarget().getElement() != current) {
15948                                        continue;
15949                                }
15950                                if (((DataFlowRelationship) relation).getEffectType() != EffectType.select
15951                                                || relation.getSources().size() != 1 || upstream != null) {
15952                                        return null;
15953                                }
15954                                Object source = ((RelationshipElement<?>) relation.getSources().iterator().next()).getElement();
15955                                if (!(source instanceof ResultColumn)) {
15956                                        return null;
15957                                }
15958                                upstream = (ResultColumn) source;
15959                        }
15960                        current = upstream;
15961                }
15962                return null;
15963        }
15964
15965        /**
15966         * Plan rule R8: whether the select item builds a STRUCT whose fields it publishes, one result column per field: a
15967         * Trino family {@code CAST(ROW(..) AS ROW(..))} with field names, a STRUCT constructor, an aliased
15968         * {@code named_struct} or Hive family {@code struct(..)}.
15969         */
15970        private boolean buildsStructItem(TResultColumn column) {
15971                if (column.getExpr() == null) {
15972                        return false;
15973                }
15974                if (rowCastFieldNames(column) != null) {
15975                        return true;
15976                }
15977                TFunctionCall call = column.getExpr().getFunctionCall();
15978                return call != null && (call.getFunctionType() == EFunctionType.struct_t
15979                                || ((isNamedStruct(call) || isPositionalStruct(call)) && column.getAliasClause() != null));
15980        }
15981
15982        /**
15983         * Plan rule R8: the result columns of a select item that {@link #buildsStructItem builds a STRUCT}, one per field
15984         * (per leaf of a nested one), each fed by its value and registered in {@link #structFieldPaths}. One method for
15985         * every query block, a CTE, a derived table or the top query, so a STRUCT a derived table builds keeps its fields
15986         * as one a CTE builds does (Mantis 4893).
15987         */
15988        private void analyzeStructItem(ResultSet resultSetModel, TResultColumn column) {
15989                if (rowCastFieldNames(column) != null) {
15990                        // Trino family CAST(ROW(a, b) AS ROW(city VARCHAR, phone VARCHAR)) AS contact: one
15991                        // column per field, contact.city <- a, as BigQuery STRUCT(a AS city) AS contact
15992                        // (Mantis 4834)
15993                        List<String> fields = rowCastFieldNames(column);
15994                        TExpressionList values = column.getExpr().getFunctionCall().getExpr1().getExprList();
15995                        for (int j = 0; j < fields.size(); j++) {
15996                                TObjectName columnName = new TObjectName();
15997                                columnName.setString(column.getAliasClause().getAliasName() + "." + fields.get(j));
15998                                ResultColumn resultColumn = modelFactory.createResultColumn(resultSetModel,
15999                                                columnName);
16000                                resultColumn.setStruct(true);
16001                                putStructField(resultColumn, new StructFieldPath(
16002                                                column.getAliasClause().getAliasName(),
16003                                                java.util.Collections.singletonList(fields.get(j)), column));
16004                                columnsInExpr visitor = new columnsInExpr();
16005                                values.getExpression(j).inOrderTraverse(visitor);
16006                                List<TParseTreeNode> functions = visitor.getFunctions();
16007                                if (functions != null && !functions.isEmpty()) {
16008                                        analyzeFunctionDataFlowRelation(resultColumn, functions, EffectType.select);
16009                                }
16010                                List<TSelectSqlStatement> subquerys = visitor.getSubquerys();
16011                                if (subquerys != null && !subquerys.isEmpty()) {
16012                                        analyzeSubqueryDataFlowRelation(resultColumn, subquerys, EffectType.select);
16013                                }
16014                                analyzeDataFlowRelation(resultColumn, visitor.getObjectNames(), EffectType.select,
16015                                                functions);
16016                                analyzeConstantDataFlowRelation(resultColumn, visitor.getConstants(),
16017                                                EffectType.select, functions);
16018                        }
16019                        return;
16020                }
16021                // named_struct('a', x, 'b', y) as STRUCT(x AS a, y AS b) (Mantis 4847); the positional
16022                // mappers pair the item, all of its fields, with one target (rule R8, Mantis 4891)
16023                TFunctionCall call = column.getExpr().getFunctionCall();
16024                boolean namedStruct = isNamedStruct(call);
16025                TObjectName structAlias = column.getAliasClause() != null
16026                                ? column.getAliasClause().getAliasName() : null;
16027                List<Object[]> leaves = new ArrayList<Object[]>();
16028                // the function node models the fields of a BigQuery STRUCT (a TTableFunction) only; Databricks
16029                // struct(x, y) is a STRUCT constructor of another node, whose one function column was named struct
16030                boolean modelledByFunction = !isPositionalStruct(call) && (call.getFunctionType() != EFunctionType.struct_t
16031                                || call instanceof gudusoft.gsqlparser.nodes.TTableFunction);
16032                if (structAlias != null && (hasNestedConstructor(call) || !modelledByFunction)
16033                                && constructorLeaves(call, new ArrayList<String>(), leaves)) {
16034                        // a STRUCT whose field is a STRUCT, or whose field names the vendor generates: a column per
16035                        // leaf, payload.a.b or st.col1, fed by the leaf's value (Mantis 4892)
16036                        for (Object[] leaf : leaves) {
16037                                @SuppressWarnings("unchecked")
16038                                List<String> path = (List<String>) leaf[0];
16039                                StringBuilder leafName = new StringBuilder(structAlias.toString());
16040                                for (String step : path) {
16041                                        leafName.append('.').append(step);
16042                                }
16043                                TObjectName columnName = new TObjectName();
16044                                columnName.setString(leafName.toString());
16045                                ResultColumn resultColumn = modelFactory.createResultColumn(resultSetModel,
16046                                                columnName);
16047                                resultColumn.setStruct(true);
16048                                putStructField(resultColumn, new StructFieldPath(structAlias, path, column));
16049                                columnsInExpr visitor = new columnsInExpr();
16050                                ((TExpression) leaf[1]).inOrderTraverse(visitor);
16051                                List<TParseTreeNode> functions = visitor.getFunctions();
16052                                if (functions != null && !functions.isEmpty()) {
16053                                        analyzeFunctionDataFlowRelation(resultColumn, functions, EffectType.select);
16054                                }
16055                                List<TSelectSqlStatement> subquerys = visitor.getSubquerys();
16056                                if (subquerys != null && !subquerys.isEmpty()) {
16057                                        analyzeSubqueryDataFlowRelation(resultColumn, subquerys, EffectType.select);
16058                                }
16059                                analyzeDataFlowRelation(resultColumn, visitor.getObjectNames(), EffectType.select,
16060                                                functions);
16061                                analyzeConstantDataFlowRelation(resultColumn, visitor.getConstants(),
16062                                                EffectType.select, functions);
16063                        }
16064                        return;
16065                }
16066                Function function = namedStruct ? createNamedStructFunction(column.getExpr().getFunctionCall())
16067                                : (Function) createFunction(column.getExpr().getFunctionCall());
16068                String functionName = getResultSetName(function);
16069                for (int j = 0; j < function.getColumns().size(); j++) {
16070                        TObjectName columnName = new TObjectName();
16071                        if (column.getAliasClause() != null) {
16072                                columnName.setString(column.getAliasClause() + "."
16073                                                + function.getColumns().get(j).getName());
16074                        }
16075                        else {
16076                                columnName.setString(functionName + "."
16077                                                + function.getColumns().get(j).getName());
16078                        }
16079                        ResultColumn resultColumn = modelFactory.createResultColumn(resultSetModel,
16080                                        columnName);
16081                        resultColumn.setStruct(true);
16082                        if (function.getColumns().get(j).getName() != null
16083                                        && !function.getColumns().get(j).getName().isEmpty()) {
16084                                // STRUCT tree: payload.a is the field node a below the column payload, as
16085                                // for a ROW the query builds; BigQuery STRUCT(x AS a) AS payload too, as
16086                                // the plan's approved change (b) (Mantis 4847, 4834). An item without an
16087                                // alias names no column: its fields only pair with a column list (Mantis 4891)
16088                                putStructField(resultColumn, new StructFieldPath(structAlias,
16089                                                java.util.Collections.singletonList(function.getColumns().get(j).getName()),
16090                                                column));
16091                        }
16092                        DataFlowRelationship relationship = modelFactory.createDataFlowRelation();
16093                        relationship.setTarget(new ResultColumnRelationshipElement(resultColumn));
16094                        relationship.addSource(
16095                                        new ResultColumnRelationshipElement(function.getColumns().get(j)));
16096                }
16097        }
16098
16099        /** The STRUCT fields the result columns stand for, by result column (rule R8). */
16100        private final Map<ResultColumn, StructFieldPath> structFieldPaths = new IdentityHashMap<ResultColumn, StructFieldPath>();
16101
16102        /** The branch columns each column of a set operation reads by position, past a star of unknown width (Mantis 4914). */
16103        private final Map<ResultColumn, List<ResultColumn>> setColumnSources = new IdentityHashMap<ResultColumn, List<ResultColumn>>();
16104
16105        /**
16106         * Registers the field path of a STRUCT field column; an SQL-LR recorder also learns which select item builds it
16107         * (observe-only: the field columns of one item are one column of the item for SQL-LR, plan 06 gate E4).
16108         */
16109        private void putStructField(ResultColumn column, StructFieldPath path) {
16110                structFieldPaths.put(column, path);
16111                SqllrObservations recorder = modelManager.getSqllrObservations();
16112                if (recorder != null && path != null && path.item != null) {
16113                        recorder.structField(column, path.item);
16114                }
16115        }
16116
16117        /**
16118         * Trino family: the field names of a select item {@code CAST(ROW(a, b) AS ROW(city VARCHAR, phone VARCHAR)) AS
16119         * contact}, one per value of the row, in order; null for any other item, or when the counts differ (Mantis 4834).
16120         */
16121        private List<String> rowCastFieldNames(TResultColumn column) {
16122                if (column.getAliasClause() == null || column.getAliasClause().getAliasName() == null
16123                                || column.getExpr() == null || column.getExpr().getExpressionType() != EExpressionType.function_t) {
16124                        return null;
16125                }
16126                return rowCastFieldNames(column.getExpr().getFunctionCall());
16127        }
16128
16129        /** {@link #rowCastFieldNames(TResultColumn)} of the call itself, the item aliased or not. */
16130        private List<String> rowCastFieldNames(TFunctionCall cast) {
16131                if (!nestedProfile().hasRowCastConstructor()) {
16132                        return null;
16133                }
16134                if (cast == null || cast.getFunctionType() != EFunctionType.cast_t || cast.getExpr1() == null
16135                                || cast.getExpr1().getExpressionType() != EExpressionType.row_constructor_t
16136                                || cast.getExpr1().getExprList() == null || cast.getTypename() == null
16137                                || cast.getTypename().getColumnDefList() == null) {
16138                        return null;
16139                }
16140                TColumnDefinitionList fields = cast.getTypename().getColumnDefList();
16141                if (fields.size() != cast.getExpr1().getExprList().size()) {
16142                        return null;
16143                }
16144                List<String> names = new ArrayList<String>();
16145                for (int i = 0; i < fields.size(); i++) {
16146                        if (fields.getColumn(i).getColumnName() == null) {
16147                                return null;
16148                        }
16149                        names.add(fields.getColumn(i).getColumnName().toString());
16150                }
16151                return names;
16152        }
16153
16154        /** The field path nodes the CREATE TABLE being analyzed declares, by declared column. */
16155        private final Map<TableColumn, List<TableColumn>> createTableDeclaredFields =
16156                        new IdentityHashMap<TableColumn, List<TableColumn>>();
16157
16158        /**
16159         * The files of an external table's LOCATION feed each field its DDL declares for {@code column}, from the same
16160         * field of the files' column {@code fileColumn}, as they feed the column (Mantis 4858): a read of
16161         * {@code payload.address.city} reaches {@code 's3://b/e/'.payload.address.city} through the table's field node.
16162         */
16163        private void linkDeclaredFieldsToFile(TableColumn column, TableColumn fileColumn, Process process) {
16164                // the fields this statement's DDL declares: a DROP keeps the table's model, with fields an earlier DDL declared
16165                List<TableColumn> declaredFields = createTableDeclaredFields.get(column);
16166                if (declaredFields == null) {
16167                        return;
16168                }
16169                for (TableColumn field : declaredFields) {
16170                        TableColumn fileField = modelFactory.createFileUriFieldPath(fileColumn, field.getFieldPath().getSegments());
16171                        if (fileField == null) {
16172                                continue;
16173                        }
16174                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
16175                        relation.addSource(new TableColumnRelationshipElement(fileField));
16176                        relation.setTarget(new TableColumnRelationshipElement(field));
16177                        relation.setProcess(process);
16178                }
16179        }
16180
16181        /**
16182         * STRUCT tree, plan section 4.2: the field path nodes of a declared column of type {@code type}, read by
16183         * {@link gudusoft.gsqlparser.common.structured.DeclaredTypeReader}: one per field of a STRUCT or ROW, intermediate
16184         * fields included; for every ARRAY a node for its element ({@code items[*]}, scalar elements too, so a LOCATION
16185         * feeds {@code tags[*]}, Mantis 4861); for every MAP a node for its value ({@code m[*]}) and its key
16186         * ({@code m[key]}), made here so a LOCATION feeds them. A field that no query reads stays a node without an edge.
16187         * The nodes are recorded in the nested-field declaration this CREATE TABLE opened (rule R10).
16188         */
16189        private void createStructFieldNodes(TableColumn declared, TTypeName type,
16190                        List<gudusoft.gsqlparser.common.structured.StructuredPathSegment> prefix, TObjectName position,
16191                        List<TableColumn> declaredFields) {
16192                createDeclaredNodes(declared, gudusoft.gsqlparser.common.structured.DeclaredTypeReader.read(type), prefix,
16193                                position, declaredFields);
16194        }
16195
16196        private void createDeclaredNodes(TableColumn declared, gudusoft.gsqlparser.common.structured.DeclaredTypeNode type,
16197                        List<gudusoft.gsqlparser.common.structured.StructuredPathSegment> prefix, TObjectName position,
16198                        List<TableColumn> declaredFields) {
16199                if (type == null) {
16200                        return;
16201                }
16202                TableColumn parent = modelFactory.createDeclaredFieldPath(declared, prefix, position);
16203                if (parent == null) {
16204                        return;
16205                }
16206                parent.setDeclaredKind(type.getKind());
16207                switch (type.getKind()) {
16208                case STRUCT:
16209                        if (type.getFields() == null) {
16210                                // the grammar kept no member list: the fields are not known, none is refused
16211                                return;
16212                        }
16213                        for (gudusoft.gsqlparser.common.structured.DeclaredTypeNode field : type.getFields()) {
16214                                List<gudusoft.gsqlparser.common.structured.StructuredPathSegment> path =
16215                                                new ArrayList<gudusoft.gsqlparser.common.structured.StructuredPathSegment>(prefix);
16216                                path.add(gudusoft.gsqlparser.common.structured.StructuredPathSegment.field(
16217                                                field.getFieldName().getColumnNameOnly()));
16218                                TableColumn node = declaredNode(declared, path, field.getFieldName(), declaredFields);
16219                                if (node != null) {
16220                                        createDeclaredNodes(declared, field, path, field.getFieldName(), declaredFields);
16221                                }
16222                        }
16223                        // declared once all its fields are made: a field the type does not list is not made later
16224                        parent.setFieldsDeclared(true);
16225                        break;
16226                case ARRAY:
16227                        List<gudusoft.gsqlparser.common.structured.StructuredPathSegment> element =
16228                                        new ArrayList<gudusoft.gsqlparser.common.structured.StructuredPathSegment>(prefix);
16229                        element.add(gudusoft.gsqlparser.common.structured.StructuredPathSegment.arrayElement());
16230                        if (declaredNode(declared, element, position, declaredFields) != null) {
16231                                createDeclaredNodes(declared, type.getElement(), element, position, declaredFields);
16232                        }
16233                        break;
16234                case MAP:
16235                        List<gudusoft.gsqlparser.common.structured.StructuredPathSegment> value =
16236                                        new ArrayList<gudusoft.gsqlparser.common.structured.StructuredPathSegment>(prefix);
16237                        value.add(gudusoft.gsqlparser.common.structured.StructuredPathSegment.mapValue());
16238                        if (declaredNode(declared, value, position, declaredFields) != null) {
16239                                createDeclaredNodes(declared, type.getElement(), value, position, declaredFields);
16240                        }
16241                        List<gudusoft.gsqlparser.common.structured.StructuredPathSegment> key =
16242                                        new ArrayList<gudusoft.gsqlparser.common.structured.StructuredPathSegment>(prefix);
16243                        key.add(gudusoft.gsqlparser.common.structured.StructuredPathSegment.mapKey());
16244                        if (declaredNode(declared, key, position, declaredFields) != null) {
16245                                createDeclaredNodes(declared, type.getKey(), key, position, declaredFields);
16246                        }
16247                        break;
16248                default:
16249                        break;
16250                }
16251        }
16252
16253        /** The declared field node at {@code path}, recorded in the declaration being made and in {@code declaredFields}. */
16254        private TableColumn declaredNode(TableColumn declared,
16255                        List<gudusoft.gsqlparser.common.structured.StructuredPathSegment> path, TObjectName position,
16256                        List<TableColumn> declaredFields) {
16257                TableColumn node = modelFactory.createDeclaredFieldPath(declared, path, position);
16258                if (node != null) {
16259                        node.setDeclaredField(true);
16260                        declaredFields.add(node);
16261                }
16262                return node;
16263        }
16264
16265        protected void flattenStructColumns(boolean hasDefinition, Table tableModel, TColumnDefinition column,
16266                        Stack<TColumnDefinition> columnPaths, int index) {
16267                columnPaths.push(column);
16268                for (int j = 0; j < column.getDatatype().getColumnDefList().size(); j++) {
16269                        TColumnDefinition columnDefinition = column.getDatatype().getColumnDefList().getColumn(j);
16270                        if (columnDefinition.getDatatype().getColumnDefList() != null) {
16271                                flattenStructColumns(hasDefinition, tableModel, columnDefinition, columnPaths, index);
16272                        } else {
16273                                TObjectName columnName = new TObjectName();
16274                                columnName.setString(getColumnName(columnPaths, columnDefinition));
16275                                TableColumn tableColumn = modelFactory.createTableColumn(tableModel, columnName, hasDefinition);
16276                                tableColumn.setColumnIndex(index);
16277                                tableColumn.setStruct(true);
16278                                
16279                                if(option.getAnalyzeMode() == AnalyzeMode.crud) {
16280                                        CrudRelationship crudRelationship = modelFactory.createCrudRelation();
16281                                        crudRelationship.setTarget(new TableColumnRelationshipElement(tableColumn));
16282                                        crudRelationship.setEffectType(EffectType.create_table);
16283                                }
16284                                
16285                                appendTableColumnToSQLEnv(tableModel, tableColumn);
16286                        }
16287                }
16288                columnPaths.pop();
16289        }
16290
16291        private String getColumnName(Stack<TColumnDefinition> columnPaths, TColumnDefinition column) {
16292                StringBuilder buffer = new StringBuilder();
16293                Iterator<TColumnDefinition> iter = columnPaths.iterator();
16294                while(iter.hasNext()) {
16295                        buffer.append(iter.next().getColumnName().getColumnNameOnly()).append(".");
16296                }
16297                buffer.append(column.getColumnName().getColumnNameOnly());
16298                return buffer.toString();
16299        }
16300
16301        private void appendTableColumnToSQLEnv(Table tableModel, TableColumn tableColumn) {
16302                //tableModel如果非determined,请不要添加到sqlenv里
16303                TSQLEnv targetEnv = tableMetadataEnvironment(tableModel);
16304                if (targetEnv != null && tableColumn!=null) {
16305                        TSQLSchema schema = targetEnv.getSQLSchema(DlineageUtil.getTableSchema(tableModel), true);
16306                        if (schema != null) {
16307                                TSQLTable tempTable = schema.createTable(DlineageUtil.getSimpleTableName(tableModel.getName()));
16308                                if (tableColumn.hasStarLinkColumn()) {
16309                                        for (int k = 0; k < tableColumn.starLinkCount(); k++) {
16310                                                tempTable.addColumn(tableColumn.starLinkCatalogSpelling(k));
16311                                        }
16312                                } else if (tableColumn.isStarColumn()) {
16313                                        addUnresolvedStar(tempTable);
16314                                } else {
16315                                        tempTable.addColumn(DlineageUtil.getColumnNameOnly(tableColumn.getName()));
16316                                }
16317                        }
16318                }
16319        }
16320
16321        /**
16322         * Records in the catalog that lineage could not resolve the table's width: a star entry
16323         * ({@code TSQLColumn.isUnresolvedStar()}), never a column of the table. A column the catalog already holds
16324         * under that name is left as it is.
16325         */
16326        private static void addUnresolvedStar(TSQLTable table) {
16327                table.addUnresolvedStar();
16328        }
16329
16330        private void appendTableColumnToSQLEnv(Table tableModel, TObjectName tableColumn) {
16331                TSQLEnv targetEnv = tableMetadataEnvironment(tableModel);
16332                if (targetEnv != null) {
16333                        TSQLSchema schema = targetEnv.getSQLSchema(DlineageUtil.getTableSchema(tableModel), true);
16334                        if (schema != null) {
16335                                TSQLTable tempTable = schema.createTable(DlineageUtil.getSimpleTableName(tableModel.getName()));
16336                                if (DlineageUtil.isStarName(tableColumn)) {
16337                                        addUnresolvedStar(tempTable);
16338                                } else {
16339                                        tempTable.addColumn(DlineageUtil.getColumnNameOnly(tableColumn.getColumnNameOnly()));
16340                                }
16341                        }
16342                }
16343        }
16344
16345        private TSQLEnv tableMetadataEnvironment(Table table) {
16346                TTable origin = table.getTableObject();
16347                if (origin != null && origin.getSqlEnv() != null && ModelBindingManager.isScopedTemporary(origin)) {
16348                        return origin.getSqlEnv();
16349                }
16350                return sqlenv;
16351        }
16352
16353        private void analyzeCreateStageStmt(TCreateStageStmt stmt) {
16354                TObjectName stageName = stmt.getStageName();
16355
16356                if (stageName != null) {
16357
16358                        Table tableModel = modelFactory.createStage(stageName);
16359                        tableModel.setCreateTable(true);
16360                        tableModel.setStage(true);
16361                        tableModel.setLocation(stmt.getExternalStageURL());
16362
16363                        Process process = modelFactory.createProcess(stmt);
16364                        tableModel.addProcess(process);
16365
16366                        TableColumn locationColumn = null;
16367                        if (stmt.getExternalStageURL() != null) {
16368                                TObjectName location = new TObjectName();
16369                                location.setString(stmt.getExternalStageURL());
16370                                locationColumn = modelFactory.createStageLocation(tableModel, location);
16371
16372                                Table pathModel = modelFactory.createTableByName(stmt.getExternalStageURL(), true);
16373                                pathModel.setPath(true);
16374                                pathModel.setCreateTable(true);
16375                                TObjectName fileUri = new TObjectName();
16376                                fileUri.setString(stmt.getExternalStageURL());
16377                                TableColumn fileUriColumn = modelFactory.createFileUri(pathModel, fileUri);
16378
16379                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
16380                                relation.addSource(new TableColumnRelationshipElement(fileUriColumn));
16381                                relation.setTarget(new TableColumnRelationshipElement(locationColumn));
16382                                relation.setProcess(process);
16383
16384                        } else {
16385                                for (TCustomSqlStatement temp : stmt.getGsqlparser().getSqlstatements()) {
16386                                        if (temp instanceof TPutStmt) {
16387                                                TPutStmt put = (TPutStmt) temp;
16388                                                TStageLocation stageLocation = put.getStageLocation();
16389                                                if (stageLocation != null && stageLocation.getStageName() != null) {
16390                                                        Table stage = modelManager.getTableByName(
16391                                                                        DlineageUtil.getTableIdentityKey(stageLocation.getStageName().toString()));
16392                                                        if (stage == tableModel) {
16393                                                                TObjectName location = new TObjectName();
16394                                                                if (!SQLUtil.isEmpty(put.getFileName())) {
16395                                                                        location.setString(put.getFileName());
16396                                                                        locationColumn = modelFactory.createStageLocation(tableModel, location);
16397                                                                }
16398                                                                break;
16399                                                        }
16400                                                }
16401                                        }
16402                                }
16403                        }
16404
16405                        String fileFormat = stmt.getFileFormatName();
16406                        if (fileFormat != null) {
16407                                for (TCustomSqlStatement temp : stmt.getGsqlparser().getSqlstatements()) {
16408                                        if (temp instanceof TCreateFileFormatStmt) {
16409                                                TCreateFileFormatStmt fileFormatStmt = (TCreateFileFormatStmt) temp;
16410                                                if (fileFormatStmt.getFileFormatName() != null
16411                                                                && SQLUtil.compareIdentifier(option.getVendor(), ESQLDataObjectType.dotTable, fileFormatStmt.getFileFormatName().toString(), fileFormat)) {
16412                                                        tableModel.setFileType(fileFormatStmt.getTypeName());
16413                                                        break;
16414                                                }
16415                                        }
16416                                }
16417                        }
16418
16419                        String procedureParent = getProcedureParentName(stmt);
16420                        if (procedureParent != null) {
16421                                tableModel.setParent(procedureParent);
16422                        }
16423
16424                        if (locationColumn != null) {
16425                                // A table whose LOCATION names this stage (its stage name as written, keyed like the stage).
16426                                Object stageKey = DlineageUtil.getTableIdentityKey(stageName.toString());
16427                                List<Table> tables = modelManager.getTablesByName();
16428                                if (tables != null) {
16429                                        for (Table referTable : tables) {
16430                                                if (referTable.getLocation() != null
16431                                                                && stageKey.equals(DlineageUtil.getTableIdentityKey(referTable.getLocation()))) {
16432                                                        for (int i = 0; i < referTable.getColumns().size(); i++) {
16433                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
16434                                                                relation.addSource(new TableColumnRelationshipElement(locationColumn));
16435                                                                relation.setTarget(new TableColumnRelationshipElement(referTable.getColumns().get(i)));
16436                                                        }
16437                                                }
16438                                        }
16439                                }
16440                        }
16441                } else {
16442                        ErrorInfo errorInfo = new ErrorInfo();
16443                        errorInfo.setErrorType(ErrorInfo.ANALYZE_ERROR);
16444                        errorInfo.setErrorMessage("Can't get target table. CreateStageStmt is " + stmt.toString());
16445                        errorInfo.setStartPosition(new Pair3<Long, Long, String>(stmt.getStartToken().lineNo,
16446                                        stmt.getStartToken().columnNo, ModelBindingManager.getGlobalHash()));
16447                        errorInfo.setEndPosition(new Pair3<Long, Long, String>(stmt.getEndToken().lineNo,
16448                                        stmt.getEndToken().columnNo + stmt.getEndToken().getAstext().length(),
16449                                        ModelBindingManager.getGlobalHash()));
16450                        errorInfo.fillInfo(this);
16451                        errorInfos.add(errorInfo);
16452                }
16453        }
16454
16455        private void analyzeCreateExternalDataSourceStmt(TCreateExternalDataSourceStmt stmt) {
16456                TObjectName dataSourceName = stmt.getDataSourceName();
16457                String locationUrl = stmt.getOption("LOCATION");
16458                if (dataSourceName != null && locationUrl != null) {
16459                        Table tableModel = modelFactory.createDataSource(dataSourceName);
16460                        tableModel.setCreateTable(true);
16461                        tableModel.setDataSource(true);
16462                        tableModel.setLocation(locationUrl);
16463
16464                        Process process = modelFactory.createProcess(stmt);
16465                        tableModel.addProcess(process);
16466
16467                        TObjectName location = new TObjectName();
16468                        location.setString(locationUrl);
16469                        modelFactory.createTableColumn(tableModel, location, true);
16470
16471                        String procedureParent = getProcedureParentName(stmt);
16472                        if (procedureParent != null) {
16473                                tableModel.setParent(procedureParent);
16474                        }
16475                } else {
16476                        ErrorInfo errorInfo = new ErrorInfo();
16477                        errorInfo.setErrorType(ErrorInfo.ANALYZE_ERROR);
16478                        errorInfo.setErrorMessage("Can't get target table. CreateExternalDataSourceStmt is " + stmt.toString());
16479                        errorInfo.setStartPosition(new Pair3<Long, Long, String>(stmt.getStartToken().lineNo,
16480                                        stmt.getStartToken().columnNo, ModelBindingManager.getGlobalHash()));
16481                        errorInfo.setEndPosition(new Pair3<Long, Long, String>(stmt.getEndToken().lineNo,
16482                                        stmt.getEndToken().columnNo + stmt.getEndToken().getAstext().length(),
16483                                        ModelBindingManager.getGlobalHash()));
16484                        errorInfo.fillInfo(this);
16485                        errorInfos.add(errorInfo);
16486                }
16487        }
16488
16489        private void analyzeCreateStreamStmt(TCreateStreamStmt stmt) {
16490                TObjectName streamName = stmt.getStreamName();
16491
16492                if (streamName != null) {
16493                        Table tableModel = modelFactory.createTableByName(stmt.getTableName());
16494                        tableModel.setCreateTable(true);
16495
16496                        Table streamModel = modelFactory.createStream(streamName);
16497                        streamModel.setCreateTable(true);
16498                        streamModel.setStream(true);
16499
16500                        Process process = modelFactory.createProcess(stmt);
16501                        tableModel.addProcess(process);
16502
16503                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
16504                        relation.setTarget(new RelationRowsRelationshipElement<TableRelationRows>(streamModel.getRelationRows()));
16505                        relation.addSource(new RelationRowsRelationshipElement<TableRelationRows>(tableModel.getRelationRows()));
16506                        relation.setProcess(process);
16507                }
16508        }
16509
16510        /**
16511         * The routine statement a statement belongs to, found by the same walk as
16512         * {@link #getProcedureParentName}: the statement itself when it is a named stored procedure or function,
16513         * else the enclosing one, a block's owner included.
16514         */
16515        private TCustomSqlStatement getProcedureParentStatement(TCustomSqlStatement stmt) {
16516                if (stmt instanceof TStoredProcedureSqlStatement
16517                                && ((TStoredProcedureSqlStatement) stmt).getStoredProcedureName() != null) {
16518                        return stmt;
16519                }
16520                stmt = stmt.getParentStmt();
16521                if (stmt == null) {
16522                        return null;
16523                }
16524                if (stmt instanceof TCommonBlock
16525                                && ((TCommonBlock) stmt).getBlockBody().getParentObjectName() instanceof TStoredProcedureSqlStatement) {
16526                        stmt = (TStoredProcedureSqlStatement) ((TCommonBlock) stmt).getBlockBody().getParentObjectName();
16527                }
16528                if (stmt instanceof TStoredProcedureSqlStatement
16529                                && ((TStoredProcedureSqlStatement) stmt).getStoredProcedureName() != null) {
16530                        return stmt;
16531                }
16532                if (stmt instanceof TTeradataCreateProcedure && ((TTeradataCreateProcedure) stmt).getProcedureName() != null) {
16533                        return stmt;
16534                }
16535                return getProcedureParentStatement(stmt);
16536        }
16537
16538        /**
16539         * The lineage model of the routine a statement belongs to, reached through that routine's own statement:
16540         * each routine's model is bound to its statement when it is created, and kept apart from what the
16541         * statement is bound to later (a SQL function's result set). Looking it up by name read a folded spelling
16542         * and, for an overloaded routine, took whichever overload of that name came first.
16543         */
16544        private Procedure getEnclosingProcedure(TCustomSqlStatement stmt) {
16545                return modelManager.getRoutineModel(getProcedureParentStatement(stmt));
16546        }
16547
16548        /** A call's name as written with its argument-count suffix where the vendor overloads routines by count. */
16549        private RoutineAliasKey callAliasKey(TFunctionCall call) {
16550                if (call == null) return null;
16551                return nameAliasKey(call.getFunctionName(), RoutineAliases.callArgumentCount(call, ModelBindingManager.getGlobalVendor()));
16552        }
16553
16554        /** A routine name as written with an alias suffix ({@code (n)}, or none). */
16555        private RoutineAliasKey nameAliasKey(TObjectName name, String signature) {
16556                return name == null ? null : RoutineAliasKey.written(name, signature);
16557        }
16558
16559        /** The name of the routine a statement belongs to, the node getProcedureParentName reads, as written. */
16560        private RoutineAliasKey procedureParentAliasKey(TCustomSqlStatement stmt) {
16561                if (stmt == null) return null;
16562                TCustomSqlStatement parent = getProcedureParentStatement(stmt);
16563                TObjectName name = null;
16564                if (parent instanceof TStoredProcedureSqlStatement
16565                                && ((TStoredProcedureSqlStatement) parent).getStoredProcedureName() != null) {
16566                        name = ((TStoredProcedureSqlStatement) parent).getStoredProcedureName();
16567                } else if (parent instanceof TTeradataCreateProcedure) {
16568                        name = ((TTeradataCreateProcedure) parent).getProcedureName();
16569                }
16570                return nameAliasKey(name, null);
16571        }
16572
16573        /** {@code key} with a single identifier as written in front (a model's schema field); null-safe. */
16574        private RoutineAliasKey qualifiedAliasKey(RoutineAliasKey key, String qualifier) {
16575                if (key == null) return null;
16576                try {
16577                        return key.withQualifier(qualifier);
16578                } catch (RuntimeException failure) {
16579                        return null;
16580                }
16581        }
16582
16583        /**
16584         * {@code key} qualified as DlineageUtil.getTableFullNameAsWritten qualifies a name before the legacy lookup: a
16585         * one-level name gets the session's database and schema spellings, a two-level name its database spelling.
16586         */
16587        private RoutineAliasKey tableDefaultedAliasKey(RoutineAliasKey key) {
16588                if (key == null) return null;
16589                try {
16590                        String database = ModelBindingManager.getGlobalDatabaseSpelling();
16591                        String schema = ModelBindingManager.getGlobalSchemaSpelling();
16592                        if (key.size() == 1) {
16593                                RoutineAliasKey qualified = schema == null ? key : key.withQualifier(schema);
16594                                return database == null ? qualified : qualified.withQualifier(database);
16595                        }
16596                        if (key.size() == 2 && database != null) return key.withQualifier(database);
16597                        return key;
16598                } catch (RuntimeException failure) {
16599                        return null;
16600                }
16601        }
16602
16603        /** A function result set's name as written: its function model's, or its call's. */
16604        private RoutineAliasKey resultSetAliasKey(ResultSet model) {
16605                if (model instanceof Function) return functionAliasKey((Function) model);
16606                if (model != null && model.getGspObject() instanceof TFunctionCall) {
16607                        return nameAliasKey(((TFunctionCall) model.getGspObject()).getFunctionName(), null);
16608                }
16609                return null;
16610        }
16611
16612        /** A function model's name as written, the text getIdentifiedFunctionName reads. */
16613        private RoutineAliasKey functionAliasKey(Function function) {
16614                if (function == null) return null;
16615                if (function.getAliasKey() != null) return function.getAliasKey();
16616                TParseTreeNode object = function.getFunctionObject();
16617                if (object instanceof TFunctionCall) return nameAliasKey(((TFunctionCall) object).getFunctionName(), null);
16618                if (object instanceof TObjectName) return nameAliasKey((TObjectName) object, null);
16619                return null;
16620        }
16621
16622        /** Analyzes the script's definition of the routine a call names ({@code key}), if it has one. */
16623        private boolean analyzeProcedureDefinition(RoutineAliasKey key) {
16624                TCustomSqlStatement definition = procedureDDLAliases.get(key);
16625                if (definition == null) return false;
16626                analyzeCustomSqlStmt(definition);
16627                return true;
16628        }
16629
16630        /**
16631         * Measurement only (D3b.0, RoutineShadow): the typed routine registry's answer beside the legacy callee. Callers
16632         * test RoutineShadow.enabled() first, so nothing here is evaluated when it is off.
16633         */
16634        private void shadowCallee(String site, TObjectName name, Procedure callee, Integer[] aliases,
16635                        gudusoft.gsqlparser.catalog.runtime.CatalogObjectKind wanted) {
16636                gudusoft.gsqlparser.dlineage.routine.RoutineShadow.lookedUp(modelManager, site, stmtStack.isEmpty() ? null : stmtStack.peek(), name, aliases, wanted, callee);
16637        }
16638
16639        private String getProcedureParentName(TCustomSqlStatement stmt) {
16640                if (stmt instanceof TStoredProcedureSqlStatement) {
16641                        if (((TStoredProcedureSqlStatement) stmt).getStoredProcedureName() != null) {
16642                                return ((TStoredProcedureSqlStatement) stmt).getStoredProcedureName().toString();
16643                        }
16644                }
16645
16646                stmt = stmt.getParentStmt();
16647                if (stmt == null)
16648                        return null;
16649
16650        if (stmt instanceof TCommonBlock) {
16651                        if(((TCommonBlock) stmt).getBlockBody().getParentObjectName() instanceof TStoredProcedureSqlStatement) {
16652                                stmt = (TStoredProcedureSqlStatement)((TCommonBlock) stmt).getBlockBody().getParentObjectName();
16653                        }
16654                }
16655
16656                if (stmt instanceof TStoredProcedureSqlStatement) {
16657                        if (((TStoredProcedureSqlStatement) stmt).getStoredProcedureName() != null) {
16658                                return ((TStoredProcedureSqlStatement) stmt).getStoredProcedureName().toString();
16659                        }
16660                }
16661                if (stmt instanceof TTeradataCreateProcedure) {
16662                        if (((TTeradataCreateProcedure) stmt).getProcedureName() != null) {
16663                                return ((TTeradataCreateProcedure) stmt).getProcedureName().toString();
16664                        }
16665                }
16666
16667                return getProcedureParentName(stmt);
16668        }
16669
16670        private TStoredProcedureSqlStatement getProcedureParent(TCustomSqlStatement stmt) {
16671                if (stmt instanceof TStoredProcedureSqlStatement) {
16672                        return (TStoredProcedureSqlStatement) stmt;
16673                }
16674
16675                stmt = stmt.getParentStmt();
16676                if (stmt == null)
16677                        return null;
16678
16679                if (stmt instanceof TCommonBlock) {
16680                        if (((TCommonBlock) stmt).getBlockBody().getParentObjectName() instanceof TStoredProcedureSqlStatement) {
16681                                stmt = (TStoredProcedureSqlStatement) ((TCommonBlock) stmt).getBlockBody().getParentObjectName();
16682                        }
16683                }
16684
16685                if (stmt instanceof TStoredProcedureSqlStatement) {
16686                        return ((TStoredProcedureSqlStatement) stmt);
16687                }
16688                if (stmt instanceof TTeradataCreateProcedure) {
16689                        return ((TTeradataCreateProcedure) stmt);
16690                }
16691
16692                return getProcedureParent(stmt);
16693        }
16694
16695        private static final class CteWriteIntent {
16696                private final List<TObjectName> columns = new ArrayList<TObjectName>();
16697                private String unsupportedReason;
16698        }
16699
16700        private static final class CteTargetColumnMapping {
16701                private final String exposedName;
16702                private final String baseColumnName;
16703
16704                private CteTargetColumnMapping(String exposedName, String baseColumnName) {
16705                        this.exposedName = exposedName;
16706                        this.baseColumnName = baseColumnName;
16707                }
16708        }
16709
16710        private static final class CteWriteTargetResolution {
16711                private TTable baseTable;
16712                private final List<CteTargetColumnMapping> columns = new ArrayList<CteTargetColumnMapping>();
16713                private String failureReason;
16714
16715                private boolean isWritable() {
16716                        return baseTable != null;
16717                }
16718        }
16719
16720        private static final class ProjectedColumnResolution {
16721                private TTable baseTable;
16722                private String baseColumnName;
16723                private String failureReason;
16724        }
16725
16726        private static final class CteSelectRelationKey {
16727                private final ResultColumn target;
16728                private final ResultColumn source;
16729
16730                private CteSelectRelationKey(ResultColumn target, ResultColumn source) {
16731                        this.target = target;
16732                        this.source = source;
16733                }
16734
16735                @Override
16736                public int hashCode() {
16737                        return 31 * System.identityHashCode(target) + System.identityHashCode(source);
16738                }
16739
16740                @Override
16741                public boolean equals(Object other) {
16742                        if (!(other instanceof CteSelectRelationKey)) {
16743                                return false;
16744                        }
16745                        CteSelectRelationKey key = (CteSelectRelationKey) other;
16746                        return target == key.target && source == key.source;
16747                }
16748        }
16749
16750        private CteWriteIntent collectMergeCteWriteIntent(TMergeSqlStatement stmt, TTable targetTable) {
16751                CteWriteIntent intent = new CteWriteIntent();
16752                if (stmt.getWhenClauses() == null) {
16753                        intent.unsupportedReason = "the MERGE has no writable target columns";
16754                        return intent;
16755                }
16756                for (int i = 0; i < stmt.getWhenClauses().size(); i++) {
16757                        TMergeWhenClause clause = stmt.getWhenClauses().getElement(i);
16758                        if (clause.getUpdateClause() != null) {
16759                                TResultColumnList assignments = clause.getUpdateClause().getUpdateColumnList();
16760                                if (assignments != null) {
16761                                        for (int j = 0; j < assignments.size(); j++) {
16762                                                TExpression assignment = assignments.getResultColumn(j).getExpr();
16763                                                addCteWriteIntentTargets(intent, assignment == null ? null : assignment.getLeftOperand());
16764                                        }
16765                                }
16766                        }
16767                        if (clause.getInsertClause() != null) {
16768                                if (clause.getInsertClause().getInsertValue() != null) {
16769                                        addCteWriteIntentWarning(intent,
16770                                                        "an object-constructor INSERT target cannot be mapped to CTE projections");
16771                                        continue;
16772                                }
16773                                TObjectNameList columns = clause.getInsertClause().getColumnList();
16774                                if (columns == null || columns.size() == 0) {
16775                                        List<TObjectName> inferredColumns = inferProjectedInsertColumns(targetTable,
16776                                                        clause.getInsertClause().getValuelist());
16777                                        if (inferredColumns == null) {
16778                                                addCteWriteIntentWarning(intent,
16779                                                                "an INSERT without an explicit target column list cannot be mapped safely");
16780                                                continue;
16781                                        }
16782                                        for (TObjectName inferredColumn : inferredColumns) {
16783                                                addCteWriteIntentColumn(intent, inferredColumn);
16784                                        }
16785                                        continue;
16786                                }
16787                                for (int j = 0; j < columns.size(); j++) {
16788                                        addCteWriteIntentColumn(intent, columns.getObjectName(j));
16789                                }
16790                        }
16791                }
16792                if (intent.columns.isEmpty() && intent.unsupportedReason == null) {
16793                        intent.unsupportedReason = "the MERGE has no modified columns from which to resolve a base table";
16794                }
16795                return intent;
16796        }
16797
16798        private void addCteWriteIntentWarning(CteWriteIntent intent, String reason) {
16799                if (intent.unsupportedReason == null) {
16800                        intent.unsupportedReason = reason;
16801                } else if (!intent.unsupportedReason.contains(reason)) {
16802                        intent.unsupportedReason += "; " + reason;
16803                }
16804        }
16805
16806        private List<TObjectName> inferProjectedInsertColumns(TTable targetTable, TResultColumnList values) {
16807                if (targetTable == null || values == null) {
16808                        return null;
16809                }
16810                TSelectSqlStatement subquery = null;
16811                TObjectNameList exposedColumns = null;
16812                if (targetTable.getCTE() != null) {
16813                        subquery = targetTable.getCTE().getSubquery();
16814                        exposedColumns = targetTable.getCTE().getColumnList();
16815                } else if (targetTable.getSubquery() != null) {
16816                        subquery = targetTable.getSubquery();
16817                        exposedColumns = getProjectedTargetColumns(targetTable);
16818                } else if (targetTable.getLinkTable() != null
16819                                && targetTable.getLinkTable().getSubquery() != null) {
16820                        subquery = targetTable.getLinkTable().getSubquery();
16821                        exposedColumns = getProjectedTargetColumns(targetTable.getLinkTable());
16822                }
16823                TResultColumnList projections = subquery == null ? null : subquery.getResultColumnList();
16824                if (projections == null || projections.size() != values.size()) {
16825                        return null;
16826                }
16827                if (exposedColumns != null && exposedColumns.size() > 0
16828                                && exposedColumns.size() != projections.size()) {
16829                        return null;
16830                }
16831                List<TObjectName> inferredColumns = new ArrayList<TObjectName>();
16832                for (int i = 0; i < projections.size(); i++) {
16833                        TResultColumn projection = projections.getResultColumn(i);
16834                        if (isCteStarProjection(projection)) {
16835                                return null;
16836                        }
16837                        String exposedName = getProjectedColumnName(exposedColumns, projection, i);
16838                        if (SQLUtil.isEmpty(exposedName)) {
16839                                return null;
16840                        }
16841                        TObjectName column = new TObjectName();
16842                        TSourceToken nameToken = new TSourceToken(exposedName);
16843                        TResultColumn value = values.getResultColumn(i);
16844                        column.setPartToken(nameToken);
16845                        column.setObjectType(TObjectName.ttobjColumn);
16846                        column.setSourceTable(targetTable);
16847                        column.setStartToken(value.getStartToken() == null
16848                                        ? nameToken : value.getStartToken());
16849                        column.setEndToken(value.getEndToken() == null
16850                                        ? nameToken : value.getEndToken());
16851                        inferredColumns.add(column);
16852                }
16853                return inferredColumns;
16854        }
16855
16856        private CteWriteIntent collectUpdateCteWriteIntent(TUpdateSqlStatement stmt) {
16857                CteWriteIntent intent = new CteWriteIntent();
16858                TResultColumnList assignments = stmt.getResultColumnList();
16859                if (assignments == null) {
16860                        intent.unsupportedReason = "the UPDATE has no writable target columns";
16861                        return intent;
16862                }
16863                for (int i = 0; i < assignments.size(); i++) {
16864                        TExpression assignment = assignments.getResultColumn(i).getExpr();
16865                        if (assignment == null || assignment.getExpressionType() == EExpressionType.function_t) {
16866                                addCteWriteIntentWarning(intent,
16867                                                "an UPDATE assignment target cannot be mapped to a CTE projection");
16868                                continue;
16869                        }
16870                        addCteWriteIntentTargets(intent, assignment.getLeftOperand());
16871                }
16872                if (intent.columns.isEmpty() && intent.unsupportedReason == null) {
16873                        intent.unsupportedReason = "the UPDATE has no modified columns from which to resolve a base table";
16874                }
16875                return intent;
16876        }
16877
16878        /** The target of an UPDATE SET assignment under any parentheses: SET (t.col) = (...) writes t.col (Mantis 4984). */
16879        private static TExpression updateSetTarget(TExpression target) {
16880                while (target != null && target.getExpressionType() == EExpressionType.parenthesis_t) {
16881                        target = target.getLeftOperand();
16882                }
16883                return target;
16884        }
16885
16886        /**
16887         * The {@code count} items of a row value, (x, y) or ROW(x, y), also inside grouping parentheses such as
16888         * (ROW(x, y)), or null when the value is no row of that many items.
16889         */
16890        private static TExpressionList rowValues(TExpression value, int count) {
16891                value = updateSetTarget(value);
16892                while (value != null && (value.getExpressionType() == EExpressionType.list_t
16893                                || value.getExpressionType() == EExpressionType.row_constructor_t) && value.getExprList() != null) {
16894                        if (value.getExprList().size() == count) {
16895                                return value.getExprList();
16896                        }
16897                        if (value.getExprList().size() != 1) {
16898                                return null;
16899                        }
16900                        value = updateSetTarget(value.getExprList().getExpression(0));
16901                }
16902                return null;
16903        }
16904
16905        /** The columns an UPDATE SET assignment target names, for mapping a CTE or inline view target to its base table. */
16906        private void addCteWriteIntentTargets(CteWriteIntent intent, TExpression target) {
16907                target = updateSetTarget(target);
16908                if (target != null && target.getExpressionType() == EExpressionType.simple_object_name_t) {
16909                        addCteWriteIntentColumn(intent, target.getObjectOperand());
16910                } else if (target != null && target.getExpressionType() == EExpressionType.list_t
16911                                && target.getExprList() != null) {
16912                        for (int j = 0; j < target.getExprList().size(); j++) {
16913                                TExpression item = target.getExprList().getExpression(j);
16914                                if (item.getExpressionType() == EExpressionType.simple_object_name_t) {
16915                                        addCteWriteIntentColumn(intent, item.getObjectOperand());
16916                                } else {
16917                                        addCteWriteIntentWarning(intent,
16918                                                        "an UPDATE assignment target cannot be mapped to a CTE projection");
16919                                }
16920                        }
16921                } else {
16922                        addCteWriteIntentWarning(intent, "an UPDATE assignment target cannot be mapped to a CTE projection");
16923                }
16924        }
16925
16926        /** Reports an UPDATE SET assignment that is left out of the lineage, at its target, else at the assignment. */
16927        private void addUnanalyzedSetAssignmentError(String reason, TExpression target, TExpression assignment) {
16928                TExpression at = target != null && target.getStartToken() != null && target.getEndToken() != null ? target
16929                                : assignment;
16930                ErrorInfo errorInfo = new ErrorInfo();
16931                errorInfo.setErrorType(ErrorInfo.ANALYZE_ERROR);
16932                errorInfo.setErrorMessage(reason + ", the assignment is not analyzed: " + at.toString());
16933                errorInfo.setStartPosition(new Pair3<Long, Long, String>(at.getStartToken().lineNo,
16934                                at.getStartToken().columnNo, ModelBindingManager.getGlobalHash()));
16935                errorInfo.setEndPosition(new Pair3<Long, Long, String>(at.getEndToken().lineNo,
16936                                at.getEndToken().columnNo + at.getEndToken().getAstext().length(), ModelBindingManager.getGlobalHash()));
16937                errorInfo.fillInfo(this);
16938                errorInfos.add(errorInfo);
16939        }
16940
16941        /** A MERGE UPDATE SET target that is a variable, not a column of the target table. */
16942        private boolean isMergeUpdateVariable(TObjectName columnObject) {
16943                if (columnObject.getDbObjectType() == EDbObjectType.variable) {
16944                        return true;
16945                }
16946                if (columnObject.getColumnNameOnly().startsWith("@")
16947                                && (option.getVendor() == EDbVendor.dbvmssql || option.getVendor() == EDbVendor.dbvazuresql)) {
16948                        return true;
16949                }
16950                return columnObject.getColumnNameOnly().startsWith(":")
16951                                && (option.getVendor() == EDbVendor.dbvhana || option.getVendor() == EDbVendor.dbvteradata);
16952        }
16953
16954        /** MERGE ... UPDATE SET column = value: the MERGE-UPDATE column takes the value and writes the target column. */
16955        private void analyzeMergeUpdateColumn(TObjectName columnObject, TExpression valueExpression, ResultSet resultSet,
16956                        Object tableModel, CteWriteTargetResolution cteTargetResolution, Process process) {
16957                if (isMergeUpdateVariable(columnObject)) {
16958                        return;
16959                }
16960
16961                ResultColumn updateColumn = modelFactory.createMergeResultColumn(resultSet, columnObject);
16962
16963                if (valueExpression == null)
16964                        return;
16965
16966                columnsInExpr visitor = new columnsInExpr();
16967                valueExpression.inOrderTraverse(visitor);
16968                List<TObjectName> objectNames = visitor.getObjectNames();
16969                List<TParseTreeNode> functions = visitor.getFunctions();
16970
16971                if (functions != null && !functions.isEmpty()) {
16972                        analyzeFunctionDataFlowRelation(updateColumn, functions, EffectType.merge_update);
16973                }
16974
16975                List<TSelectSqlStatement> subquerys = visitor.getSubquerys();
16976                if (subquerys != null && !subquerys.isEmpty()) {
16977                        analyzeSubqueryDataFlowRelation(updateColumn, subquerys, EffectType.merge_update);
16978                }
16979
16980                analyzeDataFlowRelation(updateColumn, objectNames, EffectType.merge_update, functions);
16981
16982                List<TParseTreeNode> constants = visitor.getConstants();
16983                analyzeConstantDataFlowRelation(updateColumn, constants, EffectType.merge_update, functions);
16984
16985                writeMergeUpdateColumn(columnObject, updateColumn, tableModel, cteTargetResolution, process);
16986        }
16987
16988        private void writeMergeUpdateColumn(TObjectName columnObject, ResultColumn updateColumn, Object tableModel,
16989                        CteWriteTargetResolution cteTargetResolution, Process process) {
16990                if (tableModel instanceof Table) {
16991                        TObjectName targetColumn = getCteBaseColumn(cteTargetResolution, columnObject);
16992                        TableColumn tableColumn = targetColumn == null ? null
16993                                        : modelFactory.createTableColumn((Table) tableModel, targetColumn, false);
16994
16995                        if (tableColumn != null) {
16996                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
16997                                relation.setEffectType(EffectType.merge_update);
16998                                relation.setTarget(new TableColumnRelationshipElement(tableColumn));
16999                                relation.addSource(new ResultColumnRelationshipElement(updateColumn));
17000                                relation.setProcess(process);
17001                        }
17002                }
17003        }
17004
17005        /**
17006         * MERGE ... UPDATE SET (a, b) = (SELECT x, y ...) or (x, y), also ROW(x, y): each column takes the value at its
17007         * position (Mantis 4986). A subquery whose select list has a star or another length cannot be paired by position, so
17008         * the assignment is reported instead of guessed.
17009         */
17010        private void analyzeMergeUpdateColumnList(TExpression assignment, TExpression targetList, ResultSet resultSet,
17011                        Object tableModel, CteWriteTargetResolution cteTargetResolution, Process process) {
17012                TExpressionList targets = targetList.getExprList();
17013                TExpression value = assignment.getRightOperand();
17014                if (targets == null || value == null) {
17015                        addUnanalyzedSetAssignmentError("Unsupported UPDATE SET target", targetList, assignment);
17016                        return;
17017                }
17018                for (int k = 0; k < targets.size(); k++) {
17019                        if (targets.getExpression(k).getExpressionType() != EExpressionType.simple_object_name_t) {
17020                                addUnanalyzedSetAssignmentError("Unsupported UPDATE SET target", targetList, assignment);
17021                                return;
17022                        }
17023                }
17024
17025                TSelectSqlStatement query = value.getSubQuery();
17026                if (query != null) {
17027                        analyzeSelectStmt(query);
17028                        ResultSet sourceModel = (ResultSet) modelManager.getModel(query);
17029                        List<ResultColumn> sourceColumns = sourceModel == null ? null : sourceModel.getColumns();
17030                        boolean pairable = sourceColumns != null && sourceColumns.size() == targets.size();
17031                        for (int k = 0; pairable && k < sourceColumns.size(); k++) {
17032                                pairable = !isStarNode(sourceColumns.get(k).getColumnObject());
17033                        }
17034                        if (!pairable) {
17035                                addUnanalyzedSetAssignmentError("UPDATE SET columns do not pair with the subquery's select list",
17036                                                targetList, assignment);
17037                                return;
17038                        }
17039                        for (int k = 0; k < targets.size(); k++) {
17040                                TObjectName columnObject = targets.getExpression(k).getObjectOperand();
17041                                if (isMergeUpdateVariable(columnObject)) {
17042                                        continue;
17043                                }
17044                                ResultColumn updateColumn = modelFactory.createMergeResultColumn(resultSet, columnObject);
17045                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
17046                                relation.setEffectType(EffectType.merge_update);
17047                                relation.setTarget(new ResultColumnRelationshipElement(updateColumn));
17048                                relation.addSource(new ResultColumnRelationshipElement(sourceColumns.get(k)));
17049                                writeMergeUpdateColumn(columnObject, updateColumn, tableModel, cteTargetResolution, process);
17050                        }
17051                        // the rows the subquery picks decide the values, as for SET column = (subquery)
17052                        if (!sourceModel.getRelationRows().getHoldRelations().isEmpty()) {
17053                                ImpactRelationship impactRelation = modelFactory.createImpactRelation();
17054                                impactRelation.setEffectType(EffectType.select);
17055                                impactRelation.addSource(
17056                                                new RelationRowsRelationshipElement<ResultSetRelationRows>(sourceModel.getRelationRows()));
17057                                impactRelation.setTarget(
17058                                                new RelationRowsRelationshipElement<ResultSetRelationRows>(resultSet.getRelationRows()));
17059                        }
17060                } else if (rowValues(value, targets.size()) != null) {
17061                        TExpressionList values = rowValues(value, targets.size());
17062                        for (int k = 0; k < targets.size(); k++) {
17063                                analyzeMergeUpdateColumn(targets.getExpression(k).getObjectOperand(), values.getExpression(k),
17064                                                resultSet, tableModel, cteTargetResolution, process);
17065                        }
17066                } else {
17067                        addUnanalyzedSetAssignmentError("UPDATE SET columns do not pair with the assigned value", targetList,
17068                                        assignment);
17069                }
17070        }
17071
17072        private void addCteWriteIntentColumn(CteWriteIntent intent, TObjectName column) {
17073                if (column == null || column.getDbObjectType() == EDbObjectType.variable) {
17074                        return;
17075                }
17076                String name = column.getColumnNameOnly();
17077                if (name == null) {
17078                        return;
17079                }
17080                if (name.startsWith("@") && (option.getVendor() == EDbVendor.dbvmssql
17081                                || option.getVendor() == EDbVendor.dbvazuresql)) {
17082                        return;
17083                }
17084                if (name.startsWith(":") && (option.getVendor() == EDbVendor.dbvhana
17085                                || option.getVendor() == EDbVendor.dbvteradata)) {
17086                        return;
17087                }
17088                intent.columns.add(column);
17089        }
17090
17091        private CteWriteTargetResolution resolveCteWriteTarget(TTable targetTable, CteWriteIntent intent) {
17092                TSelectSqlStatement subquery = targetTable == null || targetTable.getCTE() == null
17093                                ? null : targetTable.getCTE().getSubquery();
17094                TObjectNameList exposedColumns = targetTable == null || targetTable.getCTE() == null
17095                                ? null : targetTable.getCTE().getColumnList();
17096                return resolveProjectedWriteTarget(subquery, exposedColumns, intent);
17097        }
17098
17099        private CteWriteTargetResolution resolveProjectedWriteTarget(TSelectSqlStatement subquery,
17100                        TObjectNameList exposedColumns, CteWriteIntent intent) {
17101                CteWriteTargetResolution resolution = new CteWriteTargetResolution();
17102                if (intent.unsupportedReason != null) {
17103                        appendCteResolutionWarning(resolution, intent.unsupportedReason);
17104                }
17105                if (subquery == null) {
17106                        appendCteResolutionWarning(resolution, "the target CTE has no SELECT body");
17107                        return resolution;
17108                }
17109
17110                if (subquery.isCombinedQuery() || subquery.getSetOperatorType() != ESetOperatorType.none) {
17111                        appendCteResolutionWarning(resolution,
17112                                        "a CTE with a set operator is not a writable single-table projection");
17113                        return resolution;
17114                }
17115                if (subquery.getSelectDistinct() != null && !subquery.getSelectDistinct().isAll()) {
17116                        appendCteResolutionWarning(resolution,
17117                                        "a DISTINCT CTE is not a writable single-table projection");
17118                        return resolution;
17119                }
17120                if (hasCteAggregate(subquery)) {
17121                        appendCteResolutionWarning(resolution,
17122                                        "a grouped or aggregate CTE is not a writable single-table projection");
17123                        return resolution;
17124                }
17125                TResultColumnList projections = subquery.getResultColumnList();
17126                if (projections == null || projections.size() == 0) {
17127                        appendCteResolutionWarning(resolution, "the target CTE has no selectable columns");
17128                        return resolution;
17129                }
17130
17131                for (TObjectName modifiedColumn : intent.columns) {
17132                        int projectionIndex = findCteProjectionIndex(exposedColumns, modifiedColumn, projections);
17133                        if (projectionIndex < 0) {
17134                                appendCteResolutionWarning(resolution, "modified column '" + modifiedColumn.getColumnNameOnly()
17135                                                + "' does not map uniquely to a CTE projection");
17136                                continue;
17137                        }
17138                        TResultColumn projection = projections.getResultColumn(projectionIndex);
17139                        boolean starProjection = isCteStarProjection(projection);
17140                        if (starProjection && exposedColumns != null && exposedColumns.size() > 0) {
17141                                appendCteResolutionWarning(resolution, "modified column '" + modifiedColumn.getColumnNameOnly()
17142                                                + "' cannot be mapped safely through an explicitly named wildcard projection");
17143                                continue;
17144                        }
17145                        TObjectName projectedColumn = projection.getColumnFullname();
17146                        if (starProjection && projectedColumn != null
17147                                        && SQLUtil.isEmpty(projectedColumn.getTableString())
17148                                        && subquery.tables != null && subquery.tables.size() > 1) {
17149                                appendCteResolutionWarning(resolution,
17150                                                "an unqualified wildcard over multiple tables is ambiguous");
17151                                continue;
17152                        }
17153                        if (projectedColumn == null || projectedColumn.getColumnSource() == null) {
17154                                appendCteResolutionWarning(resolution, "modified column '" + modifiedColumn.getColumnNameOnly()
17155                                                + "' is calculated or has no resolvable base column");
17156                                continue;
17157                        }
17158
17159                        gudusoft.gsqlparser.resolver2.model.ColumnSource source = projectedColumn.getColumnSource();
17160                        if (!starProjection && SQLUtil.isEmpty(projectedColumn.getTableString())
17161                                        && isAmbiguousUnqualifiedProjection(subquery, projectedColumn)) {
17162                                appendCteResolutionWarning(resolution,
17163                                                "an unqualified projection over multiple tables is ambiguous");
17164                                continue;
17165                        }
17166                        if (source.isAmbiguous()) {
17167                                appendCteResolutionWarning(resolution, "modified column '" + modifiedColumn.getColumnNameOnly()
17168                                                + "' has more than one possible base table");
17169                                continue;
17170                        }
17171                        String projectedColumnName = starProjection
17172                                        ? modifiedColumn.getColumnNameOnly() : projectedColumn.getColumnNameOnly();
17173                        ProjectedColumnResolution projectedResolution = resolveProjectedColumn(
17174                                        getResolvedProjectedSourceTable(projectedColumn), projectedColumnName,
17175                                        new HashSet<TSelectSqlStatement>());
17176                        if (projectedResolution.failureReason != null) {
17177                                appendCteResolutionWarning(resolution, projectedResolution.failureReason);
17178                                continue;
17179                        }
17180                        TTable baseTable = projectedResolution.baseTable;
17181                        if (!isPhysicalCteWriteTable(baseTable)) {
17182                                appendCteResolutionWarning(resolution, "modified column '" + modifiedColumn.getColumnNameOnly()
17183                                                + "' does not resolve to a physical base table");
17184                                continue;
17185                        }
17186                        if (resolution.baseTable != null && resolution.baseTable != baseTable) {
17187                                appendCteResolutionWarning(resolution,
17188                                                "modified columns resolve to more than one base table");
17189                                resolution.baseTable = null;
17190                                resolution.columns.clear();
17191                                return resolution;
17192                        }
17193
17194                        String baseColumnName = projectedResolution.baseColumnName;
17195                        if (SQLUtil.isEmpty(baseColumnName)) {
17196                                baseColumnName = projectedColumn.getColumnNameOnly();
17197                        }
17198                        if (SQLUtil.isEmpty(baseColumnName)) {
17199                                appendCteResolutionWarning(resolution, "modified column '" + modifiedColumn.getColumnNameOnly()
17200                                                + "' has no resolvable physical column name");
17201                                continue;
17202                        }
17203
17204                        resolution.baseTable = baseTable;
17205                        resolution.columns.add(new CteTargetColumnMapping(modifiedColumn.getColumnNameOnly(), baseColumnName));
17206                }
17207                if (resolution.baseTable == null && resolution.failureReason == null) {
17208                        resolution.failureReason = "no modified column resolves safely to a physical base table";
17209                }
17210                return resolution;
17211        }
17212
17213        private boolean isAmbiguousUnqualifiedProjection(TSelectSqlStatement subquery,
17214                        TObjectName projectedColumn) {
17215                if (subquery.tables == null || subquery.tables.size() <= 1) {
17216                        return false;
17217                }
17218                String columnName = projectedColumn.getColumnNameOnly();
17219                int matches = 0;
17220                int unknowns = 0;
17221                TTable onlyUnknown = null;
17222                for (int i = 0; i < subquery.tables.size(); i++) {
17223                        TTable candidate = subquery.tables.getTable(i);
17224                        Boolean hasColumn = hasAuthoritativeColumn(candidate, columnName);
17225                        if (hasColumn == null) {
17226                                unknowns++;
17227                                onlyUnknown = candidate;
17228                                continue;
17229                        }
17230                        if (hasColumn.booleanValue() && ++matches > 1) {
17231                                return true;
17232                        }
17233                }
17234                if (matches == 1) {
17235                        return unknowns != 0;
17236                }
17237                return unknowns != 1 || getResolvedProjectedSourceTable(projectedColumn) != onlyUnknown;
17238        }
17239
17240        private TTable getResolvedProjectedSourceTable(TObjectName column) {
17241                if (column == null) {
17242                        return null;
17243                }
17244                TTable sourceTable = column.getSourceTable();
17245                if (sourceTable != null || column.getColumnSource() == null
17246                                || column.getColumnSource().getSourceNamespace() == null) {
17247                        return sourceTable;
17248                }
17249                return column.getColumnSource().getSourceNamespace().getSourceTable();
17250        }
17251
17252        private Boolean hasAuthoritativeColumn(TTable table, String columnName) {
17253                if (table == null || SQLUtil.isEmpty(columnName)) {
17254                        return null;
17255                }
17256                if (!isPhysicalCteWriteTable(table)) {
17257                        TSelectSqlStatement subquery = null;
17258                        TObjectNameList exposedColumns = null;
17259                        if (table.getCTE() != null) {
17260                                subquery = table.getCTE().getSubquery();
17261                                exposedColumns = table.getCTE().getColumnList();
17262                        } else if (table.getSubquery() != null) {
17263                                subquery = table.getSubquery();
17264                                exposedColumns = getProjectedTargetColumns(table);
17265                        } else if (table.getLinkTable() != null
17266                                        && table.getLinkTable().getSubquery() != null) {
17267                                subquery = table.getLinkTable().getSubquery();
17268                                exposedColumns = getProjectedTargetColumns(table.getLinkTable());
17269                        }
17270                        TResultColumnList projections = subquery == null
17271                                        ? null : subquery.getResultColumnList();
17272                        if (projections == null || projections.size() == 0
17273                                        || (exposedColumns != null && exposedColumns.size() > 0
17274                                                        && exposedColumns.size() != projections.size())) {
17275                                return null;
17276                        }
17277                        boolean matched = false;
17278                        for (int i = 0; i < projections.size(); i++) {
17279                                TResultColumn projection = projections.getResultColumn(i);
17280                                if (isCteStarProjection(projection)) {
17281                                        return null;
17282                                }
17283                                String exposedName = getProjectedColumnName(exposedColumns, projection, i);
17284                                if (SQLUtil.isEmpty(exposedName)) {
17285                                        return null;
17286                                }
17287                                if (SQLUtil.sameName(option.getVendor(), ESQLDataObjectType.dotColumn,
17288                                                columnName, exposedName)) {
17289                                        matched = true;
17290                                }
17291                        }
17292                        return Boolean.valueOf(matched);
17293                }
17294                TColumnDefinitionList definitions = table.getColumnDefinitions();
17295                if (definitions != null && definitions.size() > 0) {
17296                        for (int i = 0; i < definitions.size(); i++) {
17297                                TObjectName name = definitions.getColumn(i).getColumnName();
17298                                if (name != null && SQLUtil.sameName(option.getVendor(), ESQLDataObjectType.dotColumn,
17299                                                columnName, name.getColumnNameOnly())) {
17300                                        return Boolean.TRUE;
17301                                }
17302                        }
17303                        return Boolean.FALSE;
17304                }
17305                if (sqlenv == null || table.getTableName() == null) {
17306                        return null;
17307                }
17308                TSQLTable sqlTable = sqlenv.searchTable(table.getTableName());
17309                if (sqlTable == null || sqlTable.getColumnList() == null
17310                                || sqlTable.getColumnList().isEmpty()) {
17311                        return null;
17312                }
17313                for (TSQLColumn column : sqlTable.getColumnList()) {
17314                        if (sqlenv.getIdentifierService().canonKeyOf(columnName, ESQLDataObjectType.dotColumn,
17315                    gudusoft.gsqlparser.sqlenv.IdentifierInputForm.SQL_LEXICAL).equals(
17316                    sqlenv.getIdentifierService().canonKeyOf(column.getName(), ESQLDataObjectType.dotColumn,
17317                    gudusoft.gsqlparser.sqlenv.IdentifierInputForm.CATALOG_STORED))) {
17318                                return Boolean.TRUE;
17319                        }
17320                }
17321                return Boolean.FALSE;
17322        }
17323
17324        private ProjectedColumnResolution resolveProjectedColumn(TTable sourceTable, String columnName,
17325                        Set<TSelectSqlStatement> visited) {
17326                ProjectedColumnResolution resolution = new ProjectedColumnResolution();
17327                if (sourceTable == null) {
17328                        resolution.failureReason = "a projected column has no uniquely resolved source table";
17329                        return resolution;
17330                }
17331                if (isPhysicalCteWriteTable(sourceTable)) {
17332                        resolution.baseTable = sourceTable;
17333                        resolution.baseColumnName = columnName;
17334                        return resolution;
17335                }
17336                TSelectSqlStatement subquery = null;
17337                TObjectNameList exposedColumns = null;
17338                if (sourceTable.getCTE() != null) {
17339                        subquery = sourceTable.getCTE().getSubquery();
17340                        exposedColumns = sourceTable.getCTE().getColumnList();
17341                } else if (sourceTable.getSubquery() != null) {
17342                        subquery = sourceTable.getSubquery();
17343                        exposedColumns = getProjectedTargetColumns(sourceTable);
17344                } else if (sourceTable.getLinkTable() != null
17345                                && sourceTable.getLinkTable().getSubquery() != null) {
17346                        subquery = sourceTable.getLinkTable().getSubquery();
17347                        exposedColumns = getProjectedTargetColumns(sourceTable.getLinkTable());
17348                }
17349                if (subquery == null || !visited.add(subquery)) {
17350                        resolution.failureReason = subquery == null
17351                                        ? "an intermediate projection has no SELECT body"
17352                                        : "the projected target contains a cyclic intermediate query";
17353                        return resolution;
17354                }
17355                if (subquery.isCombinedQuery() || subquery.getSetOperatorType() != ESetOperatorType.none) {
17356                        resolution.failureReason = "an intermediate projection with a set operator is not writable";
17357                        return resolution;
17358                }
17359                if (subquery.getSelectDistinct() != null && !subquery.getSelectDistinct().isAll()) {
17360                        resolution.failureReason = "an intermediate DISTINCT projection is not writable";
17361                        return resolution;
17362                }
17363                if (hasCteAggregate(subquery)) {
17364                        resolution.failureReason = "an intermediate grouped or aggregate projection is not writable";
17365                        return resolution;
17366                }
17367                TResultColumnList projections = subquery.getResultColumnList();
17368                if (projections == null || projections.size() == 0 || SQLUtil.isEmpty(columnName)) {
17369                        resolution.failureReason = "an intermediate projection has no mappable target column";
17370                        return resolution;
17371                }
17372                TObjectName exposedColumn = new TObjectName();
17373                TSourceToken columnToken = new TSourceToken(columnName);
17374                exposedColumn.setPartToken(columnToken);
17375                exposedColumn.setObjectType(TObjectName.ttobjColumn);
17376                int projectionIndex = findCteProjectionIndex(exposedColumns, exposedColumn, projections);
17377                if (projectionIndex < 0) {
17378                        resolution.failureReason = "modified column '" + columnName
17379                                        + "' does not map uniquely through an intermediate projection";
17380                        return resolution;
17381                }
17382                TResultColumn projection = projections.getResultColumn(projectionIndex);
17383                boolean starProjection = isCteStarProjection(projection);
17384                if (starProjection && exposedColumns != null && exposedColumns.size() > 0) {
17385                        resolution.failureReason = "modified column '" + columnName
17386                                        + "' cannot be mapped safely through an explicitly named intermediate wildcard projection";
17387                        return resolution;
17388                }
17389                TObjectName innerColumn = projection.getColumnFullname();
17390                if (innerColumn == null || innerColumn.getColumnSource() == null) {
17391                        resolution.failureReason = "modified column '" + columnName
17392                                        + "' has no resolvable intermediate base column";
17393                        return resolution;
17394                }
17395                if (starProjection && SQLUtil.isEmpty(innerColumn.getTableString())
17396                                && subquery.tables != null && subquery.tables.size() > 1) {
17397                        resolution.failureReason = "an unqualified intermediate wildcard over multiple tables is ambiguous";
17398                        return resolution;
17399                }
17400                gudusoft.gsqlparser.resolver2.model.ColumnSource source = innerColumn.getColumnSource();
17401                if (!starProjection && SQLUtil.isEmpty(innerColumn.getTableString())
17402                                && isAmbiguousUnqualifiedProjection(subquery, innerColumn)) {
17403                        resolution.failureReason = "an unqualified intermediate projection over multiple tables is ambiguous";
17404                        return resolution;
17405                }
17406                if (source.isAmbiguous()) {
17407                        resolution.failureReason = "modified column '" + columnName
17408                                        + "' has more than one possible intermediate base table";
17409                        return resolution;
17410                }
17411                String innerName = starProjection ? columnName : innerColumn.getColumnNameOnly();
17412                return resolveProjectedColumn(getResolvedProjectedSourceTable(innerColumn), innerName, visited);
17413        }
17414
17415        private void appendCteResolutionWarning(CteWriteTargetResolution resolution, String reason) {
17416                if (resolution.failureReason == null) {
17417                        resolution.failureReason = reason;
17418                } else if (!resolution.failureReason.contains(reason)) {
17419                        resolution.failureReason += "; " + reason;
17420                }
17421        }
17422
17423        private boolean hasCteAggregate(TSelectSqlStatement subquery) {
17424                if (subquery.getGroupByClause() != null) {
17425                        return true;
17426                }
17427                TResultColumnList projections = subquery.getResultColumnList();
17428                if (projections == null) {
17429                        return false;
17430                }
17431                for (int i = 0; i < projections.size(); i++) {
17432                        TExpression expression = projections.getResultColumn(i).getExpr();
17433                        if (expression == null) {
17434                                continue;
17435                        }
17436                        columnsInExpr visitor = new columnsInExpr();
17437                        expression.inOrderTraverse(visitor);
17438                        List<TParseTreeNode> functions = visitor.getFunctions();
17439                        if (functions == null) {
17440                                continue;
17441                        }
17442                        for (TParseTreeNode function : functions) {
17443                                if (function instanceof TFunctionCall
17444                                                && ((TFunctionCall) function).getWindowDef() == null
17445                                                && isAggregateFunction((TFunctionCall) function)) {
17446                                        return true;
17447                                }
17448                        }
17449                }
17450                return false;
17451        }
17452
17453        private int findCteProjectionIndex(TObjectNameList exposedColumns, TObjectName modifiedColumn,
17454                        TResultColumnList projections) {
17455                int match = -1;
17456                int starMatch = -1;
17457                for (int i = 0; i < projections.size(); i++) {
17458                        TResultColumn projection = projections.getResultColumn(i);
17459                        String exposedName;
17460                        if (exposedColumns != null && exposedColumns.size() > 0) {
17461                                if (i >= exposedColumns.size()) {
17462                                        continue;
17463                                }
17464                                exposedName = getProjectedColumnName(exposedColumns, projection, i);
17465                        } else {
17466                                exposedName = SQLUtil.isEmpty(projection.getColumnAlias())
17467                                                ? projection.getDisplayName() : projection.getColumnAlias();
17468                                if (isCteStarProjection(projection)) {
17469                                        if (starMatch == -1) {
17470                                                starMatch = i;
17471                                        } else {
17472                                                starMatch = -2;
17473                                        }
17474                                }
17475                        }
17476                        if (SQLUtil.sameName(option.getVendor(), ESQLDataObjectType.dotColumn,
17477                                        modifiedColumn.getColumnNameOnly(), exposedName)) {
17478                                if (match >= 0) {
17479                                        return -1;
17480                                }
17481                                match = i;
17482                        }
17483                }
17484                return match >= 0 ? match : starMatch;
17485        }
17486
17487        private String getProjectedColumnName(TObjectNameList exposedColumns,
17488                        TResultColumn projection, int index) {
17489                if (exposedColumns != null && exposedColumns.size() > 0) {
17490                        if (index >= exposedColumns.size()) {
17491                                return null;
17492                        }
17493                        TObjectName exposedColumn = exposedColumns.getObjectName(index);
17494                        String exposedName = exposedColumn.getColumnNameOnly();
17495                        return SQLUtil.isEmpty(exposedName) ? exposedColumn.toString() : exposedName;
17496                }
17497                return SQLUtil.isEmpty(projection.getColumnAlias())
17498                                ? projection.getDisplayName() : projection.getColumnAlias();
17499        }
17500
17501        private boolean isCteStarProjection(TResultColumn projection) {
17502                if (projection == null || projection.getExpr() == null
17503                                || projection.getExpr().getExpressionType() != EExpressionType.simple_object_name_t
17504                                || projection.getExpr().getObjectOperand() == null) {
17505                        return false;
17506                }
17507                return DlineageUtil.isWildcard(projection.getExpr().getObjectOperand());
17508        }
17509
17510        private boolean isPhysicalCteWriteTable(TTable table) {
17511                return table != null && table.getTableName() != null && table.getCTE() == null
17512                                && table.getSubquery() == null
17513                                && (table.getLinkTable() == null || table.getLinkTable().getSubquery() == null);
17514        }
17515
17516        private CteWriteTargetResolution resolveCteDeleteTarget(TSelectSqlStatement subquery) {
17517                return resolveCteDeleteTarget(subquery, new HashSet<TSelectSqlStatement>());
17518        }
17519
17520        private CteWriteTargetResolution resolveCteDeleteTarget(TSelectSqlStatement subquery,
17521                        Set<TSelectSqlStatement> visited) {
17522                CteWriteTargetResolution resolution = new CteWriteTargetResolution();
17523                if (subquery == null || !visited.add(subquery)) {
17524                        resolution.failureReason = "the target CTE has no acyclic SELECT body";
17525                        return resolution;
17526                }
17527                if (subquery.isCombinedQuery() || subquery.getSetOperatorType() != ESetOperatorType.none) {
17528                        resolution.failureReason = "a CTE with a set operator is not a writable single-table projection";
17529                        return resolution;
17530                }
17531                if (subquery.getSelectDistinct() != null && !subquery.getSelectDistinct().isAll()) {
17532                        resolution.failureReason = "a DISTINCT CTE is not a writable single-table projection";
17533                        return resolution;
17534                }
17535                if (hasCteAggregate(subquery)) {
17536                        resolution.failureReason = "a grouped or aggregate CTE is not a writable single-table projection";
17537                        return resolution;
17538                }
17539                if (subquery.tables == null || subquery.tables.size() != 1) {
17540                        resolution.failureReason = "a DELETE target CTE resolves to more than one base table";
17541                        return resolution;
17542                }
17543                TTable table = subquery.tables.getTable(0);
17544                if (isPhysicalCteWriteTable(table)) {
17545                        resolution.baseTable = table;
17546                        return resolution;
17547                }
17548                if (table.getCTE() != null) {
17549                        return resolveCteDeleteTarget(table.getCTE().getSubquery(), visited);
17550                }
17551                if (table.getSubquery() != null) {
17552                        return resolveCteDeleteTarget(table.getSubquery(), visited);
17553                }
17554                if (table.getLinkTable() != null && table.getLinkTable().getSubquery() != null) {
17555                        return resolveCteDeleteTarget(table.getLinkTable().getSubquery(), visited);
17556                }
17557                resolution.failureReason = "the DELETE target CTE does not resolve to a physical base table";
17558                return resolution;
17559        }
17560
17561        private TObjectNameList getProjectedTargetColumns(TTable targetTable) {
17562                return targetTable.getAliasClause() == null
17563                                ? null : targetTable.getAliasClause().getColumns();
17564        }
17565
17566        private TObjectName getCteBaseColumn(CteWriteTargetResolution resolution, TObjectName targetColumn) {
17567                if (resolution == null) {
17568                        return targetColumn;
17569                }
17570                if (!resolution.isWritable() || targetColumn == null) {
17571                        return null;
17572                }
17573                for (CteTargetColumnMapping mapping : resolution.columns) {
17574                        if (SQLUtil.sameName(option.getVendor(), ESQLDataObjectType.dotColumn,
17575                                        targetColumn.getColumnNameOnly(), mapping.exposedName)) {
17576                                TObjectName baseColumn = new TObjectName();
17577                                TSourceToken nameToken = new TSourceToken(mapping.baseColumnName);
17578                                baseColumn.setPartToken(nameToken);
17579                                baseColumn.setObjectType(TObjectName.ttobjColumn);
17580                                baseColumn.setSourceTable(resolution.baseTable);
17581                                baseColumn.setStartToken(targetColumn.getStartToken() == null
17582                                                ? nameToken : targetColumn.getStartToken());
17583                                baseColumn.setEndToken(targetColumn.getEndToken() == null
17584                                                ? nameToken : targetColumn.getEndToken());
17585                                return baseColumn;
17586                        }
17587                }
17588                return null;
17589        }
17590
17591        private QueryTable analyzeCteWriteTarget(TTable targetTable, Process process) {
17592                return analyzeProjectedWriteTarget(targetTable, targetTable.getCTE().getSubquery(),
17593                                targetTable.getCTE().getColumnList(), process);
17594        }
17595
17596        private QueryTable analyzeProjectedWriteTarget(TTable targetTable, TSelectSqlStatement subquery,
17597                        TObjectNameList exposedColumns, Process process) {
17598                QueryTable queryTable = modelFactory.createQueryTable(targetTable);
17599                if (exposedColumns != null) {
17600                        for (int i = 0; i < exposedColumns.size(); i++) {
17601                                modelFactory.createResultColumn(queryTable, exposedColumns.getObjectName(i));
17602                        }
17603                }
17604
17605                if (subquery == null || stmtStack.contains(subquery)) {
17606                        return queryTable;
17607                }
17608                analyzeSelectStmt(subquery);
17609                ResultSet resultSetModel = (ResultSet) modelManager.getModel(subquery);
17610                if (resultSetModel == null || resultSetModel == queryTable) {
17611                        return queryTable;
17612                }
17613
17614                if (!resultSetModel.getRelationRows().getHoldRelations().isEmpty()) {
17615                        ImpactRelationship impactRelation = modelFactory.createImpactRelation();
17616                        impactRelation.setEffectType(EffectType.select);
17617                        impactRelation.addSource(new RelationRowsRelationshipElement<ResultSetRelationRows>(
17618                                        resultSetModel.getRelationRows()));
17619                        impactRelation.setTarget(new RelationRowsRelationshipElement<ResultSetRelationRows>(
17620                                        queryTable.getRelationRows()));
17621                }
17622
17623                if (subquery.getSetOperatorType() != ESetOperatorType.none) {
17624                        SelectSetResultSet selectSetResultSetModel = (SelectSetResultSet) resultSetModel;
17625                        for (int i = 0; i < selectSetResultSetModel.getColumns().size(); i++) {
17626                                ResultColumn sourceColumn = selectSetResultSetModel.getColumns().get(i);
17627                                ResultColumn targetColumn = exposedColumns != null && i < queryTable.getColumns().size()
17628                                                ? queryTable.getColumns().get(i)
17629                                                : modelFactory.createSelectSetResultColumn(queryTable, sourceColumn);
17630                                for (TObjectName starLinkColumn : sourceColumn.getStarLinkColumnList()) {
17631                                        targetColumn.bindStarLinkColumn(starLinkColumn);
17632                                }
17633                                createCteSelectRelation(process, targetColumn, sourceColumn);
17634                        }
17635                } else {
17636                        for (int i = 0; i < resultSetModel.getColumns().size(); i++) {
17637                                ResultColumn sourceColumn = resultSetModel.getColumns().get(i);
17638                                ResultColumn targetColumn = exposedColumns != null && i < queryTable.getColumns().size()
17639                                                ? queryTable.getColumns().get(i)
17640                                                : modelFactory.createSelectSetResultColumn(queryTable, sourceColumn);
17641                                for (TObjectName starLinkColumn : sourceColumn.getStarLinkColumnList()) {
17642                                        targetColumn.bindStarLinkColumn(starLinkColumn);
17643                                }
17644                                createCteSelectRelation(process, targetColumn, sourceColumn);
17645                        }
17646                }
17647                return queryTable;
17648        }
17649
17650        private void createProjectedWriteImpact(TSelectSqlStatement subquery, Table tableModel,
17651                        EffectType effectType, Process process) {
17652                if (subquery == null || tableModel == null) {
17653                        return;
17654                }
17655                Object model = modelManager.getModel(subquery);
17656                if (!(model instanceof ResultSet)) {
17657                        return;
17658                }
17659                ResultSet resultSet = (ResultSet) model;
17660                if (resultSet.getRelationRows().getHoldRelations().isEmpty()) {
17661                        return;
17662                }
17663                ImpactRelationship impactRelation = modelFactory.createImpactRelation();
17664                impactRelation.setEffectType(effectType);
17665                impactRelation.addSource(new RelationRowsRelationshipElement<ResultSetRelationRows>(
17666                                resultSet.getRelationRows()));
17667                impactRelation.setTarget(new RelationRowsRelationshipElement<TableRelationRows>(
17668                                tableModel.getRelationRows()));
17669                impactRelation.setProcess(process);
17670        }
17671
17672        private void createCteSelectRelation(Process process, ResultColumn targetColumn, ResultColumn sourceColumn) {
17673                createCteSelectRelation(process, targetColumn, sourceColumn, false);
17674        }
17675
17676        /** {@code positional}: the target is a position of a column list, which reads a star whole ({@link #positionSource}). */
17677        private void createCteSelectRelation(Process process, ResultColumn targetColumn, ResultColumn sourceColumn,
17678                        boolean positional) {
17679                if (!cteSelectRelationKeys.add(new CteSelectRelationKey(targetColumn, sourceColumn))) {
17680                        return;
17681                }
17682                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
17683                relation.setEffectType(EffectType.select);
17684                relation.setTarget(new ResultColumnRelationshipElement(targetColumn));
17685                relation.addSource(positional ? positionSource(sourceColumn) : new ResultColumnRelationshipElement(sourceColumn));
17686                relation.setProcess(process);
17687        }
17688
17689        private void addCteWriteTargetHint(TTable targetTable, String reason) {
17690                addCteWriteTargetHint(targetTable, reason, false);
17691        }
17692
17693        private void addCteWriteTargetHint(TTable targetTable, String reason, boolean partiallyResolved) {
17694                ErrorInfo errorInfo = new ErrorInfo();
17695                errorInfo.setErrorType(ErrorInfo.SYNTAX_HINT);
17696                String targetKind = targetTable.getCTE() == null ? "Projected write target" : "CTE write target";
17697                String targetName = targetTable.getTableName() == null
17698                                ? targetTable.getAliasName() : targetTable.getTableName().toString();
17699                errorInfo.setErrorMessage(targetKind + " '" + targetName + "' "
17700                                + (partiallyResolved ? "was only partially resolved: " : "was kept as a result set: ")
17701                                + reason + ".");
17702                if (targetTable.getStartToken() != null && targetTable.getEndToken() != null) {
17703                        errorInfo.setStartPosition(new Pair3<Long, Long, String>(targetTable.getStartToken().lineNo,
17704                                        targetTable.getStartToken().columnNo, ModelBindingManager.getGlobalHash()));
17705                        errorInfo.setEndPosition(new Pair3<Long, Long, String>(targetTable.getEndToken().lineNo,
17706                                        targetTable.getEndToken().columnNo
17707                                                        + targetTable.getEndToken().getAstext().length(), ModelBindingManager.getGlobalHash()));
17708                }
17709                errorInfo.fillInfo(this);
17710                errorInfos.add(errorInfo);
17711        }
17712
17713        private void analyzeMergeStmt(TMergeSqlStatement stmt) {
17714                Object tableModel;
17715                CteWriteTargetResolution cteTargetResolution = null;
17716                // the source result set whose rows a branch below already carried to the target
17717                ResultSet sourceRowsLinked = null;
17718                // the relations made for the ON and WHEN conditions
17719                List<ImpactRelationship> conditionRelations = new ArrayList<ImpactRelationship>();
17720                // the result sets of the UPDATE and INSERT actions, with their effect
17721                List<Pair<ResultSet, EffectType>> actionRows = new ArrayList<Pair<ResultSet, EffectType>>();
17722                if (stmt.getUsingTable() != null) {
17723                        TTable table = stmt.getTargetTable();
17724                        Process process = modelFactory.createProcess(stmt);
17725                        if (table.getCTE() != null) {
17726                                QueryTable cteTargetModel = analyzeCteWriteTarget(table, process);
17727                                cteTargetResolution = resolveCteWriteTarget(table, collectMergeCteWriteIntent(stmt, table));
17728                                if (cteTargetResolution.isWritable()) {
17729                                        tableModel = modelFactory.createTable(cteTargetResolution.baseTable);
17730                                        ((Table) tableModel).addProcess(process);
17731                                        createProjectedWriteImpact(table.getCTE().getSubquery(), (Table) tableModel,
17732                                                        EffectType.merge, process);
17733                                } else {
17734                                        tableModel = cteTargetModel;
17735                                }
17736                                if (cteTargetResolution.failureReason != null) {
17737                                        addCteWriteTargetHint(table, cteTargetResolution.failureReason,
17738                                                        cteTargetResolution.isWritable());
17739                                }
17740                        } else if(table.getSubquery()!=null) {
17741                                TObjectNameList exposedColumns = getProjectedTargetColumns(table);
17742                                QueryTable projectedTargetModel = analyzeProjectedWriteTarget(table,
17743                                                table.getSubquery(), exposedColumns, process);
17744                                cteTargetResolution = resolveProjectedWriteTarget(table.getSubquery(), exposedColumns,
17745                                                collectMergeCteWriteIntent(stmt, table));
17746                                if (cteTargetResolution.isWritable()) {
17747                                        tableModel = modelFactory.createTable(cteTargetResolution.baseTable);
17748                                        ((Table) tableModel).addProcess(process);
17749                                        createProjectedWriteImpact(table.getSubquery(), (Table) tableModel,
17750                                                        EffectType.merge, process);
17751                                } else {
17752                                        tableModel = projectedTargetModel;
17753                                }
17754                                if (cteTargetResolution.failureReason != null) {
17755                                        addCteWriteTargetHint(table, cteTargetResolution.failureReason,
17756                                                        cteTargetResolution.isWritable());
17757                                }
17758                        }
17759                        else {
17760                                tableModel = modelFactory.createTable(table);
17761                                ((Table)tableModel).addProcess(process);
17762                        }
17763                        
17764                        for(TTable item: stmt.tables) {
17765                                // Skip subqueries and CTE references: a CTE feeding USING is resolved
17766                                // through its subquery (see the getCTE() branch below), so materializing
17767                                // it here as a physical base table leaves a spurious table named after
17768                                // the CTE in the lineage output. (MantisBT #4493)
17769                                if(item.getSubquery()!=null || item.getCTE()!=null) {
17770                                        continue;
17771                                }
17772                                Table tableItemModel = modelFactory.createTable(item);                          
17773                                if (tableItemModel.getColumns() == null || tableItemModel.getColumns().isEmpty()) {
17774                                        tableItemModel.addColumnsFromSQLEnv();
17775                                }
17776                        }
17777
17778                        if (stmt.getUsingTable().getSubquery() != null) {
17779                                QueryTable queryTable = modelFactory.createQueryTable(stmt.getUsingTable());
17780                                analyzeSelectStmt(stmt.getUsingTable().getSubquery());
17781
17782                                ResultSet resultSetModel = (ResultSet) modelManager.getModel(stmt.getUsingTable().getSubquery());
17783                                
17784                                if (queryTable != null && resultSetModel != null && queryTable != resultSetModel) {
17785                                        if (queryTable.getColumns().size() == resultSetModel.getColumns().size()) {
17786                                                for (int i = 0; i < queryTable.getColumns().size(); i++) {
17787                                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
17788                                                        relation.setEffectType(EffectType.select);
17789                                                        relation.setTarget(new ResultColumnRelationshipElement(queryTable.getColumns().get(i)));
17790                                                        relation.addSource(new ResultColumnRelationshipElement(resultSetModel.getColumns().get(i)));
17791                                                        relation.setProcess(process);
17792                                                }
17793                                        }
17794                                }
17795                                
17796                                if (resultSetModel != null && !resultSetModel.getRelationRows().getHoldRelations().isEmpty()) {
17797                                        ImpactRelationship impactRelation = modelFactory.createImpactRelation();
17798                                        impactRelation.setEffectType(EffectType.merge);
17799                                        impactRelation.addSource(new RelationRowsRelationshipElement<ResultSetRelationRows>(
17800                                                        resultSetModel.getRelationRows()));
17801                                        if (tableModel instanceof Table) {
17802                                                impactRelation.setTarget(
17803                                                                new RelationRowsRelationshipElement<TableRelationRows>(((Table)tableModel).getRelationRows()));
17804                                        }
17805                                        else {
17806                                                impactRelation.setTarget(
17807                                                                new RelationRowsRelationshipElement<ResultSetRelationRows>(((ResultSet)tableModel).getRelationRows()));
17808                                        }
17809                                        sourceRowsLinked = resultSetModel;
17810                                }
17811                        } else if (stmt.getUsingTable().getCTE() != null) {
17812                                // USING references a CTE defined in the WITH clause attached to the
17813                                // MERGE. Mirror the SELECT-side CTE handling so the CTE subquery (and
17814                                // the base tables inside it) are analyzed and linked through to the
17815                                // merge source; otherwise the CTE is treated as an opaque base table
17816                                // and the tables feeding it are dropped from the lineage. (MantisBT #4493)
17817                                QueryTable queryTable = modelFactory.createQueryTable(stmt.getUsingTable());
17818
17819                                TObjectNameList cteColumns = stmt.getUsingTable().getCTE().getColumnList();
17820                                if (cteColumns != null) {
17821                                        for (int j = 0; j < cteColumns.size(); j++) {
17822                                                modelFactory.createResultColumn(queryTable, cteColumns.getObjectName(j));
17823                                        }
17824                                }
17825
17826                                TSelectSqlStatement subquery = stmt.getUsingTable().getCTE().getSubquery();
17827                                if (subquery != null && !stmtStack.contains(subquery)) {
17828                                        analyzeSelectStmt(subquery);
17829
17830                                        ResultSet resultSetModel = (ResultSet) modelManager.getModel(subquery);
17831
17832                                        if (resultSetModel != null && resultSetModel != queryTable) {
17833                                                if (subquery.getSetOperatorType() != ESetOperatorType.none) {
17834                                                        SelectSetResultSet selectSetResultSetModel = (SelectSetResultSet) modelManager
17835                                                                        .getModel(subquery);
17836                                                        for (int j = 0; j < selectSetResultSetModel.getColumns().size(); j++) {
17837                                                                ResultColumn sourceColumn = selectSetResultSetModel.getColumns().get(j);
17838                                                                ResultColumn targetColumn = null;
17839                                                                if (cteColumns != null && j < queryTable.getColumns().size()) {
17840                                                                        targetColumn = queryTable.getColumns().get(j);
17841                                                                } else {
17842                                                                        targetColumn = modelFactory.createSelectSetResultColumn(queryTable, sourceColumn);
17843                                                                }
17844                                                                for (TObjectName starLinkColumn : sourceColumn.getStarLinkColumnList()) {
17845                                                                        targetColumn.bindStarLinkColumn(starLinkColumn);
17846                                                                }
17847                                                                createCteSelectRelation(process, targetColumn, sourceColumn);
17848                                                        }
17849                                                } else {
17850                                                        for (int j = 0; j < resultSetModel.getColumns().size(); j++) {
17851                                                                ResultColumn sourceColumn = resultSetModel.getColumns().get(j);
17852                                                                ResultColumn targetColumn = null;
17853                                                                if (cteColumns != null && j < queryTable.getColumns().size()) {
17854                                                                        targetColumn = queryTable.getColumns().get(j);
17855                                                                } else {
17856                                                                        targetColumn = modelFactory.createSelectSetResultColumn(queryTable, sourceColumn);
17857                                                                }
17858                                                                for (TObjectName starLinkColumn : sourceColumn.getStarLinkColumnList()) {
17859                                                                        targetColumn.bindStarLinkColumn(starLinkColumn);
17860                                                                }
17861                                                                createCteSelectRelation(process, targetColumn, sourceColumn);
17862                                                        }
17863                                                }
17864                                        }
17865
17866                                        if (resultSetModel != null && !resultSetModel.getRelationRows().getHoldRelations().isEmpty()) {
17867                                                ImpactRelationship impactRelation = modelFactory.createImpactRelation();
17868                                                impactRelation.setEffectType(EffectType.merge);
17869                                                impactRelation.addSource(new RelationRowsRelationshipElement<ResultSetRelationRows>(
17870                                                                resultSetModel.getRelationRows()));
17871                                                if (tableModel instanceof Table) {
17872                                                        impactRelation.setTarget(
17873                                                                        new RelationRowsRelationshipElement<TableRelationRows>(((Table) tableModel).getRelationRows()));
17874                                                } else {
17875                                                        impactRelation.setTarget(
17876                                                                        new RelationRowsRelationshipElement<ResultSetRelationRows>(((ResultSet) tableModel).getRelationRows()));
17877                                                }
17878                                                sourceRowsLinked = resultSetModel;
17879                                        }
17880                                }
17881                        } else {
17882                                if (stmt.getUsingTable().getAliasClause() != null && stmt.getUsingTable().getAliasClause().getColumns() != null && stmt.getUsingTable().getValueClause() != null && stmt.getUsingTable().getValueClause().getRows()!=null) {
17883                                        Table usingTable = modelFactory.createTableFromCreateDDL(stmt.getUsingTable(), false, stmt.getUsingTable().getAliasName() + stmt.getUsingTable().getTableName());
17884                                        usingTable.setCreateTable(true);
17885                                        usingTable.setSubType(SubType.function);
17886                                        for (int z=0;z<stmt.getUsingTable().getAliasClause().getColumns().size();z++) {
17887                                                TObjectName columnName = stmt.getUsingTable().getAliasClause().getColumns().getObjectName(z);
17888                                                TableColumn tableColumn = modelFactory.createTableColumn(usingTable, columnName, true);
17889                                                TResultColumn resultColumn = stmt.getUsingTable().getValueClause().getRows().get(0).getResultColumn(z);
17890                                                modelManager.bindModel(resultColumn, tableColumn);
17891                                                analyzeResultColumnExpressionRelation(tableColumn, resultColumn.getExpr());                                             }
17892                                }
17893                                else {
17894                                        Table sourceTable = modelFactory.createTable(stmt.getUsingTable());
17895                                        // A source table whose rows were picked by a filter or a join (#t filled by
17896                                        // SELECT ... INTO ... WHERE) passes that on to the target, as a filtered subquery
17897                                        // source does above (MantisBT #4758)
17898                                        if (sourceTable != tableModel && !sourceTable.getRelationRows().getHoldRelations().isEmpty()) {
17899                                                createMergeRowsImpact(
17900                                                                new RelationRowsRelationshipElement<TableRelationRows>(sourceTable.getRelationRows()),
17901                                                                tableModel);
17902                                        }
17903                                }
17904                        }
17905
17906
17907                        if (stmt.getWhenClauses() != null && stmt.getWhenClauses().size() > 0) {
17908                                for (int i = 0; i < stmt.getWhenClauses().size(); i++) {
17909                                        TMergeWhenClause clause = stmt.getWhenClauses().getElement(i);
17910                                        if (clause.getCondition() != null) {
17911                                                conditionRelations.add(
17912                                                                analyzeFilterCondition(null, clause.getCondition(), null, null, EffectType.merge_when));
17913                                        }
17914                                        // the predicates of the action pick its rows as a WHEN condition does: Oracle's
17915                                        // UPDATE ... WHERE, DELETE WHERE and INSERT ... WHERE, and a DELETE's own condition
17916                                        for (TExpression predicate : mergeActionPredicates(clause)) {
17917                                                conditionRelations.add(analyzeFilterCondition(null, predicate, null, null, EffectType.merge_when));
17918                                        }
17919                                        if (clause.getUpdateClause() != null) {
17920                                                TResultColumnList columns = clause.getUpdateClause().getUpdateColumnList();
17921                                                if (columns == null || columns.size() == 0)
17922                                                        continue;
17923
17924                                                ResultSet resultSet = modelFactory.createResultSet(clause.getUpdateClause(), false);
17925                                                createPseudoImpactRelation(stmt, resultSet, EffectType.merge_update);
17926                                                actionRows.add(new Pair<ResultSet, EffectType>(resultSet, EffectType.merge_update));
17927
17928                                                for (int j = 0; j < columns.size(); j++) {
17929                                                        TExpression assignment = columns.getResultColumn(j).getExpr();
17930                                                        TExpression assignmentTarget = updateSetTarget(assignment.getLeftOperand());
17931                                                        if (assignmentTarget != null
17932                                                                        && assignmentTarget.getExpressionType() == EExpressionType.simple_object_name_t) {
17933                                                                analyzeMergeUpdateColumn(assignmentTarget.getObjectOperand(), assignment.getRightOperand(),
17934                                                                                resultSet, tableModel, cteTargetResolution, process);
17935                                                        } else if (assignmentTarget != null
17936                                                                        && assignmentTarget.getExpressionType() == EExpressionType.list_t) {
17937                                                                analyzeMergeUpdateColumnList(assignment, assignmentTarget, resultSet, tableModel,
17938                                                                                cteTargetResolution, process);
17939                                                        } else {
17940                                                                addUnanalyzedSetAssignmentError("Unsupported UPDATE SET target", assignmentTarget, assignment);
17941                                                        }
17942                                                }
17943                                        }
17944                                        if (clause.getInsertClause() != null) {
17945                                                TExpression insertValue = clause.getInsertClause().getInsertValue();
17946                                                if (insertValue != null
17947                                                                && insertValue.getExpressionType() == EExpressionType.objectConstruct_t) {
17948                                                        ResultSet resultSet = modelFactory.createResultSet(clause.getInsertClause(), false);
17949
17950                                                        createPseudoImpactRelation(stmt, resultSet, EffectType.merge_insert);
17951                                                        actionRows.add(new Pair<ResultSet, EffectType>(resultSet, EffectType.merge_insert));
17952
17953                                                        TObjectConstruct objectConstruct = insertValue.getObjectConstruct();
17954                                                        for (int z = 0; z < objectConstruct.getPairs().size(); z++) {
17955                                                                TPair pair = objectConstruct.getPairs().getElement(z);
17956
17957                                                                if (pair.getKeyName().getExpressionType() == EExpressionType.simple_constant_t) {
17958                                                                        TObjectName columnObject = new TObjectName();
17959                                                                        TConstant constant = pair.getKeyName().getConstantOperand();
17960                                                                        TSourceToken newSt = new TSourceToken(
17961                                                                                        constant.getValueToken().getTextWithoutQuoted());
17962                                                                        columnObject.setPartToken(newSt);
17963                                                                        columnObject.setSourceTable(stmt.getTargetTable());
17964                                                                        columnObject.setStartToken(constant.getStartToken());
17965                                                                        columnObject.setEndToken(constant.getEndToken());
17966
17967                                                                        ResultColumn insertColumn = modelFactory.createMergeResultColumn(resultSet,
17968                                                                                        columnObject);
17969
17970                                                                        TExpression valueExpression = pair.getKeyValue();
17971                                                                        if (valueExpression == null)
17972                                                                                continue;
17973
17974                                                                        columnsInExpr visitor = new columnsInExpr();
17975                                                                        valueExpression.inOrderTraverse(visitor);
17976                                                                        List<TObjectName> objectNames = visitor.getObjectNames();
17977                                                                        List<TParseTreeNode> functions = visitor.getFunctions();
17978
17979                                                                        if (functions != null && !functions.isEmpty()) {
17980                                                                                analyzeFunctionDataFlowRelation(insertColumn, functions,
17981                                                                                                EffectType.merge_insert);
17982                                                                        }
17983
17984                                                                        List<TSelectSqlStatement> subquerys = visitor.getSubquerys();
17985                                                                        if (subquerys != null && !subquerys.isEmpty()) {
17986                                                                                analyzeSubqueryDataFlowRelation(insertColumn, subquerys,
17987                                                                                                EffectType.merge_insert);
17988                                                                        }
17989
17990                                                                        analyzeDataFlowRelation(insertColumn, objectNames, EffectType.merge_insert,
17991                                                                                        functions);
17992
17993                                                                        List<TParseTreeNode> constants = visitor.getConstants();
17994                                                                        analyzeConstantDataFlowRelation(insertColumn, constants, EffectType.merge_insert,
17995                                                                                        functions);
17996
17997                                                                        TObjectName targetColumn = getCteBaseColumn(cteTargetResolution, columnObject);
17998                                                                        TableColumn tableColumn = !(tableModel instanceof Table) || targetColumn == null ? null
17999                                                                                        : modelFactory.createTableColumn((Table)tableModel, targetColumn, false);
18000
18001                                                                        if (tableColumn != null) {
18002                                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
18003                                                                                relation.setEffectType(EffectType.merge_insert);
18004                                                                                relation.setTarget(new TableColumnRelationshipElement(tableColumn));
18005                                                                                relation.addSource(new ResultColumnRelationshipElement(insertColumn));
18006                                                                                relation.setProcess(process);
18007                                                                        }
18008                                                                }
18009                                                        }
18010                                                } else {
18011                                                        TObjectNameList columns = clause.getInsertClause().getColumnList();
18012                                                        TResultColumnList values = clause.getInsertClause().getValuelist();
18013                                                        if (values == null || values.size() == 0) {
18014                                                                if (clause.getInsertClause().toString().toLowerCase(Locale.ROOT).indexOf("row") != -1) {
18015                                                                        if (stmt.getUsingTable().getSubquery() != null && tableModel instanceof Table) {
18016                                                                                ResultSet sourceResultSet = modelFactory.createQueryTable(stmt.getUsingTable());
18017                                                                                TObjectName targetStarColumn = DlineageUtil.syntheticStarName();
18018                                                                                TableColumn targetTableColumn = modelFactory.createTableColumn((Table)tableModel,
18019                                                                                                targetStarColumn, true);
18020                                                                                if (sourceResultSet.getColumns() == null
18021                                                                                                || sourceResultSet.getColumns().isEmpty()) {
18022                                                                                        TObjectName sourceStarColumn = DlineageUtil.syntheticStarName();
18023                                                                                        modelFactory.createResultColumn(sourceResultSet, sourceStarColumn);
18024                                                                                }
18025                                                                                for (ResultColumn sourceColumn : sourceResultSet.getColumns()) {
18026                                                                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
18027                                                                                        relation.setEffectType(EffectType.merge_insert);
18028                                                                                        relation.setTarget(new TableColumnRelationshipElement(targetTableColumn));
18029                                                                                        relation.addSource(new ResultColumnRelationshipElement(sourceColumn));
18030                                                                                        relation.setProcess(process);
18031                                                                                }
18032                                                                        } else if (tableModel instanceof Table) {
18033                                                                                Table sourceTable = modelFactory.createTable(stmt.getUsingTable());
18034                                                                                TObjectName sourceStarColumn = DlineageUtil.syntheticStarName();
18035                                                                                TableColumn sourceTableColumn = modelFactory.createTableColumn(sourceTable,
18036                                                                                                sourceStarColumn, true);
18037                                                                                TObjectName targetStarColumn = DlineageUtil.syntheticStarName();
18038                                                                                TableColumn targetTableColumn = modelFactory.createTableColumn(((Table)tableModel),
18039                                                                                                targetStarColumn, true);
18040                                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
18041                                                                                relation.setEffectType(EffectType.merge_insert);
18042                                                                                relation.setTarget(new TableColumnRelationshipElement(targetTableColumn));
18043                                                                                relation.addSource(new TableColumnRelationshipElement(sourceTableColumn));
18044                                                                                relation.setProcess(process);
18045                                                                        }
18046
18047                                                                }
18048                                                                continue;
18049                                                        }
18050
18051                                                        List<TObjectName> tableColumns = new ArrayList<TObjectName>();
18052                                                        boolean unresolvedProjectedColumns = false;
18053                                                        if (columns == null || columns.size() == 0) {
18054                                                                List<TObjectName> inferredColumns = inferProjectedInsertColumns(
18055                                                                                stmt.getTargetTable(), values);
18056                                                                unresolvedProjectedColumns = inferredColumns == null
18057                                                                                && cteTargetResolution != null;
18058                                                                if (inferredColumns != null) {
18059                                                                        tableColumns.addAll(inferredColumns);
18060                                                                } else {
18061//                                                              if (!((Table)tableModel).getColumns().isEmpty()) {
18062//                                                                      for (int j = 0; j < ((Table)tableModel).getColumns().size(); j++) {
18063//                                                                              if (((Table)tableModel).getColumns().get(j).getColumnObject() == null) {
18064//                                                                                      continue;
18065//                                                                              }
18066//                                                                              tableColumns.add(((Table)tableModel).getColumns().get(j).getColumnObject());
18067//                                                                      }
18068//                                                              } else {
18069                                                                        for (int j = 0; j < values.size(); j++) {
18070                                                                                TResultColumn column = values.getResultColumn(j);
18071                                                                                if (column.getAliasClause() != null) {
18072                                                                                        tableColumns.add(column.getAliasClause().getAliasName());
18073                                                                                } else if (column.getFieldAttr() != null) {
18074                                                                                        tableColumns.add(column.getFieldAttr());
18075                                                                                } else {
18076                                                                                        TObjectName columnName = new TObjectName();
18077                                                                                        columnName.setString(column.toString());
18078                                                                                        tableColumns.add(columnName);
18079                                                                                }
18080//                                                                      }
18081                                                                        }
18082                                                                }
18083                                                        } else {
18084                                                                for (int j = 0; j < columns.size(); j++) {
18085                                                                        tableColumns.add(columns.getObjectName(j));
18086                                                                }
18087                                                        }
18088
18089                                                        ResultSet resultSet = modelFactory.createResultSet(clause.getInsertClause(), false);
18090
18091                                                        createPseudoImpactRelation(stmt, resultSet, EffectType.merge_insert);
18092                                                        actionRows.add(new Pair<ResultSet, EffectType>(resultSet, EffectType.merge_insert));
18093
18094                                                        for (int j = 0; j < tableColumns.size() && j < values.size(); j++) {
18095                                                                TObjectName columnObject = tableColumns.get(j);
18096
18097                                                                ResultColumn insertColumn = modelFactory.createMergeResultColumn(resultSet,
18098                                                                                columnObject);
18099
18100                                                                TExpression valueExpression = values.getResultColumn(j).getExpr();
18101                                                                if (valueExpression == null)
18102                                                                        continue;
18103
18104                                                                columnsInExpr visitor = new columnsInExpr();
18105                                                                valueExpression.inOrderTraverse(visitor);
18106                                                                List<TObjectName> objectNames = visitor.getObjectNames();
18107                                                                List<TParseTreeNode> functions = visitor.getFunctions();
18108
18109                                                                if (functions != null && !functions.isEmpty()) {
18110                                                                        analyzeFunctionDataFlowRelation(insertColumn, functions, EffectType.merge_insert);
18111                                                                }
18112
18113                                                                List<TSelectSqlStatement> subquerys = visitor.getSubquerys();
18114                                                                if (subquerys != null && !subquerys.isEmpty()) {
18115                                                                        analyzeSubqueryDataFlowRelation(insertColumn, subquerys, EffectType.merge_insert);
18116                                                                }
18117
18118                                                                analyzeDataFlowRelation(insertColumn, objectNames, EffectType.merge_insert, functions);
18119
18120                                                                List<TParseTreeNode> constants = visitor.getConstants();
18121                                                                analyzeConstantDataFlowRelation(insertColumn, constants, EffectType.merge_insert,
18122                                                                                functions);
18123
18124                                                                TObjectName targetColumn = unresolvedProjectedColumns ? null
18125                                                                                : getCteBaseColumn(cteTargetResolution, columnObject);
18126                                                                TableColumn tableColumn = !(tableModel instanceof Table) || targetColumn == null ? null
18127                                                                                : modelFactory.createTableColumn(((Table)tableModel), targetColumn, false);
18128                                                                if(tableColumn == null) {
18129                                                                        if (cteTargetResolution == null && tableModel instanceof Table
18130                                                                                        && ((Table) tableModel).isCreateTable()) {
18131                                                                                tableColumn = ((Table) tableModel).getColumns().get(j);
18132                                                                        }
18133                                                                        else {
18134                                                                                continue;
18135                                                                        }
18136                                                                }
18137
18138                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
18139                                                                relation.setEffectType(EffectType.merge_insert);
18140                                                                relation.setTarget(new TableColumnRelationshipElement(tableColumn));
18141                                                                relation.addSource(new ResultColumnRelationshipElement(insertColumn));
18142                                                                relation.setProcess(process);
18143                                                        }
18144                                                }
18145                                        }
18146                                }
18147
18148                        }
18149
18150                        if (stmt.getCondition() != null) {
18151                                conditionRelations.add(
18152                                                analyzeFilterCondition(null, stmt.getCondition(), null, JoinClauseType.on, EffectType.merge));
18153                        }
18154                        // The ON and WHEN conditions decide which target rows are matched and which source rows are
18155                        // inserted. analyzeFilterCondition records them on the source's rows; carry those rows to the
18156                        // target unless a branch above already did. A table, VALUES or CTE source, or a subquery
18157                        // without a filter of its own, left them without an edge, so the condition columns reached
18158                        // no table (MantisBT #4758).
18159                        Set<ResultSet> conditionRowsLinked = Collections.newSetFromMap(new IdentityHashMap<ResultSet, Boolean>());
18160                        for (ImpactRelationship condition : conditionRelations) {
18161                                if (condition == null || condition.getTarget() == null
18162                                                || !(condition.getTarget().getElement() instanceof ResultSetRelationRows)) {
18163                                        continue;
18164                                }
18165                                ResultSet conditionRows = ((ResultSetRelationRows) condition.getTarget().getElement()).getHolder();
18166                                if (conditionRows == null || conditionRows == tableModel || !conditionRowsLinked.add(conditionRows)) {
18167                                        continue;
18168                                }
18169                                if (conditionRows != sourceRowsLinked) {
18170                                        createMergeRowsImpact(new RelationRowsRelationshipElement<ResultSetRelationRows>(
18171                                                        conditionRows.getRelationRows()), tableModel);
18172                                }
18173                                // the same rows pick the rows each action updates or inserts; a filtered source reached
18174                                // them through createPseudoImpactRelation already, an unfiltered one did not
18175                                for (Pair<ResultSet, EffectType> action : actionRows) {
18176                                        if (action.first != conditionRows && !hasRowsEdge(conditionRows, action.first)) {
18177                                                ImpactRelationship impactRelation = modelFactory.createImpactRelation();
18178                                                impactRelation.setEffectType(action.second);
18179                                                impactRelation.addSource(new RelationRowsRelationshipElement<ResultSetRelationRows>(
18180                                                                conditionRows.getRelationRows()));
18181                                                impactRelation.setTarget(new RelationRowsRelationshipElement<ResultSetRelationRows>(
18182                                                                action.first.getRelationRows()));
18183                                        }
18184                                }
18185                        }
18186                }
18187        }
18188
18189        /** Oracle's UPDATE ... WHERE, DELETE WHERE and INSERT ... WHERE of a MERGE branch, and a DELETE's own condition. */
18190        private static List<TExpression> mergeActionPredicates(TMergeWhenClause clause) {
18191                List<TExpression> predicates = new ArrayList<TExpression>();
18192                if (clause.getUpdateClause() != null) {
18193                        if (clause.getUpdateClause().getUpdateWhereClause() != null) {
18194                                predicates.add(clause.getUpdateClause().getUpdateWhereClause());
18195                        }
18196                        if (clause.getUpdateClause().getDeleteWhereClause() != null) {
18197                                predicates.add(clause.getUpdateClause().getDeleteWhereClause());
18198                        }
18199                }
18200                if (clause.getInsertClause() != null && clause.getInsertClause().getInsertWhereClause() != null) {
18201                        predicates.add(clause.getInsertClause().getInsertWhereClause());
18202                }
18203                if (clause.getDeleteClause() != null && clause.getDeleteClause().getCondition() != null) {
18204                        predicates.add(clause.getDeleteClause().getCondition());
18205                }
18206                return predicates;
18207        }
18208
18209        /** Whether a row-level relation already runs from {@code from}'s rows to {@code to}'s rows. */
18210        private static boolean hasRowsEdge(ResultSet from, ResultSet to) {
18211                for (Relationship relation : to.getRelationRows().getHoldRelations()) {
18212                        for (Object source : relation.getSources()) {
18213                                if (source instanceof RelationshipElement
18214                                                && ((RelationshipElement<?>) source).getElement() == from.getRelationRows()) {
18215                                        return true;
18216                                }
18217                        }
18218                }
18219                return false;
18220        }
18221
18222        /** A merge impact from {@code sourceRows} to the rows of the MERGE target (a table, or a query table). */
18223        private void createMergeRowsImpact(RelationRowsRelationshipElement<?> sourceRows, Object targetModel) {
18224                RelationRowsRelationshipElement<?> targetRows;
18225                if (targetModel instanceof Table) {
18226                        targetRows = new RelationRowsRelationshipElement<TableRelationRows>(((Table) targetModel).getRelationRows());
18227                } else if (targetModel instanceof ResultSet) {
18228                        targetRows = new RelationRowsRelationshipElement<ResultSetRelationRows>(
18229                                        ((ResultSet) targetModel).getRelationRows());
18230                } else {
18231                        return;
18232                }
18233                ImpactRelationship impactRelation = modelFactory.createImpactRelation();
18234                impactRelation.setEffectType(EffectType.merge);
18235                impactRelation.addSource(sourceRows);
18236                impactRelation.setTarget(targetRows);
18237        }
18238
18239        private List<TableColumn> bindInsertTableColumn(Table tableModel, TInsertIntoValue value, List<TObjectName> keyMap,
18240                        List<TResultColumn> valueMap) {
18241                List<TableColumn> tableColumns = new ArrayList<TableColumn>();
18242                if (value.getColumnList() != null) {
18243                        for (int z = 0; z < value.getColumnList().size(); z++) {
18244                                TableColumn tableColumn = modelFactory.createInsertTableColumn(tableModel,
18245                                                value.getColumnList().getObjectName(z));
18246                                tableColumns.add(tableColumn);
18247                                keyMap.add(tableColumn.getColumnObject());
18248                        }
18249                }
18250
18251                if (value.getTargetList() != null) {
18252                        for (int z = 0; z < value.getTargetList().size(); z++) {
18253                                TMultiTarget target = value.getTargetList().getMultiTarget(z);
18254                                TResultColumnList columns = target.getColumnList();
18255                                for (int i = 0; i < columns.size(); i++) {
18256                                        if (value.getColumnList() == null) {
18257                                                TableColumn tableColumn = modelFactory.createInsertTableColumn(tableModel,
18258                                                                columns.getResultColumn(i).getFieldAttr());
18259                                                tableColumns.add(tableColumn);
18260                                        }
18261                                        valueMap.add(columns.getResultColumn(i));
18262                                }
18263                        }
18264                }
18265
18266                return tableColumns;
18267        }
18268
18269        /**
18270         * Whether an INTO clause of a multi-table INSERT (Oracle {@code INSERT ALL} / {@code INSERT FIRST}) decides its
18271         * own mapping, with a column list or a VALUES clause. One with neither takes the subquery's columns in order,
18272         * as {@code INSERT INTO t SELECT ...} does.
18273         */
18274        private static boolean mapsItsOwnColumns(TInsertIntoValue value) {
18275                return (value.getColumnList() != null && value.getColumnList().size() > 0) || insertIntoValues(value) != null;
18276        }
18277
18278        /** The VALUES items of an INTO clause of a multi-table INSERT, or null when it has no VALUES clause. */
18279        private static TResultColumnList insertIntoValues(TInsertIntoValue value) {
18280                if (value.getTargetList() == null || value.getTargetList().size() == 0) {
18281                        return null;
18282                }
18283                TMultiTarget row = value.getTargetList().getMultiTarget(0);
18284                return row == null || row.getColumnList() == null || row.getColumnList().size() == 0 ? null
18285                                : row.getColumnList();
18286        }
18287
18288        /** An INTO clause of a multi-table INSERT that names its own columns or values, with its target columns. */
18289        private static final class SelfMappedInto {
18290                final Table table;
18291                final TInsertIntoValue value;
18292                /** the target column of each position of the clause, null where no column can be named */
18293                final List<TableColumn> targets;
18294                /** the clause's position among the statement's INTO clauses */
18295                final int position;
18296                /** the WHEN conditions that decide which rows reach the clause, see {@link #governingConditions} */
18297                final List<TExpression> conditions;
18298
18299                SelfMappedInto(Table table, TInsertIntoValue value, List<TableColumn> targets, int position,
18300                                List<TExpression> conditions) {
18301                        this.table = table;
18302                        this.value = value;
18303                        this.targets = targets;
18304                        this.position = position;
18305                        this.conditions = conditions;
18306                }
18307        }
18308
18309        /** The columns one INTO clause of a conditional multi-table INSERT writes, and the WHEN conditions routing rows to it. */
18310        private static final class WhenScope {
18311                final int position;
18312                final List<TableColumn> targets;
18313                final List<TExpression> conditions;
18314
18315                WhenScope(int position, List<TableColumn> targets, List<TExpression> conditions) {
18316                        this.position = position;
18317                        this.targets = targets;
18318                        this.conditions = conditions;
18319                }
18320        }
18321
18322        /**
18323         * The WHEN conditions that decide which rows reach the INTO clauses of the {@code branch}-th WHEN of a
18324         * conditional multi-table INSERT, or of its ELSE when {@code branch} is the number of WHENs. INSERT ALL tests
18325         * every WHEN on its own, so a branch depends on its own condition; INSERT FIRST takes the first WHEN that holds,
18326         * so a branch depends on its own condition and every one before it; ELSE takes the rows no WHEN took, so it
18327         * depends on all of them. The rows of a branch did not use to be tied to its own condition: every condition was
18328         * applied to every target, and a target of a clause without a column list or VALUES got none.
18329         */
18330        private static List<TExpression> governingConditions(TInsertSqlStatement stmt, int branch) {
18331                List<TExpression> conditions = new ArrayList<TExpression>();
18332                int count = stmt.getInsertConditions().size();
18333                for (int i = 0; i < count; i++) {
18334                        TExpression condition = stmt.getInsertConditions().getElement(i).getCondition();
18335                        if (condition == null) {
18336                                continue;
18337                        }
18338                        if (i == branch || branch >= count || (stmt.isInsertFirst() && i < branch)) {
18339                                conditions.add(condition);
18340                        }
18341                }
18342                return conditions;
18343        }
18344
18345        /** The relations registered so far, by identity, to tell afterwards which ones a step added. */
18346        private Set<Relationship> relationSnapshot() {
18347                Set<Relationship> snapshot = Collections.newSetFromMap(new IdentityHashMap<Relationship, Boolean>());
18348                snapshot.addAll(Arrays.asList(modelManager.getRelations()));
18349                return snapshot;
18350        }
18351
18352        /** The columns of {@code table} that value relations registered after {@code snapshot} write into. */
18353        private List<TableColumn> columnsWrittenSince(Set<Relationship> snapshot, Table table) {
18354                List<TableColumn> written = new ArrayList<TableColumn>();
18355                for (Relationship relation : modelManager.getRelations()) {
18356                        if (snapshot.contains(relation) || !(relation instanceof DataFlowRelationship)
18357                                        || !(relation.getTarget() instanceof TableColumnRelationshipElement)) {
18358                                continue;
18359                        }
18360                        TableColumn column = ((TableColumnRelationshipElement) relation.getTarget()).getElement();
18361                        if (column != null && column.getTable() == table && !written.contains(column)) {
18362                                written.add(column);
18363                        }
18364                }
18365                return written;
18366        }
18367
18368        /**
18369         * Row impact of the WHEN conditions of a conditional multi-table INSERT on the columns of one INTO clause: an
18370         * {@code fdr} from every subquery column a governing condition reads to every target column. A condition name
18371         * is bound like a VALUES name, to the select item the parser bound it to, so a qualified name
18372         * ({@code WHEN x.k > 0}), a name inside a function and a select-list alias all reach their column; they used to
18373         * be matched to the subquery's columns by spelling, and a qualified one never was. A name bound to a wildcard
18374         * reads the column the wildcard's expansion gives it, which exists when the columns behind the wildcard are known
18375         * (a declared table, a CTE). Over a table that is not declared the name may instead be a variable or parameter
18376         * of the enclosing block: the parser binds any name to a wildcard, while a column wins only when the table has
18377         * it. Such a name gets no edge rather than an invented column.
18378         */
18379        private void analyzeInsertWhenImpact(TSelectSqlStatement subquery, ResultSet subqueryRows, WhenScope scope,
18380                        Map<TExpression, List<Pair<ResultColumn, TObjectName>>> conditionReads, EffectType effectType) {
18381                List<Pair<ResultColumn, TObjectName>> sources = new ArrayList<Pair<ResultColumn, TObjectName>>();
18382                for (TExpression condition : scope.conditions) {
18383                        List<Pair<ResultColumn, TObjectName>> reads = conditionReads.get(condition);
18384                        if (reads == null) {
18385                                reads = new ArrayList<Pair<ResultColumn, TObjectName>>();
18386                                columnsInExpr visitor = new columnsInExpr();
18387                                visitor.setSkipFunction(true);
18388                                condition.inOrderTraverse(visitor);
18389                                // every name decides the routing, including those of a CASE's own conditions
18390                                List<TObjectName> names = new ArrayList<TObjectName>(visitor.getObjectNames());
18391                                names.addAll(caseConditionNames(condition));
18392                                for (TObjectName name : names) {
18393                                        ResultColumn source = insertAllSelectItem(subquery, subqueryRows, name, true);
18394                                        if (source != null) {
18395                                                reads.add(new Pair<ResultColumn, TObjectName>(source, name));
18396                                        }
18397                                }
18398                                conditionReads.put(condition, reads);
18399                        }
18400                        sources.addAll(reads);
18401                }
18402                if (sources.isEmpty()) {
18403                        return;
18404                }
18405                for (TableColumn target : scope.targets) {
18406                        ImpactRelationship relation = modelFactory.createImpactRelation();
18407                        relation.setEffectType(effectType);
18408                        relation.setTarget(new TableColumnRelationshipElement(target));
18409                        for (Pair<ResultColumn, TObjectName> source : sources) {
18410                                relation.addSource(new ResultColumnRelationshipElement(source.first, source.second.getLocation(),
18411                                                source.second));
18412                        }
18413                }
18414        }
18415
18416        /**
18417         * The target column of each position of an INTO clause that names its own columns or values: the {@code i}-th
18418         * name of its column list, or without one the {@code i}-th column of a declared table, or, when neither names
18419         * it, a column named after its VALUES item when that item is a column, as {@code INSERT INTO t SELECT ...} names
18420         * a column after its select item. Null where none of these names it. Called while the INTO clauses are
18421         * collected, where {@link #bindInsertTableColumn} made the same columns, so their ids keep their order.
18422         */
18423        private List<TableColumn> insertIntoTargets(Table tableModel, TInsertIntoValue value) {
18424                TObjectNameList columnList = value.getColumnList() != null && value.getColumnList().size() > 0
18425                                ? value.getColumnList() : null;
18426                TResultColumnList values = insertIntoValues(value);
18427                boolean declared = tableModel.isCreateTable() && tableModel.getColumns() != null
18428                                && !containStarColumn(tableModel.getColumns());
18429                int width = columnList != null ? columnList.size() : (values != null ? values.size() : 0);
18430                List<TableColumn> targets = new ArrayList<TableColumn>(width);
18431                for (int i = 0; i < width; i++) {
18432                        TableColumn target = null;
18433                        if (columnList != null) {
18434                                target = modelFactory.createInsertTableColumn(tableModel, columnList.getObjectName(i));
18435                        } else if (declared) {
18436                                if (i < tableModel.getColumns().size()) {
18437                                        target = tableModel.getColumns().get(i);
18438                                }
18439                        } else {
18440                                TObjectName valueName = values.getResultColumn(i).getFieldAttr();
18441                                if (valueName != null) {
18442                                        target = modelFactory.createInsertTableColumn(tableModel, valueName);
18443                                }
18444                        }
18445                        targets.add(target);
18446                }
18447                return targets;
18448        }
18449
18450        /**
18451         * Lineage of one INTO clause of a multi-table INSERT that names its own columns or values. The clause alone
18452         * decides where each value goes: target {@code i} ({@link #insertIntoTargets}) receives the {@code i}-th VALUES
18453         * item, or without VALUES the subquery's {@code i}-th column. The per-table mapping of an INSERT used to handle
18454         * these clauses by the subquery's column order, so {@code INSERT ALL INTO t (c2, c1) VALUES (v, w) SELECT v, w
18455         * FROM s} wrote {@code v} into {@code c1}, and without a declaration of {@code t} the clause had no lineage.
18456         *
18457         * @param firstOfTable whether this is the first such clause into its table, which carries the subquery's row
18458         *        filter to the table's rows once
18459         * @return the target columns, for the row impact of the WHEN conditions
18460         */
18461        private List<TableColumn> analyzeInsertIntoClause(TInsertSqlStatement stmt, SelfMappedInto into,
18462                        EffectType effectType, boolean firstOfTable) {
18463                List<TableColumn> targets = new ArrayList<TableColumn>();
18464                TSelectSqlStatement subquery = stmt.getSubQuery();
18465                ResultSet subqueryRows = null;
18466                if (subquery != null) {
18467                        Object model = modelManager.getModel(subquery);
18468                        if (!(model instanceof ResultSet) && subquery.getResultColumnList() != null) {
18469                                model = modelManager.getModel(subquery.getResultColumnList());
18470                        }
18471                        if (model instanceof ResultSet) {
18472                                subqueryRows = (ResultSet) model;
18473                        }
18474                }
18475                if (firstOfTable && subqueryRows != null && !subqueryRows.getRelationRows().getHoldRelations().isEmpty()) {
18476                        ImpactRelationship impactRelation = modelFactory.createImpactRelation();
18477                        impactRelation.setEffectType(effectType);
18478                        impactRelation.addSource(
18479                                        new RelationRowsRelationshipElement<ResultSetRelationRows>(subqueryRows.getRelationRows()));
18480                        impactRelation.setTarget(new RelationRowsRelationshipElement<TableRelationRows>(into.table.getRelationRows()));
18481                }
18482
18483                TResultColumnList values = insertIntoValues(into.value);
18484                int width = into.targets.size();
18485                // Without VALUES the subquery's columns fill the list in order; a wildcard of unknown width leaves each
18486                // position the set of items that could supply it.
18487                List<List<Integer>> candidates = values == null && subqueryRows != null
18488                                ? candidateSelectItems(subqueryRows.getColumns(), width) : null;
18489                // a set operation with a star of unknown width: the items of each of its selects (Mantis 4914)
18490                List<SelectPositions> setSelects = candidates != null && subquery.isCombinedQuery()
18491                                ? setOperationPositions(subquery, width) : null;
18492                Process process = modelFactory.createProcess(stmt);
18493                for (int i : insertIntoEmissionOrder(subquery, subqueryRows, values, width)) {
18494                        TableColumn target = into.targets.get(i);
18495                        if (target == null) {
18496                                continue;
18497                        }
18498                        if (values != null) {
18499                                if (i < values.size()) {
18500                                        analyzeInsertIntoValue(subquery, subqueryRows, target, values.getResultColumn(i).getExpr(),
18501                                                        effectType, process);
18502                                }
18503                        } else if (setSelects != null) {
18504                                if (hasCandidates(setSelects, i)) {
18505                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
18506                                        relation.setEffectType(effectType);
18507                                        relation.setTarget(new TableColumnRelationshipElement(target));
18508                                        for (SelectPositions select : setSelects) {
18509                                                for (int k : select.candidates.get(i)) {
18510                                                        for (ResultColumn column : select.items.get(k)) {
18511                                                                relation.addSource(positionSource(column));
18512                                                        }
18513                                                }
18514                                        }
18515                                        relation.setProcess(process);
18516                                }
18517                        } else if (candidates != null && !candidates.get(i).isEmpty()) {
18518                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
18519                                relation.setEffectType(effectType);
18520                                relation.setTarget(new TableColumnRelationshipElement(target));
18521                                for (int k : candidates.get(i)) {
18522                                        relation.addSource(positionSource(subqueryRows.getColumns().get(k)));
18523                                }
18524                                relation.setProcess(process);
18525                        }
18526                }
18527                for (TableColumn target : into.targets) {
18528                        if (target != null) {
18529                                targets.add(target);
18530                        }
18531                }
18532                return targets;
18533        }
18534
18535        /**
18536         * The order in which the positions of such an INTO clause emit their relations: by the subquery column their
18537         * value reads first, the order in which the per-table mapping emitted them, so a relation this mapping leaves
18538         * unchanged keeps its id in the output. A position whose value reads no subquery column comes last. Only the
18539         * order of emission depends on this, never which relations exist.
18540         */
18541        private List<Integer> insertIntoEmissionOrder(TSelectSqlStatement subquery, ResultSet subqueryRows,
18542                        TResultColumnList values, int width) {
18543                List<Integer> order = new ArrayList<Integer>(width);
18544                for (int i = 0; i < width; i++) {
18545                        order.add(i);
18546                }
18547                if (values == null || subqueryRows == null) {
18548                        return order;
18549                }
18550                final int[] firstRead = new int[width];
18551                for (int i = 0; i < width; i++) {
18552                        firstRead[i] = Integer.MAX_VALUE;
18553                        if (i >= values.size() || values.getResultColumn(i).getExpr() == null) {
18554                                continue;
18555                        }
18556                        columnsInExpr visitor = new columnsInExpr();
18557                        visitor.setSkipFunction(true);
18558                        values.getResultColumn(i).getExpr().inOrderTraverse(visitor);
18559                        for (TObjectName name : visitor.getObjectNames()) {
18560                                ResultColumn source = insertAllSelectItem(subquery, subqueryRows, name);
18561                                int position = source == null ? -1 : subqueryRows.getColumns().indexOf(source);
18562                                if (position >= 0 && position < firstRead[i]) {
18563                                        firstRead[i] = position;
18564                                }
18565                        }
18566                }
18567                Collections.sort(order, new Comparator<Integer>() {
18568                        @Override
18569                        public int compare(Integer a, Integer b) {
18570                                if (firstRead[a] != firstRead[b]) {
18571                                        return firstRead[a] < firstRead[b] ? -1 : 1;
18572                                }
18573                                return a.compareTo(b);
18574                        }
18575                });
18576                return order;
18577        }
18578
18579        /**
18580         * Lineage of one VALUES item of an INTO clause of a multi-table INSERT into {@code target}. A VALUES item reads
18581         * the subquery's select list, and the parser binds each of its names to the select item it names
18582         * ({@code TObjectName.getSourceColumn()}), by alias or by column name. The item's value comes from those select
18583         * items, not from a column found by spelling: {@code VALUES (b)} over {@code SELECT x.w AS b} reads {@code x.w}.
18584         * For the same reason a function or CASE is not analyzed as a function node, which looks its arguments up by
18585         * spelling: the names inside its arguments and results are bound the same way and the function is recorded on
18586         * the relation, and the names in a CASE condition decide the row's value without being it, so they become an
18587         * {@code fdr}. A value that reads no select item keeps the general path, where a function is a node as in an
18588         * ordinary INSERT ... VALUES.
18589         */
18590        private void analyzeInsertIntoValue(TSelectSqlStatement subquery, ResultSet subqueryRows, TableColumn target,
18591                        TExpression valueExpr, EffectType effectType, Process process) {
18592                if (valueExpr == null) {
18593                        return;
18594                }
18595                columnsInExpr visitor = new columnsInExpr();
18596                visitor.setSkipFunction(true);
18597                valueExpr.inOrderTraverse(visitor);
18598                List<ResultColumn> sources = new ArrayList<ResultColumn>();
18599                List<TObjectName> unbound = new ArrayList<TObjectName>();
18600                for (TObjectName name : visitor.getObjectNames()) {
18601                        ResultColumn source = insertAllSelectItem(subquery, subqueryRows, name);
18602                        if (source != null) {
18603                                if (!sources.contains(source)) {
18604                                        sources.add(source);
18605                                }
18606                        } else if (!readsSubqueryTableDirectly(name)) {
18607                                unbound.add(name);
18608                        }
18609                }
18610                TParseTreeNode function = outermostFunction(valueExpr);
18611                List<TParseTreeNode> functions = function == null ? null : Collections.singletonList(function);
18612                if (sources.isEmpty() && unbound.isEmpty() && function != null) {
18613                        // a function of constants only: a node, as in an ordinary INSERT ... VALUES
18614                        analyzeFunctionDataFlowRelation(target, functions, effectType);
18615                        return;
18616                }
18617                if (!sources.isEmpty()) {
18618                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
18619                        relation.setEffectType(effectType);
18620                        relation.setTarget(new TableColumnRelationshipElement(target));
18621                        relation.setProcess(process);
18622                        if (function != null) {
18623                                relation.setFunction(getFunctionName(function));
18624                        }
18625                        for (ResultColumn source : sources) {
18626                                relation.addSource(new ResultColumnRelationshipElement(source));
18627                        }
18628                }
18629                if (!unbound.isEmpty()) {
18630                        analyzeDataFlowRelation(target, unbound, effectType, functions, process);
18631                }
18632                List<ResultColumn> conditions = caseConditionSources(subquery, subqueryRows, valueExpr);
18633                if (!conditions.isEmpty()) {
18634                        ImpactRelationship impact = modelFactory.createImpactRelation();
18635                        impact.setEffectType(effectType);
18636                        impact.setTarget(new TableColumnRelationshipElement(target));
18637                        for (ResultColumn source : conditions) {
18638                                impact.addSource(new ResultColumnRelationshipElement(source));
18639                        }
18640                }
18641                List<TSelectSqlStatement> subquerys = visitor.getSubquerys();
18642                if (subquerys != null && !subquerys.isEmpty()) {
18643                        analyzeSubqueryDataFlowRelation(target, subquerys, effectType);
18644                }
18645                List<TParseTreeNode> constants = visitor.getConstants();
18646                if (constants != null && !constants.isEmpty()) {
18647                        analyzeConstantDataFlowRelation(target, constants, effectType, functions);
18648                }
18649        }
18650
18651        /**
18652         * Whether a VALUES name of a multi-table INSERT that is bound to no select item was credited by the resolver to a
18653         * table of the subquery. VALUES cannot read those tables directly, so such a column does not exist in the
18654         * lineage. A variable, a parameter, a bind variable, a sequence's NEXTVAL or CURRVAL and a pseudo-column carry
18655         * no such credit and keep the general path.
18656         */
18657        private static boolean readsSubqueryTableDirectly(TObjectName name) {
18658                return name.getSourceColumn() == null && name.getSourceTable() != null
18659                                && name.getDbObjectType() == EDbObjectType.column;
18660        }
18661
18662        /** The function or CASE a VALUES item is, under any parentheses, or null. */
18663        private static TParseTreeNode outermostFunction(TExpression expr) {
18664                TExpression current = expr;
18665                while (current != null && current.getExpressionType() == EExpressionType.parenthesis_t) {
18666                        current = current.getLeftOperand();
18667                }
18668                if (current == null) {
18669                        return null;
18670                }
18671                if (current.getExpressionType() == EExpressionType.function_t && current.getFunctionCall() != null) {
18672                        return current.getFunctionCall();
18673                }
18674                if (current.getExpressionType() == EExpressionType.case_t && current.getCaseExpression() != null) {
18675                        return current.getCaseExpression();
18676                }
18677                return null;
18678        }
18679
18680        /** The select items the conditions of the CASE expressions inside a VALUES item read. */
18681        private List<ResultColumn> caseConditionSources(TSelectSqlStatement subquery, ResultSet subqueryRows,
18682                        TExpression valueExpr) {
18683                List<ResultColumn> sources = new ArrayList<ResultColumn>();
18684                for (TObjectName name : caseConditionNames(valueExpr)) {
18685                        ResultColumn source = insertAllSelectItem(subquery, subqueryRows, name);
18686                        if (source != null && !sources.contains(source)) {
18687                                sources.add(source);
18688                        }
18689                }
18690                return sources;
18691        }
18692
18693        /**
18694         * The names in the conditions of the CASE expressions inside {@code expr}: each CASE's input and the comparison of
18695         * each WHEN, names inside functions included. {@code columnsInExpr} in its flattening mode reads only a CASE's
18696         * results.
18697         */
18698        private List<TObjectName> caseConditionNames(TExpression expr) {
18699                final List<TCaseExpression> cases = new ArrayList<TCaseExpression>();
18700                expr.acceptChildren(new TParseTreeVisitor() {
18701                        @Override
18702                        public void preVisit(TCaseExpression node) {
18703                                cases.add(node);
18704                        }
18705                });
18706                List<TObjectName> names = new ArrayList<TObjectName>();
18707                for (TCaseExpression caseExpr : cases) {
18708                        List<TExpression> conditions = new ArrayList<TExpression>();
18709                        if (caseExpr.getInput_expr() != null) {
18710                                conditions.add(caseExpr.getInput_expr());
18711                        }
18712                        TWhenClauseItemList items = caseExpr.getWhenClauseItemList();
18713                        for (int i = 0; items != null && i < items.size(); i++) {
18714                                if (items.getWhenClauseItem(i).getComparison_expr() != null) {
18715                                        conditions.add(items.getWhenClauseItem(i).getComparison_expr());
18716                                }
18717                        }
18718                        for (TExpression condition : conditions) {
18719                                columnsInExpr visitor = new columnsInExpr();
18720                                visitor.setSkipFunction(true);
18721                                condition.inOrderTraverse(visitor);
18722                                names.addAll(visitor.getObjectNames());
18723                        }
18724                }
18725                return names;
18726        }
18727
18728        private ResultColumn insertAllSelectItem(TSelectSqlStatement subquery, ResultSet subqueryRows, TObjectName name) {
18729                return insertAllSelectItem(subquery, subqueryRows, name, false);
18730        }
18731
18732        /**
18733         * The subquery column a VALUES name of a multi-table INSERT reads, given the select item the parser bound it
18734         * to; for a wildcard item null, or with {@code throughWildcard} the column the wildcard's expansion gives the
18735         * name (a WHEN condition name). Of a set operation ({@code UNION ALL} ...) that item belongs to one branch, whose
18736         * rows are only part of the subquery's, so the name reads the set's column at the item's position in its
18737         * branch's result, counted after any wildcard before it is expanded. When a wildcard of unknown width stands
18738         * before it, that position is unknown and the item's own branch column is what can be proven.
18739         */
18740        private ResultColumn insertAllSelectItem(TSelectSqlStatement subquery, ResultSet subqueryRows, TObjectName name,
18741                        boolean throughWildcard) {
18742                TResultColumn item = name == null ? null : name.getSourceColumn();
18743                if (item == null || subquery == null) {
18744                        return null;
18745                }
18746                ResultColumn own = selectItemColumn(item, throughWildcard ? name : null);
18747                if (own == null) {
18748                        return null;
18749                }
18750                if (!subquery.isCombinedQuery() || subqueryRows == null) {
18751                        return own;
18752                }
18753                TSelectSqlStatement branch = branchHoldingSelectItem(subquery, item);
18754                Object branchModel = branch == null ? null
18755                                : modelManager.getModel(branch.getResultColumnList() != null ? branch.getResultColumnList() : branch);
18756                if (!(branchModel instanceof ResultSet)) {
18757                        return own;
18758                }
18759                List<ResultColumn> branchColumns = ((ResultSet) branchModel).getColumns();
18760                int index = branchColumns.indexOf(own);
18761                if (index < 0 || index >= subqueryRows.getColumns().size()) {
18762                        return own;
18763                }
18764                for (int k = 0; k < index; k++) {
18765                        if (branchColumns.get(k).isStarColumn()) {
18766                                return own;
18767                        }
18768                }
18769                ResultColumn column = subqueryRows.getColumns().get(index);
18770                return column.isStarColumn() ? own : column;
18771        }
18772
18773        /**
18774         * The result column a name bound to select item {@code item} reads: the item's own column, or, given the
18775         * {@code name}, for a wildcard item the column its expansion gives that name (the expansion is keyed by column
18776         * name and holds the columns of a declared table or of a CTE). Null for a wildcard item without a name or whose
18777         * expansion does not name it.
18778         */
18779        private ResultColumn selectItemColumn(TResultColumn item, TObjectName name) {
18780                Object model = modelManager.getModel(item);
18781                if (model instanceof ResultColumn) {
18782                        return ((ResultColumn) model).isStarColumn() ? null : (ResultColumn) model;
18783                }
18784                if (name != null && model instanceof LinkedHashMap) {
18785                        for (Object value : ((LinkedHashMap<?, ?>) model).values()) {
18786                                if (value instanceof ResultColumn && !((ResultColumn) value).isStarColumn()
18787                                                && DlineageUtil.sameColumnName(((ResultColumn) value).getName(), name)) {
18788                                        return (ResultColumn) value;
18789                                }
18790                        }
18791                }
18792                return null;
18793        }
18794
18795        /** The branch of a set operation whose select list holds {@code item}, or null. */
18796        private static TSelectSqlStatement branchHoldingSelectItem(TSelectSqlStatement setOperation, TResultColumn item) {
18797                Deque<TSelectSqlStatement> stack = new ArrayDeque<TSelectSqlStatement>();
18798                stack.push(setOperation);
18799                while (!stack.isEmpty()) {
18800                        TSelectSqlStatement current = stack.pop();
18801                        if (current.isCombinedQuery()) {
18802                                if (current.getRightStmt() != null) {
18803                                        stack.push(current.getRightStmt());
18804                                }
18805                                if (current.getLeftStmt() != null) {
18806                                        stack.push(current.getLeftStmt());
18807                                }
18808                        } else if (current.getResultColumnList() != null) {
18809                                for (int i = 0; i < current.getResultColumnList().size(); i++) {
18810                                        if (current.getResultColumnList().getResultColumn(i) == item) {
18811                                                return current;
18812                                        }
18813                                }
18814                        }
18815                }
18816                return null;
18817        }
18818
18819        private TableColumn matchColumn(List<TableColumn> tableColumns, TableColumn targetColumn) {
18820                String columnName = targetColumn.getName();
18821                if (tableColumns == null) {
18822                        return null;
18823                }
18824                for (int i = 0; i < tableColumns.size(); i++) {
18825                        TableColumn column = tableColumns.get(i);
18826                        if (column.getColumnObject() == null) {
18827                                continue;
18828                        }
18829                        if(column.isStruct() && targetColumn.isStruct()) {
18830                                List<String> names = SQLUtil.parseNames(column.getName());
18831                                List<String> targetNames = SQLUtil
18832                                                .parseNames(targetColumn.getName());
18833                                if (!DlineageUtil.sameColumnName(targetNames.get(0), names.get(0))) {
18834                                        continue;
18835                                }
18836                        }
18837                        if (DlineageUtil.sameColumnName(column.getColumnObject().toString(), columnName)) {
18838                                return column;
18839                        }
18840                }
18841                return null;
18842        }
18843        
18844        private TableColumn matchColumn(List<TableColumn> tableColumns, TObjectName columnName) {
18845                if (tableColumns == null) {
18846                        return null;
18847                }
18848                TableColumn declaredColumn = null;
18849                for (int i = 0; i < tableColumns.size(); i++) {
18850                        TableColumn column = tableColumns.get(i);
18851                        if (column.getColumnObject() == null) {
18852                                continue;
18853                        }
18854                        if (!DlineageUtil.sameColumnName(column.getColumnObject(), columnName)) {
18855                                continue;
18856                        }
18857                        // objcol.a2 is not the column objcol.a1 was written into (Mantis #4747)
18858                        if (DlineageUtil.sameFieldLeaf(column.getColumnObject(), columnName)) {
18859                                return column;
18860                        }
18861                        // A declared table has no column for an attribute: objcol.a1 lands in the declared objcol, where
18862                        // ModelFactory.createTableColumn puts an UPDATE or MERGE target and a read of it. Missing it here sent
18863                        // the write to the declared column at its list position instead, or dropped it (Mantis #4747).
18864                        if (declaredColumn == null && column.getTable() != null && column.getTable().isCreateTable()
18865                                        && DlineageUtil.isFieldPath(columnName) && !DlineageUtil.isFieldPath(column.getColumnObject())) {
18866                                declaredColumn = column;
18867                        }
18868                }
18869                return declaredColumn;
18870        }
18871
18872        private ResultColumn matchResultColumn(List<ResultColumn> resultColumns, ResultColumn resultColumn) {
18873                if (resultColumns == null) {
18874                        return null;
18875                }
18876
18877                TObjectName columnName = getObjectName(resultColumn);
18878                if (columnName == null) {
18879                        return null;
18880                }
18881
18882                for (int i = 0; i < resultColumns.size(); i++) {
18883                        ResultColumn column = resultColumns.get(i);
18884                        if (column.getAlias() != null
18885                                        && DlineageUtil.sameColumnName(column.getAlias(), columnName))
18886                                return column;
18887                        if (column.getName() != null && DlineageUtil.sameColumnName(column.getName(), columnName))
18888                                return column;
18889                        if (column.getName() != null && column.isStarColumn()) {
18890                                if (isBareStarNode(column.getColumnObject())) {
18891                                        return column;
18892                                } else {
18893                                        TObjectName columnObjectName = getObjectName(column);
18894                                        if (columnObjectName.getTableString() != null
18895                                                        && columnObjectName.getTableString().equals(getResultSetAlias(resultColumn))) {
18896                                                return column;
18897                                        }
18898                                }
18899                        }
18900                }
18901                return null;
18902        }
18903
18904        private String getResultSetAlias(ResultColumn resultColumn) {
18905                ResultSet resultSet = resultColumn.getResultSet();
18906                if (resultSet instanceof QueryTable) {
18907                        return ((QueryTable) resultSet).getAlias();
18908                }
18909                return null;
18910        }
18911
18912        private ResultColumn matchResultColumn(List<ResultColumn> resultColumns, TObjectName columnName) {
18913                if (resultColumns == null) {
18914                        return null;
18915                }
18916                for (int i = 0; i < resultColumns.size(); i++) {
18917                        ResultColumn column = resultColumns.get(i);
18918                        if (column.getAlias() != null
18919                                        && DlineageUtil.sameColumnName(column.getAlias(), columnName))
18920                                return column;
18921                        if (column.getName() != null && DlineageUtil.sameColumnName(column.getName(), columnName))
18922                                return column;
18923                        if (column.getName() != null && column.isStarColumn()) {
18924                                if (isBareStarNode(column.getColumnObject())) {
18925                                        return column;
18926                                } else {
18927                                        TObjectName columnObjectName = getObjectName(column);
18928                                        if (columnObjectName.getTableString() != null
18929                                                        && columnObjectName.getTableString().equals(columnName.getTableString())) {
18930                                                return column;
18931                                        }
18932                                }
18933                        }
18934                }
18935                return null;
18936        }
18937
18938        /** Rule R8: the select items of {@code columns}, each as its result columns; a STRUCT item's fields are one item. */
18939        private List<List<ResultColumn>> selectItemGroups(List<ResultColumn> columns) {
18940                List<List<ResultColumn>> groups = new ArrayList<List<ResultColumn>>();
18941                for (int j = 0; j < columns.size();) {
18942                        int width = columns.get(j).isStarColumn() ? 1 : selectItemWidth(columns, j);
18943                        groups.add(new ArrayList<ResultColumn>(columns.subList(j, j + width)));
18944                        j += width;
18945                }
18946                return groups;
18947        }
18948
18949        /**
18950         * Rule R8: how many select items {@code columns} holds from {@code j} on, the fields of a STRUCT item one item. A
18951         * column-list mapper pairs a star of unknown width with one more target column while more target columns remain
18952         * than these items: the select list is as wide as the column list, so the items after the star fill the last
18953         * columns (owner ruling 2026-10-07). Counting result columns instead let a STRUCT of two fields after the star
18954         * take the column right after it. Only the first {@code end} columns are the select list's: a mapper appends the
18955         * columns it expands a star into while it pairs.
18956         */
18957        private int selectItemsFrom(List<ResultColumn> columns, int j, int end) {
18958                List<ResultColumn> selectList = columns.subList(0, Math.min(end, columns.size()));
18959                int items = 0;
18960                for (int k = j; k < selectList.size(); items++) {
18961                        k += selectList.get(k).isStarColumn() ? 1 : selectItemWidth(selectList, k);
18962                }
18963                return items;
18964        }
18965
18966        /** How many of {@code columns} are stars. */
18967        private static int starColumnCount(List<ResultColumn> columns) {
18968                int stars = 0;
18969                for (ResultColumn column : columns) {
18970                        if (column.isStarColumn()) {
18971                                stars++;
18972                        }
18973                }
18974                return stars;
18975        }
18976
18977        /**
18978         * Pairs the column list of a view with a select list that has two or more stars of unknown width (Mantis 4915):
18979         * each view column reads every select item that can stand at its position ({@link #candidateSelectItems}), as an
18980         * INSERT column list does; a STRUCT item proven at the position feeds its fields. Pairing them one by one split the
18981         * list between the stars at a boundary nothing in the SQL proves. A set operation with a star of unknown width
18982         * gives the items of each of its selects (Mantis 4914), for a view or, {@code effectType} create_table, the column
18983         * list of CREATE TABLE ... AS.
18984         */
18985        private void mapViewColumnsByCandidates(Table viewModel, List<TObjectName> aliases, List<SelectPositions> selects,
18986                        EffectType effectType, Process process) {
18987                for (int i = 0; i < aliases.size(); i++) {
18988                        TObjectName alias = aliases.get(i);
18989                        if (!hasCandidates(selects, i)) {
18990                                continue;
18991                        }
18992                        TableColumn viewColumn;
18993                        if (effectType == EffectType.create_view) {
18994                                viewColumn = modelFactory.createViewColumn(viewModel, alias, i, true);
18995                                appendTableColumnToSQLEnv(viewModel, viewColumn);
18996                        } else {
18997                                // the column list of CREATE TABLE ... AS
18998                                viewColumn = modelFactory.createTableColumn(viewModel, alias, true);
18999                                if (viewColumn == null) {
19000                                        continue;
19001                                }
19002                        }
19003                        for (SelectPositions select : selects) {
19004                                List<Integer> candidates = select.candidates.get(i);
19005                                if (candidates.isEmpty()) {
19006                                        continue;
19007                                }
19008                                List<ResultColumn> single = candidates.size() == 1 ? select.items.get(candidates.get(0)) : null;
19009                                if (single != null && (single.size() > 1 || structFieldPaths.containsKey(single.get(0)))) {
19010                                        feedFromStructItem(viewColumn, single, alias, effectType, process, true);
19011                                        continue;
19012                                }
19013                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
19014                                relation.setEffectType(effectType);
19015                                relation.setTarget(new ViewColumnRelationshipElement(viewColumn));
19016                                for (int k : candidates) {
19017                                        for (ResultColumn column : select.items.get(k)) {
19018                                                relation.addSource(positionSource(column));
19019                                        }
19020                                }
19021                                relation.setProcess(process);
19022                        }
19023                }
19024        }
19025
19026        /** The columns a view's column list names; a constraint of it (Oracle CONSTRAINT pk PRIMARY KEY (a)) has no alias and takes no position. */
19027        private static List<TObjectName> viewColumnAliases(TViewAliasItemList viewItems) {
19028                List<TObjectName> aliases = new ArrayList<TObjectName>();
19029                for (int i = 0; i < viewItems.size(); i++) {
19030                        if (viewItems.getViewAliasItem(i).getAlias() != null) {
19031                                aliases.add(viewItems.getViewAliasItem(i).getAlias());
19032                        }
19033                }
19034                return aliases;
19035        }
19036
19037        /**
19038         * Pairs the column list of a CTE with a select list that has two or more stars of unknown width (Mantis 4916): each
19039         * CTE column reads every select item that can stand at its position ({@link #candidateSelectItems}), as an INSERT
19040         * column list does. Pairing them one by one gave every column to the first star and ran past the end. A STRUCT
19041         * item proven at its position is one CTE column with a field node per field, as the one-by-one pairing builds it.
19042         * A set operation with a star of unknown width gives the items of each of its selects (Mantis 4914), for a CTE or
19043         * the alias list of a derived table.
19044         */
19045        private void mapCteColumnsByCandidates(QueryTable queryTable, List<SelectPositions> selects, TObjectNameList cteColumns) {
19046                mapCteColumnsByCandidates(queryTable, selects, cteColumns, null);
19047        }
19048
19049        /**
19050         * {@link #mapCteColumnsByCandidates(QueryTable, List, TObjectNameList)} with relations of {@code process}: the UPDATE
19051         * that reads the CTE (Mantis 5003) keeps its process on them; a query passes null.
19052         */
19053        private void mapCteColumnsByCandidates(QueryTable queryTable, List<SelectPositions> selects, TObjectNameList cteColumns,
19054                        Process process) {
19055                for (int i = 0; i < queryTable.getColumns().size(); i++) {
19056                        ResultColumn targetColumn = queryTable.getColumns().get(i);
19057                        for (SelectPositions select : selects) {
19058                                List<Integer> candidates = select.candidates.get(i);
19059                                List<ResultColumn> single = candidates.size() == 1 ? select.items.get(candidates.get(0)) : null;
19060                                if (single != null && structFieldPaths.containsKey(single.get(0))) {
19061                                        for (ResultColumn field : single) {
19062                                                createCteSelectRelation(process, targetColumn, field);
19063                                                StructFieldPath path = structFieldPaths.get(field);
19064                                                ResultColumn node = builtFieldNodeAt(targetColumn, path.path);
19065                                                if (node == null) {
19066                                                        node = structFieldNode(targetColumn, path.path, cteColumns.getObjectName(i), path.item);
19067                                                }
19068                                                DataFlowRelationship nodeRelation = modelFactory.createDataFlowRelation();
19069                                                nodeRelation.setEffectType(EffectType.select);
19070                                                nodeRelation.setTarget(new ResultColumnRelationshipElement(node));
19071                                                nodeRelation.addSource(new ResultColumnRelationshipElement(field));
19072                                                nodeRelation.setProcess(process);
19073                                        }
19074                                        continue;
19075                                }
19076                                for (int k : candidates) {
19077                                        for (ResultColumn sourceColumn : select.items.get(k)) {
19078                                                // a CTE column renames a position: the names a star links stay the star's, never the column's
19079                                                createCteSelectRelation(process, targetColumn, sourceColumn, true);
19080                                                setOperationFieldNodes(targetColumn, sourceColumn);
19081                                        }
19082                                }
19083                        }
19084                }
19085        }
19086
19087        /** The first result column of each item, which says whether the item is a star. */
19088        private static List<ResultColumn> groupHeads(List<List<ResultColumn>> groups) {
19089                List<ResultColumn> heads = new ArrayList<ResultColumn>(groups.size());
19090                for (List<ResultColumn> group : groups) {
19091                        heads.add(group.get(0));
19092                }
19093                return heads;
19094        }
19095
19096        /**
19097         * Feeds {@code tableColumn}, the target at {@code position} of a list, from each select of {@code selects}
19098         * ({@link #feedCandidateItems}).
19099         */
19100        private void feedSelectPositions(TableColumn tableColumn, List<SelectPositions> selects, int position,
19101                        EffectType effectType, Process process) {
19102                for (SelectPositions select : selects) {
19103                        if (!select.candidates.get(position).isEmpty()) {
19104                                feedCandidateItems(tableColumn, tableColumn.getColumnObject(), select.items,
19105                                                select.candidates.get(position), effectType, process);
19106                        }
19107                }
19108        }
19109
19110        /**
19111         * Feeds {@code tableColumn} from the select items {@link #candidateSelectItems} names for it: the one item at its
19112         * position when the position is proven, a STRUCT item by its fields (rule R8); every candidate item, whole, when
19113         * it is not.
19114         */
19115        private void feedCandidateItems(TableColumn tableColumn, TObjectName position, List<List<ResultColumn>> groups,
19116                        List<Integer> candidates, EffectType effectType, Process process) {
19117                if (candidates.size() == 1) {
19118                        List<ResultColumn> group = groups.get(candidates.get(0));
19119                        if (group.size() > 1 || structFieldPaths.containsKey(group.get(0))) {
19120                                // one candidate: the sum rule proves the position, behind stars of unknown width too (owner ruling
19121                                // 2026-10-07)
19122                                feedFromStructItem(tableColumn, group, position, effectType, process, false);
19123                                return;
19124                        }
19125                }
19126                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
19127                relation.setEffectType(effectType);
19128                relation.setTarget(new TableColumnRelationshipElement(tableColumn));
19129                for (int k : candidates) {
19130                        for (ResultColumn column : groups.get(k)) {
19131                                relation.addSource(positionSource(column));
19132                        }
19133                }
19134                relation.setProcess(process);
19135                if (candidates.size() == 1) {
19136                        feedBuiltFieldNodes(tableColumn, groups.get(candidates.get(0)).get(0), effectType, process, false);
19137                }
19138        }
19139
19140        /** The select items of one select and, for each target position of a list, the indexes of those that can stand at it. */
19141        private static final class SelectPositions {
19142                final List<List<ResultColumn>> items;
19143                final List<List<Integer>> candidates;
19144
19145                SelectPositions(List<List<ResultColumn>> items, List<List<Integer>> candidates) {
19146                        this.items = items;
19147                        this.candidates = candidates;
19148                }
19149        }
19150
19151        /** {@link SelectPositions} of the select whose result columns are {@code columns}, for a list of {@code width} targets. */
19152        private SelectPositions selectPositions(List<ResultColumn> columns, int width) {
19153                return selectPositions(columns, width, null);
19154        }
19155
19156        /**
19157         * {@link SelectPositions} of the select whose result columns are {@code columns}, for a list of {@code width} targets
19158         * that, when {@code unique} is not null, may name only the first columns of the select; a column of the select named
19159         * like one of {@code unique} stands within the list ({@link #candidateSelectItems(List, int, boolean[])}).
19160         */
19161        private SelectPositions selectPositions(List<ResultColumn> columns, int width, List<TObjectName> unique) {
19162                List<List<ResultColumn>> items = selectItemGroups(columns);
19163                return new SelectPositions(items, candidateSelectItems(groupHeads(items), width, namedInList(items, unique)));
19164        }
19165
19166        /** For each item, whether it is a column named like one of {@code unique}; null when {@code unique} is. */
19167        private boolean[] namedInList(List<List<ResultColumn>> items, List<TObjectName> unique) {
19168                if (unique == null) {
19169                        return null;
19170                }
19171                boolean[] named = new boolean[items.size()];
19172                for (int k = 0; k < items.size(); k++) {
19173                        ResultColumn head = items.get(k).get(0);
19174                        for (TObjectName name : unique) {
19175                                named[k] |= !head.isStarColumn() && referencesColumn(name, head);
19176                        }
19177                }
19178                return named;
19179        }
19180
19181        /**
19182         * For a column list of a view or CREATE TABLE AS that may name fewer columns than its query has
19183         * ({@link #columnListMayBeShorter}): its names, which no other column of the query can have, as PostgreSQL rejects a
19184         * column name twice; none where a second column of a name is renamed (DuckDB). Null where the list names every
19185         * column, so that its length is the width of the query.
19186         */
19187        private List<TObjectName> shorterList(List<TObjectName> names) {
19188                if (!columnListMayBeShorter(option.getVendor())) {
19189                        return null;
19190                }
19191                return option.getVendor() == EDbVendor.dbvduckdb ? Collections.<TObjectName>emptyList() : names;
19192        }
19193
19194        /**
19195         * For the column list {@code names} of a derived table or a CTE that may name fewer columns than its query has
19196         * ({@link #columnListMayBeShorter}): its names the query reads through {@code table}, which no other column of the
19197         * query can have, as PostgreSQL rejects such a read as ambiguous; none where a second column of a name is renamed
19198         * (DuckDB). Null where the list names every column.
19199         */
19200        private List<TObjectName> shorterList(TObjectNameList names, TTable table) {
19201                if (names == null || !columnListMayBeShorter(option.getVendor())) {
19202                        return null;
19203                }
19204                List<TObjectName> read = new ArrayList<TObjectName>();
19205                TObjectNameList references = table == null ? null : table.getLinkedColumns();
19206                for (int i = 0; option.getVendor() != EDbVendor.dbvduckdb && i < names.size(); i++) {
19207                        for (int j = 0; references != null && j < references.size(); j++) {
19208                                if (DlineageUtil.sameColumnName(names.getObjectName(i), references.getObjectName(j))) {
19209                                        read.add(names.getObjectName(i));
19210                                        break;
19211                                }
19212                        }
19213                }
19214                return read;
19215        }
19216
19217        /**
19218         * Whether a column list of {@code vendor} may name fewer columns than its query has, the others keeping their names:
19219         * PostgreSQL (parse_cte.c, "we allow it to be shorter for consistency with Alias handling"; CREATE TABLE AS, "too
19220         * few column names are OK"), the databases built on it, and DuckDB (1.4.4).
19221         */
19222        private static boolean columnListMayBeShorter(EDbVendor vendor) {
19223                return vendor == EDbVendor.dbvpostgresql || vendor == EDbVendor.dbvgreenplum || vendor == EDbVendor.dbvedb
19224                                || vendor == EDbVendor.dbvgaussdb || vendor == EDbVendor.dbvduckdb;
19225        }
19226
19227        /** Whether a select of {@code selects} has an item that can stand at {@code position}. */
19228        private static boolean hasCandidates(List<SelectPositions> selects, int position) {
19229                for (SelectPositions select : selects) {
19230                        if (!select.candidates.get(position).isEmpty()) {
19231                                return true;
19232                        }
19233                }
19234                return false;
19235        }
19236
19237        /**
19238         * Mantis 4914: the {@link SelectPositions} of each select of the set operation {@code stmt}, for a list of
19239         * {@code width} targets; null when no select has a star of unknown width, or a select has no result set. The
19240         * columns of a set operation are named after one select, so one star there can stand for positions the other
19241         * selects fill with different columns: only the selects tell them apart, each against the width of the list.
19242         */
19243        private List<SelectPositions> setOperationPositions(TSelectSqlStatement stmt, int width) {
19244                return setOperationPositions(stmt, width, null);
19245        }
19246
19247        /**
19248         * {@link #setOperationPositions(TSelectSqlStatement, int)} for a list that, when {@code unique} is not null, may name
19249         * only the first columns ({@link #shorterList(List)}). Every select has the width of the set operation: a select
19250         * without a star states it, and the first select, which names the columns, bounds it where a column of it named
19251         * like one of {@code unique} stands within the list with no star after it. Without a bound the selects can run
19252         * past the list.
19253         */
19254        private List<SelectPositions> setOperationPositions(TSelectSqlStatement stmt, int width, List<TObjectName> unique) {
19255                List<ResultSet> selects = new ArrayList<ResultSet>();
19256                boolean star = false;
19257                Deque<TSelectSqlStatement> stack = new ArrayDeque<TSelectSqlStatement>();
19258                stack.push(stmt);
19259                while (!stack.isEmpty()) {
19260                        TSelectSqlStatement current = stack.pop();
19261                        if (current.isCombinedQuery()) {
19262                                // the right branch first, so that the left one comes first
19263                                if (current.getRightStmt() != null) {
19264                                        stack.push(current.getRightStmt());
19265                                }
19266                                if (current.getLeftStmt() != null) {
19267                                        stack.push(current.getLeftStmt());
19268                                }
19269                                continue;
19270                        }
19271                        ResultSet select = current.getResultColumnList() == null ? null : setOperationBranch(current);
19272                        if (select == null) {
19273                                return null;
19274                        }
19275                        selects.add(select);
19276                        star |= starColumnCount(select.getColumns()) > 0;
19277                }
19278                if (!star || selects.size() < 2) {
19279                        return null;
19280                }
19281                if (unique != null) {
19282                        for (ResultSet select : selects) {
19283                                if (starColumnCount(select.getColumns()) == 0) {
19284                                        width = Math.max(width, selectItemGroups(select.getColumns()).size());
19285                                        unique = null;
19286                                        break;
19287                                }
19288                        }
19289                }
19290                int widest = Integer.MAX_VALUE;
19291                if (unique != null) {
19292                        List<List<ResultColumn>> first = selectItemGroups(selects.get(0).getColumns());
19293                        widest = widest(groupHeads(first), width, namedInList(first, unique));
19294                }
19295                List<SelectPositions> positions = new ArrayList<SelectPositions>(selects.size());
19296                for (int s = 0; s < selects.size(); s++) {
19297                        positions.add(setOperationSelect(selects.get(s).getColumns(), width, widest,
19298                                        s == 0 ? unique : Collections.<TObjectName>emptyList(), unique != null));
19299                }
19300                // a column of the set operation whose sources are exactly the selects' items at a position feeds that position
19301                // itself, as before: only where a star stands for several positions do the selects tell them apart
19302                Object model = modelManager.getModel(stmt);
19303                if (model instanceof ResultSet) {
19304                        SelectPositions own = setOperationSelect(((ResultSet) model).getColumns(), width, widest, unique,
19305                                        unique != null);
19306                        boolean through = false;
19307                        for (int i = 0; i < width; i++) {
19308                                List<Integer> column = own.candidates.get(i);
19309                                if (column.size() == 1 && carriesExactly(own.items.get(column.get(0)), positions, i)) {
19310                                        for (SelectPositions select : positions) {
19311                                                select.candidates.get(i).clear();
19312                                        }
19313                                        through = true;
19314                                } else {
19315                                        column.clear();
19316                                }
19317                        }
19318                        if (through) {
19319                                positions.add(0, own);
19320                        }
19321                }
19322                return positions;
19323        }
19324
19325        /**
19326         * {@link SelectPositions} of a select of a set operation: for a list of {@code width} targets, when not
19327         * {@code open}; for one that may name only the first of at most {@code widest} columns, when {@code open}.
19328         */
19329        private SelectPositions setOperationSelect(List<ResultColumn> columns, int width, int widest,
19330                        List<TObjectName> unique, boolean open) {
19331                if (!open || widest == Integer.MAX_VALUE) {
19332                        return selectPositions(columns, width, open ? unique : null);
19333                }
19334                List<List<ResultColumn>> items = selectItemGroups(columns);
19335                List<ResultColumn> heads = groupHeads(items);
19336                List<List<Integer>> candidates = new ArrayList<List<Integer>>(width);
19337                for (int i = 0; i < width; i++) {
19338                        candidates.add(new ArrayList<Integer>());
19339                }
19340                // the set operation is from width to widest columns wide: the items that can stand at a position at any of them
19341                for (int total = width; total <= widest; total++) {
19342                        List<List<Integer>> at = candidateSelectItems(heads, total);
19343                        for (int i = 0; i < width; i++) {
19344                                for (int k : at.get(i)) {
19345                                        if (!candidates.get(i).contains(k)) {
19346                                                candidates.get(i).add(k);
19347                                        }
19348                                }
19349                        }
19350                }
19351                for (List<Integer> position : candidates) {
19352                        Collections.sort(position);
19353                }
19354                return new SelectPositions(items, candidates);
19355        }
19356
19357        /**
19358         * The widest a select with the items {@code items} can be under a list of {@code width} names that may name only
19359         * its first columns, where an item that is {@code named} stands within the list: past the last such item only the
19360         * items after it, unless one of them is a star. MAX_VALUE without such a bound.
19361         */
19362        private static int widest(List<ResultColumn> items, int width, boolean[] named) {
19363                int last = -1;
19364                for (int k = 0; k < items.size(); k++) {
19365                        if (named[k]) {
19366                                last = k;
19367                        }
19368                }
19369                if (last < 0) {
19370                        return Integer.MAX_VALUE;
19371                }
19372                for (int k = last + 1; k < items.size(); k++) {
19373                        if (items.get(k).getName() != null && items.get(k).isStarColumn()) {
19374                                return Integer.MAX_VALUE;
19375                        }
19376                }
19377                return Math.max(width, width - 1 + items.size() - last);
19378        }
19379
19380        /**
19381         * Whether the columns {@code item} of a set operation read exactly the items the selects {@code positions} have at
19382         * {@code position}, as the set operation's mapping recorded them ({@link #setColumnSources}).
19383         */
19384        private boolean carriesExactly(List<ResultColumn> item, List<SelectPositions> positions, int position) {
19385                Set<ResultColumn> sources = Collections.newSetFromMap(new IdentityHashMap<ResultColumn, Boolean>());
19386                for (ResultColumn column : item) {
19387                        List<ResultColumn> recorded = setColumnSources.get(column);
19388                        if (recorded == null) {
19389                                return false;
19390                        }
19391                        sources.addAll(recorded);
19392                }
19393                Set<ResultColumn> items = Collections.newSetFromMap(new IdentityHashMap<ResultColumn, Boolean>());
19394                for (SelectPositions select : positions) {
19395                        for (int k : select.candidates.get(position)) {
19396                                items.addAll(select.items.get(k));
19397                        }
19398                }
19399                return sources.equals(items);
19400        }
19401
19402        /**
19403         * For each declared target column of an INSERT, the select-list items that could supply it when
19404         * the select list contains a wildcard whose width is unknown.
19405         *
19406         * <p>{@code INSERT INTO t SELECT c.*, o.*} projects {@code |c| + |o|} columns onto t's declared
19407         * columns, but with neither width known the boundary between the two wildcards is unknown as
19408         * well, so NO target column can be attributed to one of them. Binding by ordinal — which is
19409         * what this method replaces — produced a confident and arbitrary split
19410         * ({@code CUSTOMERS.* -> ORDERNAME}, {@code ORDERS.* -> ORDERALTNUM}) that no fact in the
19411         * script supports, and it also let a partial wildcard expansion name a specific column as the
19412         * first one behind the star.
19413         *
19414         * <p>The interval each item can occupy follows from the widths that ARE known, plus the fact
19415         * that the widths must SUM to the number of target columns. An item starts after everything
19416         * before it, so its earliest position is bounded below both by the definitely-one-column items
19417         * before it and by how little the items from it onward can cover; its latest position is
19418         * bounded above both by the definitely-one-column items after it and by how far the items
19419         * before it can push it. A select list with no unknown widths therefore yields exactly one
19420         * position per item — the ordinal mapping — which is why this is a generalization of the
19421         * positional rule and not a different rule. So does a trailing concrete item after any number
19422         * of wildcards, which the sum constraint pins to the last position.
19423         *
19424         * <p>The result is a candidate SET, deliberately over-approximate where the mapping is
19425         * undetermined: every candidate carries the {@code *} marker that says the column behind it is
19426         * unknown. A narrower answer would have to invent the boundary, and an empty answer would hide
19427         * the dependency altogether, which is the failure mode nobody can see in the output.
19428         *
19429         * @param items select-list result columns, in projection order
19430         * @param targetWidth number of declared target columns
19431         * @return for each target position, the indexes into {@code items} that could supply it
19432         */
19433        private static List<List<Integer>> candidateSelectItems(List<ResultColumn> items, int targetWidth) {
19434                return candidateSelectItems(items, targetWidth, null);
19435        }
19436
19437        /**
19438         * {@link #candidateSelectItems(List, int)} for a list that may name only the first columns of the select when
19439         * {@code listed} is not null ({@link #shorterList(List)}): the select can run past the list, so the widths reach it
19440         * but need not sum to it. A column past the list keeps its name, so an item named like a name of the list that no
19441         * other column can have ({@code listed[k]}) stands within it, and so does every item before it.
19442         */
19443        private static List<List<Integer>> candidateSelectItems(List<ResultColumn> items, int targetWidth, boolean[] listed) {
19444                int size = items.size();
19445                int[] minWidth = new int[size];
19446                int[] maxWidth = new int[size];
19447                for (int k = 0; k < size; k++) {
19448                        boolean star = items.get(k).getName() != null && items.get(k).isStarColumn();
19449                        // A wildcard of unknown width may contribute nothing at all (SELECT c.* EXCEPT(...) over
19450                        // a two-column table) or the whole target, so it constrains neither end.
19451                        minWidth[k] = star ? 0 : 1;
19452                        maxWidth[k] = star ? targetWidth : 1;
19453                }
19454                // the latest position each item can start at, and the first item past the last one
19455                long[] latestStart = new long[size + 1];
19456                latestStart[size] = Long.MAX_VALUE;
19457                for (int k = size - 1; listed != null && k >= 0; k--) {
19458                        latestStart[k] = latestStart[k + 1] == Long.MAX_VALUE ? Long.MAX_VALUE : latestStart[k + 1] - minWidth[k];
19459                        if (listed[k]) {
19460                                latestStart[k] = Math.min(latestStart[k], targetWidth - 1);
19461                        }
19462                }
19463                List<List<Integer>> candidates = new ArrayList<List<Integer>>(targetWidth);
19464                for (int i = 0; i < targetWidth; i++) {
19465                        candidates.add(new ArrayList<Integer>());
19466                }
19467                for (int k = 0; k < size; k++) {
19468                        int minStart = 0;
19469                        int maxStart = 0;
19470                        for (int l = 0; l < k; l++) {
19471                                minStart += minWidth[l];
19472                                maxStart += maxWidth[l];
19473                        }
19474                        int minAfter = 0;
19475                        for (int l = k + 1; l < size; l++) {
19476                                minAfter += minWidth[l];
19477                        }
19478                        // Widths sum to targetWidth, so item k cannot start earlier than the point from which
19479                        // everything from k onward could still fill the rest. That is what pins a trailing
19480                        // concrete item to the last position instead of leaving it a candidate for every one.
19481                        long maxFromHere = 0;
19482                        for (int l = k; l < size; l++) {
19483                                maxFromHere += maxWidth[l];
19484                        }
19485                        int lowest = Math.max(minStart, (int) Math.max(0, targetWidth - maxFromHere));
19486                        // a list that may be shorter: the items after this one need not fit into the list, but a star ends before
19487                        // the latest start of the item after it
19488                        long highest = listed == null ? Math.min(targetWidth - 1 - minAfter, maxStart + maxWidth[k] - 1)
19489                                        : Math.min(maxStart + maxWidth[k] - 1, minWidth[k] == 0 ? latestStart[k] - 1 : latestStart[k]);
19490                        for (int i = Math.max(0, lowest); i <= highest && i < targetWidth; i++) {
19491                                candidates.get(i).add(k);
19492                        }
19493                }
19494                return candidates;
19495        }
19496
19497        private void analyzeInsertStmt(TInsertSqlStatement stmt) {
19498                Map<Table, List<TObjectName>> insertTableKeyMap = new LinkedHashMap<Table, List<TObjectName>>();
19499                Map<Table, List<TResultColumn>> insertTableValueMap = new LinkedHashMap<Table, List<TResultColumn>>();
19500                // The insert target's columns, per target table model (a table model is the table's identity).
19501                Map<Table, List<TableColumn>> tableColumnMap = new LinkedHashMap<Table, List<TableColumn>>();
19502                List<Table> inserTables = new ArrayList<Table>();
19503                // INTO clauses of a multi-table INSERT that name their own columns or values, mapped by
19504                // analyzeInsertIntoClause instead of the per-table mapping below
19505                List<SelfMappedInto> selfMappedIntoClauses = new ArrayList<SelfMappedInto>();
19506                // for an entry of inserTables that is an INTO clause of a conditional multi-table INSERT: its position and
19507                // the WHEN conditions routing rows to it
19508                Map<Integer, Pair<Integer, List<TExpression>>> plainIntoConditions = new HashMap<Integer, Pair<Integer, List<TExpression>>>();
19509                // the columns each INTO clause of a conditional multi-table INSERT writes, with its WHEN conditions
19510                List<WhenScope> whenScopes = new ArrayList<WhenScope>();
19511                boolean hasInsertColumns = false;
19512                
19513                EffectType effectType = EffectType.insert;
19514                if(stmt.getInsertToken()!=null && stmt.getInsertToken().toString().toLowerCase(Locale.ROOT).startsWith("replace")) {
19515                        effectType = EffectType.replace;
19516                }
19517                
19518                if (stmt.getInsertConditions() != null && stmt.getInsertConditions().size() > 0) {
19519                        // the INTO clauses of every WHEN branch, then those of the ELSE branch, which had no lineage before,
19520                        // each with the WHEN conditions that route rows to it
19521                        List<TInsertIntoValue> intoClauses = new ArrayList<TInsertIntoValue>();
19522                        List<List<TExpression>> intoConditions = new ArrayList<List<TExpression>>();
19523                        int branches = stmt.getInsertConditions().size();
19524                        for (int i = 0; i < branches; i++) {
19525                                TInsertCondition condition = stmt.getInsertConditions().getElement(i);
19526                                List<TExpression> governing = governingConditions(stmt, i);
19527                                for (int j = 0; j < condition.getInsertIntoValues().size(); j++) {
19528                                        intoClauses.add(condition.getInsertIntoValues().getElement(j));
19529                                        intoConditions.add(governing);
19530                                }
19531                        }
19532                        if (stmt.getElseIntoValues() != null) {
19533                                List<TExpression> governing = governingConditions(stmt, branches);
19534                                for (int j = 0; j < stmt.getElseIntoValues().size(); j++) {
19535                                        intoClauses.add(stmt.getElseIntoValues().getElement(j));
19536                                        intoConditions.add(governing);
19537                                }
19538                        }
19539                        for (int k = 0; k < intoClauses.size(); k++) {
19540                                TInsertIntoValue value = intoClauses.get(k);
19541                                TTable table = value.getTable();
19542                                Table tableModel = modelFactory.createTable(table);
19543                                if (mapsItsOwnColumns(value)) {
19544                                        selfMappedIntoClauses.add(new SelfMappedInto(tableModel, value, insertIntoTargets(tableModel, value),
19545                                                        k, intoConditions.get(k)));
19546                                        tableModel.addProcess(modelFactory.createProcess(stmt));
19547                                        continue;
19548                                }
19549
19550                                inserTables.add(tableModel);
19551                                plainIntoConditions.put(inserTables.size() - 1, new Pair<Integer, List<TExpression>>(k, intoConditions.get(k)));
19552                                List<TObjectName> keyMap = new ArrayList<TObjectName>();
19553                                List<TResultColumn> valueMap = new ArrayList<TResultColumn>();
19554                                insertTableKeyMap.put(tableModel, keyMap);
19555                                insertTableValueMap.put(tableModel, valueMap);
19556
19557                                List<TableColumn> tableColumns = bindInsertTableColumn(tableModel, value, keyMap, valueMap);
19558                                if (tableColumnMap.get(tableModel) == null
19559                                                && !tableColumns.isEmpty()) {
19560                                        tableColumnMap.put(tableModel,
19561                                                        tableColumns);
19562                                }
19563
19564                                // if (stmt.getSubQuery() != null)
19565                                {
19566                                        Process process = modelFactory.createProcess(stmt);
19567                                        tableModel.addProcess(process);
19568                                }
19569                        }
19570                        hasInsertColumns = true;
19571                } else if (stmt.getInsertIntoValues() != null && stmt.getInsertIntoValues().size() > 0) {
19572                        for (int i = 0; i < stmt.getInsertIntoValues().size(); i++) {
19573                                TInsertIntoValue value = stmt.getInsertIntoValues().getElement(i);
19574                                TTable table = value.getTable();
19575                                Table tableModel = modelFactory.createTable(table);
19576                                if (mapsItsOwnColumns(value)) {
19577                                        selfMappedIntoClauses.add(new SelfMappedInto(tableModel, value, insertIntoTargets(tableModel, value), i,
19578                                                        Collections.<TExpression>emptyList()));
19579                                        tableModel.addProcess(modelFactory.createProcess(stmt));
19580                                        continue;
19581                                }
19582
19583                                inserTables.add(tableModel);
19584                                List<TObjectName> keyMap = new ArrayList<TObjectName>();
19585                                List<TResultColumn> valueMap = new ArrayList<TResultColumn>();
19586                                insertTableKeyMap.put(tableModel, keyMap);
19587                                insertTableValueMap.put(tableModel, valueMap);
19588
19589                                List<TableColumn> tableColumns = bindInsertTableColumn(tableModel, value, keyMap, valueMap);
19590                                if (tableColumnMap.get(tableModel) == null && !tableColumns.isEmpty()) {
19591                                        tableColumnMap.put(tableModel, tableColumns);
19592                                }
19593
19594                                // if (stmt.getSubQuery() != null)
19595                                {
19596                                        Process process = modelFactory.createProcess(stmt);
19597                                        tableModel.addProcess(process);
19598                                }
19599                        }
19600                        hasInsertColumns = true;
19601                } else if (stmt.getColumnList() != null && stmt.getColumnList().size() > 0) {
19602                        TTable table = stmt.getTargetTable();
19603                        Table tableModel = modelFactory.createTable(table);
19604
19605                        inserTables.add(tableModel);
19606                        List<TObjectName> keyMap = new ArrayList<TObjectName>();
19607                        insertTableKeyMap.put(tableModel, keyMap);
19608                        List<TableColumn> tableColumns = new ArrayList<TableColumn>();
19609                        for (int i = 0; i < stmt.getColumnList().size(); i++) {
19610                                TableColumn tableColumn = modelFactory.createInsertTableColumn(tableModel,
19611                                                stmt.getColumnList().getObjectName(i));
19612                                tableColumns.add(tableColumn);
19613                        }
19614                        if (tableColumnMap.get(tableModel) == null && !tableColumns.isEmpty()) {
19615                                tableColumnMap.put(tableModel, tableColumns);
19616                        }
19617
19618                        // if (stmt.getSubQuery() != null)
19619                        {
19620                                Process process = modelFactory.createProcess(stmt);
19621                                tableModel.addProcess(process);
19622                        }
19623                        hasInsertColumns = true;
19624                } else if (stmt.getOutputClause() != null && stmt.getOutputClause().getSelectItemList().size() > 0) {
19625                        TTable table = stmt.getTargetTable();
19626                        Table tableModel = modelFactory.createTable(table);
19627
19628                        inserTables.add(tableModel);
19629                        List<TObjectName> keyMap = new ArrayList<TObjectName>();
19630                        insertTableKeyMap.put(tableModel, keyMap);
19631                        List<TableColumn> tableColumns = new ArrayList<TableColumn>();
19632                        for (int i = 0; i < stmt.getOutputClause().getSelectItemList().size(); i++) {
19633                                TObjectName columnName = stmt.getOutputClause().getSelectItemList().getResultColumn(i).getFieldAttr();
19634                                if (columnName.getPseudoTableType() != EPseudoTableType.none) {
19635                                        // Phase 1 already swapped tokens; getColumnNameOnly() returns actual column name
19636                                        String column = columnName.getColumnNameOnly();
19637                                        columnName = new TObjectName();
19638                                        columnName.setString(column);
19639                                } else {
19640                                        String column = columnName.toString().toLowerCase(Locale.ROOT);
19641                                        if ((column.startsWith("inserted.") || column.startsWith("deleted."))
19642                                                        && columnName.getPropertyToken() != null) {
19643                                                column = columnName.getPropertyToken().getAstext();
19644                                                columnName = new TObjectName();
19645                                                columnName.setString(column);
19646                                        }
19647                                }
19648                                TableColumn tableColumn = modelFactory.createInsertTableColumn(tableModel, columnName);
19649                                tableColumns.add(tableColumn);
19650                        }
19651                        if (tableColumnMap.get(tableModel) == null && !tableColumns.isEmpty()) {
19652                                tableColumnMap.put(tableModel, tableColumns);
19653                        }
19654
19655                        // if (stmt.getSubQuery() != null)
19656                        {
19657                                Process process = modelFactory.createProcess(stmt);
19658                                tableModel.addProcess(process);
19659                        }
19660                        hasInsertColumns = true;
19661                } else {
19662                        TTable table = stmt.getTargetTable();
19663                        Table tableModel;
19664                        if (table != null) {
19665                                tableModel = modelFactory.createTable(table);
19666                                // if (stmt.getSubQuery() != null)
19667                                {
19668                                        Process process = modelFactory.createProcess(stmt);
19669                                        tableModel.addProcess(process);
19670                                }
19671                                if (tableModel.getColumns() == null || tableModel.getColumns().isEmpty()) {
19672                                        tableModel.addColumnsFromSQLEnv();
19673                                }
19674                        } else if (stmt.getDirectoryName() != null) {
19675                                tableModel = modelFactory.createTableByName(stmt.getDirectoryName(), true);
19676                                tableModel.setPath(true);
19677                                tableModel.setCreateTable(true);
19678                                TObjectName fileUri = new TObjectName();
19679                                fileUri.setString("uri=" + stmt.getDirectoryName());
19680                                TableColumn tableColumn = modelFactory.createFileUri(tableModel, fileUri);
19681                                // if (stmt.getSubQuery() != null)
19682                                {
19683                                        Process process = modelFactory.createProcess(stmt);
19684                                        tableModel.addProcess(process);
19685                                }
19686                        } else {
19687                                ErrorInfo errorInfo = new ErrorInfo();
19688                                errorInfo.setErrorType(ErrorInfo.ANALYZE_ERROR);
19689                                errorInfo.setErrorMessage("Can't get target table. InsertSqlStatement is " + stmt.toString());
19690                                errorInfo.setStartPosition(new Pair3<Long, Long, String>(stmt.getStartToken().lineNo,
19691                                                stmt.getStartToken().columnNo, ModelBindingManager.getGlobalHash()));
19692                                errorInfo.setEndPosition(new Pair3<Long, Long, String>(stmt.getEndToken().lineNo,
19693                                                stmt.getEndToken().columnNo + stmt.getEndToken().getAstext().length(),
19694                                                ModelBindingManager.getGlobalHash()));
19695                                errorInfo.fillInfo(this);
19696                                errorInfos.add(errorInfo);
19697                                return;
19698                        }
19699                        inserTables.add(tableModel);
19700                        if (table != null
19701                                        && tableColumnMap.get(tableModel) == null) {
19702                                if (tableModel.getColumns() != null && !tableModel.getColumns().isEmpty()) {
19703                                        tableColumnMap.put(tableModel,
19704                                                        tableModel.getColumns());
19705                                } else {
19706                                        tableColumnMap.put(tableModel, null);
19707                                }
19708                        }
19709                }
19710
19711                if (stmt.getSubQuery() != null) {
19712                        analyzeSelectStmt(stmt.getSubQuery());
19713                }
19714
19715                Set<Table> filteredTables = new HashSet<Table>();
19716                for (SelfMappedInto into : selfMappedIntoClauses) {
19717                        List<TableColumn> targets = analyzeInsertIntoClause(stmt, into, effectType, filteredTables.add(into.table));
19718                        if (!into.conditions.isEmpty()) {
19719                                whenScopes.add(new WhenScope(into.position, targets, into.conditions));
19720                        }
19721                }
19722
19723                // what the per-table mapping below writes for an INTO clause of a conditional INSERT, for its WHEN impact
19724                Set<Relationship> relationsBefore = null;
19725                Table capturedTable = null;
19726                Pair<Integer, List<TExpression>> capturedInto = null;
19727                int tableIndex = -1;
19728                Iterator<Table> tableIter = inserTables.iterator();
19729                while (tableIter.hasNext()) {
19730                        if (capturedInto != null) {
19731                                whenScopes.add(new WhenScope(capturedInto.first, columnsWrittenSince(relationsBefore, capturedTable),
19732                                                capturedInto.second));
19733                                capturedInto = null;
19734                        }
19735                        Table tableModel = tableIter.next();
19736                        tableIndex++;
19737                        Pair<Integer, List<TExpression>> intoConditionsOfTable = plainIntoConditions.get(tableIndex);
19738                        if (intoConditionsOfTable != null && !intoConditionsOfTable.second.isEmpty()) {
19739                                relationsBefore = relationSnapshot();
19740                                capturedTable = tableModel;
19741                                capturedInto = intoConditionsOfTable;
19742                        }
19743                        List<TableColumn> tableColumns = tableColumnMap.get(tableModel);
19744                        List<TObjectName> keyMap = insertTableKeyMap.get(tableModel);
19745                        List<TResultColumn> valueMap = insertTableValueMap.get(tableModel);
19746                        boolean initColumn = (hasInsertColumns && tableColumns != null && !containStarColumn(tableColumns));
19747
19748                        if (stmt.getSubQuery() != null) {
19749
19750                                List<TSelectSqlStatement> subquerys = new ArrayList<TSelectSqlStatement>();
19751                                if (stmt.getSubQuery().getResultColumnList() != null || stmt.getSubQuery().getTransformClause() != null) {
19752                                        subquerys.add(stmt.getSubQuery());
19753                                } else if (stmt.getSubQuery().getValueClause() != null
19754                                                && stmt.getSubQuery().getValueClause().getRows() != null) {
19755                                        for (TResultColumnList resultColumnList : stmt.getSubQuery().getValueClause().getRows()) {
19756                                                for(TResultColumn resultColumn: resultColumnList) {
19757                                                        if(resultColumn.getExpr()!=null && resultColumn.getExpr().getSubQuery()!=null) {
19758                                                                analyzeSelectStmt(resultColumn.getExpr().getSubQuery());
19759                                                                subquerys.add(resultColumn.getExpr().getSubQuery());
19760                                                        }
19761                                                }
19762                                        }
19763                                }
19764
19765                                for(TSelectSqlStatement subquery: subquerys) {
19766                                        if ((tableModel.isCreateTable() && tableModel.getColumns() != null)
19767                                                        || (subquery.getSetOperatorType() == ESetOperatorType.none
19768                                                                        && stmt.getColumnList() != null && stmt.getColumnList().size() > 0)) {
19769
19770                                                ResultSet resultSetModel = null;
19771
19772                                                if (subquery != null) {
19773                                                        resultSetModel = (ResultSet) modelManager.getModel(subquery);
19774                                                }
19775
19776                                                TResultColumnList resultset = subquery.getResultColumnList();
19777                                                if (resultSetModel == null && resultset != null) {
19778                                                        resultSetModel = (ResultSet) modelManager.getModel(resultset);
19779                                                }
19780
19781                                                if (resultSetModel == null) {
19782                                                        ErrorInfo errorInfo = new ErrorInfo();
19783                                                        errorInfo.setErrorType(ErrorInfo.ANALYZE_ERROR);
19784                                                        errorInfo.setErrorMessage("Can't get resultset model");
19785                                                        errorInfo.setStartPosition(new Pair3<Long, Long, String>(resultset.getStartToken().lineNo,
19786                                                                        resultset.getStartToken().columnNo, ModelBindingManager.getGlobalHash()));
19787                                                        errorInfo.setEndPosition(new Pair3<Long, Long, String>(resultset.getEndToken().lineNo,
19788                                                                        resultset.getEndToken().columnNo + resultset.getEndToken().getAstext().length(),
19789                                                                        ModelBindingManager.getGlobalHash()));
19790                                                        errorInfos.add(errorInfo);
19791                                                }
19792
19793                                                if (!resultSetModel.getRelationRows().getHoldRelations().isEmpty()) {
19794                                                        ImpactRelationship impactRelation = modelFactory.createImpactRelation();
19795                                                        impactRelation.setEffectType(effectType);
19796                                                        impactRelation.addSource(new RelationRowsRelationshipElement<ResultSetRelationRows>(
19797                                                                        resultSetModel.getRelationRows()));
19798                                                        impactRelation.setTarget(new RelationRowsRelationshipElement<TableRelationRows>(
19799                                                                        tableModel.getRelationRows()));
19800                                                }
19801
19802                                                int resultSetSize = resultSetModel.getColumns().size();
19803                                                int j = 0;
19804                                                int starIndex = 0;
19805                                                TObjectNameList items = stmt.getColumnList();
19806                                                List<String> itemNames = new ArrayList<String>();
19807                                                int starColumnCount = 0;
19808                                                for (ResultColumn item : resultSetModel.getColumns()) {
19809                                                        if (item.isStarColumn()) {
19810                                                                starColumnCount += 1;
19811                                                        }
19812                                                }
19813                                                // a set operation with a star of unknown width: each listed column reads, from each of its
19814                                                // selects, the items that can stand at its position (Mantis 4914)
19815                                                List<SelectPositions> setSelects = items != null && subquery.isCombinedQuery()
19816                                                                ? setOperationPositions(subquery, items.size()) : null;
19817                                                if (items != null && (setSelects != null || starColumnCount > 1)) {
19818                                                        // Same undetermined mapping as the branch below, for an INSERT that names its
19819                                                        // target columns: the listed columns are the target positions, and more than
19820                                                        // one unknown-width wildcard leaves it unknown which of them feeds which.
19821                                                        // a select item that builds a STRUCT is one item, all of its fields (rule R8)
19822                                                        List<SelectPositions> selects = setSelects != null ? setSelects
19823                                                                        : Collections.singletonList(selectPositions(resultSetModel.getColumns(), items.size()));
19824                                                        for (int i = 0; i < items.size(); i++) {
19825                                                                TObjectName column = items.getObjectName(i);
19826                                                                if (column.getDbObjectType() == EDbObjectType.variable) {
19827                                                                        continue;
19828                                                                }
19829                                                                if (column.getColumnNameOnly().startsWith("@")
19830                                                                                && (option.getVendor() == EDbVendor.dbvmssql
19831                                                                                                || option.getVendor() == EDbVendor.dbvazuresql)) {
19832                                                                        continue;
19833                                                                }
19834                                                                if (column.getColumnNameOnly().startsWith(":")
19835                                                                                && (option.getVendor() == EDbVendor.dbvhana
19836                                                                                                || option.getVendor() == EDbVendor.dbvteradata)) {
19837                                                                        continue;
19838                                                                }
19839                                                                if (!hasCandidates(selects, i)) {
19840                                                                        continue;
19841                                                                }
19842                                                                TableColumn tableColumn = matchColumn(tableModel.getColumns(), column);
19843                                                                if (tableColumn == null) {
19844                                                                        if (tableModel.isCreateTable() && !containStarColumn(tableModel.getColumns())) {
19845                                                                                if (tableModel.getColumns().size() <= i) {
19846                                                                                        continue;
19847                                                                                }
19848                                                                                tableColumn = tableModel.getColumns().get(i);
19849                                                                        } else {
19850                                                                                tableColumn = modelFactory.createTableColumn(tableModel, column, false);
19851                                                                                if (tableColumn == null) {
19852                                                                                        continue;
19853                                                                                }
19854                                                                        }
19855                                                                }
19856                                                                feedSelectPositions(tableColumn, selects, i, effectType, modelFactory.createProcess(stmt));
19857                                                        }
19858                                                } else if (items != null) {
19859                                                        for (int i = 0; i < items.size() && j < resultSetSize; i++) {
19860                                                                TObjectName column = items.getObjectName(i);
19861
19862                                                                if (column.getDbObjectType() == EDbObjectType.variable) {
19863                                                                        continue;
19864                                                                }
19865
19866                                                                if (column.getColumnNameOnly().startsWith("@")
19867                                                                                && (option.getVendor() == EDbVendor.dbvmssql
19868                                                                                                || option.getVendor() == EDbVendor.dbvazuresql)) {
19869                                                                        continue;
19870                                                                }
19871
19872                                                                if (column.getColumnNameOnly().startsWith(":")
19873                                                                                && (option.getVendor() == EDbVendor.dbvhana
19874                                                                                                || option.getVendor() == EDbVendor.dbvteradata)) {
19875                                                                        continue;
19876                                                                }
19877
19878                                                                ResultColumn resultColumn = resultSetModel.getColumns().get(j);
19879                                                                List<ResultColumn> structItem = null;
19880                                                                if (!resultSetModel.getColumns().get(j).isStarColumn()) {
19881                                                                        // a select item that builds a STRUCT is one position, all of its fields (rule R8)
19882                                                                        int width = selectItemWidth(resultSetModel.getColumns(), j);
19883                                                                        if (width > 1 || structFieldPaths.containsKey(resultColumn)) {
19884                                                                                structItem = new ArrayList<ResultColumn>(
19885                                                                                                resultSetModel.getColumns().subList(j, j + width));
19886                                                                        }
19887                                                                        j += width;
19888                                                                } else {
19889                                                                        starIndex++;
19890                                                                        if (selectItemsFrom(resultSetModel.getColumns(), j, resultSetSize) == items.size() - i) {
19891                                                                                j++;
19892
19893                                                                        }
19894                                                                }
19895                                                                if (column != null) {
19896                                                                        TableColumn tableColumn;
19897                                                                        // if (!initColumn) {
19898                                                                        tableColumn = matchColumn(tableModel.getColumns(), column);
19899                                                                        if (tableColumn == null) {
19900                                                                                if (tableModel.isCreateTable() && !containStarColumn(tableModel.getColumns())) {
19901                                                                                        if (tableModel.getColumns().size() <= i) {
19902                                                                                                continue;
19903                                                                                        }
19904                                                                                        tableColumn = tableModel.getColumns().get(i);
19905                                                                                } else {
19906                                                                                        tableColumn = modelFactory.createTableColumn(tableModel, column, false);
19907                                                                                        if(tableColumn == null) {
19908                                                                                                continue;
19909                                                                                        }
19910                                                                                }
19911                                                                        }
19912//                                                      } else {
19913//                                                              tableColumn = matchColumn(tableColumns, column);
19914//                                                              if (tableColumn == null) {
19915//                                                                      continue;
19916//                                                              }
19917//                                                      }
19918                                                                        if (structItem != null) {
19919                                                                                // the select list is as wide as the column list, so behind one star of unknown
19920                                                                                // width the items after it still have proven positions (owner ruling 2026-10-07)
19921                                                                                feedFromStructItem(tableColumn, structItem, column, effectType,
19922                                                                                                modelFactory.createProcess(stmt), false);
19923                                                                                for (ResultColumn field : structItem) {
19924                                                                                        itemNames.add(field.getName());
19925                                                                                }
19926                                                                                continue;
19927                                                                        }
19928                                                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
19929                                                                        relation.setEffectType(effectType);
19930                                                                        relation.setTarget(new TableColumnRelationshipElement(tableColumn));
19931                                                                        if (resultColumn.hasStarLinkColumn()
19932                                                                                        && resultColumn.starLinkCount() > starIndex - 1 && starColumnCount<=1) {
19933                                                                                boolean find = false;
19934                                                                                while (resultColumn.starLinkCount() > starIndex - 1) {
19935                                                                                        TObjectName name = resultColumn.getStarLinkColumnName(starIndex - 1);
19936                                                                                        if (itemNames.contains(name.toString())) {
19937                                                                                                starIndex++;
19938                                                                                                continue;
19939                                                                                        }
19940                                                                                        ResultColumn expandStarColumn = modelFactory
19941                                                                                                        .createResultColumn(resultSetModel, name, false);
19942                                                                                        relation.addSource(new ResultColumnRelationshipElement(expandStarColumn));
19943                                                                                        itemNames.add(resultColumn.getName());
19944                                                                                        find = true;
19945                                                                                        break;
19946                                                                                }
19947                                                                                if (!find) {
19948                                                                                        relation.addSource(new ResultColumnRelationshipElement(resultColumn));
19949                                                                                        itemNames.add(resultColumn.getName());
19950                                                                                }
19951                                                                        } else {
19952                                                                                relation.addSource(insertListSource(resultColumn, subquery));
19953                                                                                itemNames.add(resultColumn.getName());
19954                                                                        }
19955                                                                        Process process = modelFactory.createProcess(stmt);
19956                                                                        relation.setProcess(process);
19957                                                                        // the fields a STRUCT column carries (rule R8, Codex review of phase 3)
19958                                                                        feedBuiltFieldNodes(tableColumn, resultColumn, effectType, process, false);
19959                                                                }
19960                                                        }
19961                                                } else {
19962                                                        // STRUCT tree: a select item that builds a STRUCT fills the declared fields of the column
19963                                                        List<TableColumn> columns = insertPositionalColumns(tableModel, resultSetModel.getColumns());
19964                                                        boolean structLeaves = columns != tableModel.getColumns();
19965                                                        // a set operation with a star of unknown width: each column reads, from each of its selects,
19966                                                        // the items that can stand at its position (Mantis 4914)
19967                                                        setSelects = !structLeaves && subquery.isCombinedQuery()
19968                                                                        ? setOperationPositions(subquery, columns.size()) : null;
19969                                                        if (columns.size() == 1 && tableModel.isPath()) {
19970                                                                for(int i=0;i<resultSetSize;i++) {
19971                                                                        ResultColumn resultColumn = resultSetModel.getColumns().get(i);
19972                                                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
19973                                                                        relation.setEffectType(effectType);
19974                                                                        relation.setTarget(new TableColumnRelationshipElement(tableModel.getColumns().get(0)));
19975                                                                        if (resultColumn.hasStarLinkColumn()
19976                                                                                        && resultColumn.starLinkCount() > starIndex - 1) {
19977                                                                                ResultColumn expandStarColumn = modelFactory.createResultColumn(
19978                                                                                                resultSetModel, resultColumn.getStarLinkColumnName(starIndex - 1),
19979                                                                                                false);
19980                                                                                relation.addSource(new ResultColumnRelationshipElement(expandStarColumn));
19981                                                                        } else {
19982                                                                                relation.addSource(new ResultColumnRelationshipElement(resultColumn));
19983                                                                        }
19984                                                                        Process process = modelFactory.createProcess(stmt);
19985                                                                        relation.setProcess(process);
19986                                                                }
19987                                                        } else if (setSelects != null || starColumnCount > 1) {
19988                                                                // More than one wildcard of unknown width: the boundary between them is
19989                                                                // unknown, so no ordinal can say which one feeds which declared target
19990                                                                // column (see candidateSelectItems). Relate each target column to every
19991                                                                // select item that could supply it, and never to an expanded name picked
19992                                                                // by ordinal out of a partial expansion.
19993                                                                // a select item that builds a STRUCT is one item, all of its fields (rule R8)
19994                                                                List<SelectPositions> selects = setSelects != null ? setSelects
19995                                                                                : Collections.singletonList(selectPositions(resultSetModel.getColumns(), columns.size()));
19996                                                                for (int i = 0; i < columns.size(); i++) {
19997                                                                        if (columns.get(i).getName() == null) {
19998                                                                                continue;
19999                                                                        }
20000                                                                        TableColumn tableColumn = (structLeaves ? columns.get(i) : matchColumn(tableModel.getColumns(), columns.get(i)));
20001                                                                        if (tableColumn == null) {
20002                                                                                if (tableModel.getColumns().size() <= i) {
20003                                                                                        continue;
20004                                                                                }
20005                                                                                tableColumn = tableModel.getColumns().get(i);
20006                                                                        }
20007                                                                        if (!hasCandidates(selects, i)) {
20008                                                                                continue;
20009                                                                        }
20010                                                                        feedSelectPositions(tableColumn, selects, i, effectType, modelFactory.createProcess(stmt));
20011                                                                }
20012                                                        } else {
20013                                                                boolean fromStruct = false;
20014                                                                for (int i = 0; i < columns.size() && j < resultSetSize; i++) {
20015                                                                        String column = columns.get(i).getName();
20016                                                                        ResultColumn resultColumn = resultSetModel.getColumns().get(j);
20017                                                                        // a select item that builds a STRUCT is one position, all of its fields (rule R8),
20018                                                                        // unless the declared leaves of the target take its fields one by one
20019                                                                        List<ResultColumn> structItem = null;
20020                                                                        if (!resultColumn.isStarColumn()) {
20021                                                                                if (resultColumn.getName().equals(resultColumn.getRefColumnName())
20022                                                                                                && isStarNode(resultColumn.getColumnObject())
20023                                                                                                && resultSetSize == 1) {
20024                                                                                        starIndex++;
20025                                                                                        if (resultSetSize - j == columns.size() - i) {
20026                                                                                                j++;
20027
20028                                                                                        }
20029                                                                                }
20030                                                                                else {
20031                                                                                        int width = structLeaves ? 1 : selectItemWidth(resultSetModel.getColumns(), j);
20032                                                                                        if (!structLeaves && (width > 1 || structFieldPaths.containsKey(resultColumn))) {
20033                                                                                                structItem = new ArrayList<ResultColumn>(
20034                                                                                                                resultSetModel.getColumns().subList(j, j + width));
20035                                                                                        }
20036                                                                                        j += width;
20037                                                                                }
20038                                                                        } else {
20039                                                                                starIndex++;
20040                                                                                int remaining = structLeaves ? resultSetSize - j
20041                                                                                                : selectItemsFrom(resultSetModel.getColumns(), j, resultSetSize);
20042                                                                                if (remaining == columns.size() - i) {
20043                                                                                        j++;
20044
20045                                                                                }
20046                                                                        }
20047                                                                        if (column != null) {
20048                                                                                TableColumn tableColumn;
20049                                                                                // if (!initColumn) {
20050                                                                                tableColumn = (structLeaves ? columns.get(i) : matchColumn(tableModel.getColumns(), columns.get(i)));
20051                                                                                if (tableColumn == null) {
20052                                                                                        if (tableModel.isCreateTable()
20053                                                                                                        && !containStarColumn(tableModel.getColumns())) {
20054                                                                                                if (tableModel.getColumns().size() <= i) {
20055                                                                                                        continue;
20056                                                                                                }
20057                                                                                                tableColumn = tableModel.getColumns().get(i);
20058                                                                                        } else {
20059                                                                                                TObjectName columnName = new TObjectName();
20060                                                                                                columnName.setString(column);
20061                                                                                                tableColumn = modelFactory.createTableColumn(tableModel, columnName,
20062                                                                                                                false);
20063                                                                                        }
20064                                                                                }
20065                                                                                else if (structItem == null && !resultColumn.isStruct() && tableColumn.isStruct()
20066                                                                                                && columns.size() != resultSetSize) {
20067                                                                                        j--;
20068                                                                                        fromStruct = true;
20069                                                                                }
20070                                                                                if (structItem != null && tableColumn != null) {
20071                                                                                        // behind a star of unknown width too: the select list is as wide as the
20072                                                                                        // table, so the items after the star fill its last columns (owner ruling
20073                                                                                        // 2026-10-07)
20074                                                                                        feedFromStructItem(tableColumn, structItem, tableColumn.getColumnObject(),
20075                                                                                                        effectType, modelFactory.createProcess(stmt), false);
20076                                                                                        continue;
20077                                                                                }
20078                                                                                if(fromStruct && !tableColumn.isStruct()) {
20079                                                                                        fromStruct = false;
20080                                                                                        resultColumn = resultSetModel.getColumns().get(j);
20081                                                                                        j++;
20082                                                                                }
20083                                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
20084                                                                                relation.setEffectType(effectType);
20085                                                                                relation.setTarget(new TableColumnRelationshipElement(tableColumn));
20086                                                                                if (unknownOrderStar(resultColumn)) {
20087                                                                                        // a star of unknown column order: neither the member at the target's ordinal
20088                                                                                        // nor one named like it is the target's (Mantis 4860)
20089                                                                                        relation.addSource(positionSource(resultColumn));
20090                                                                                } else if (resultColumn.hasStarLinkColumn()
20091                                                                                                && resultColumn.starLinkCount() > starIndex - 1) {
20092                                                                                        ResultColumn expandStarColumn = modelFactory.createResultColumn(
20093                                                                                                        resultSetModel, resultColumn.getStarLinkColumnName(starIndex - 1),
20094                                                                                                        false);
20095                                                                                        relation.addSource(new ResultColumnRelationshipElement(expandStarColumn));
20096                                                                                } else {
20097                                                                                        relation.addSource(new ResultColumnRelationshipElement(resultColumn, starIndex - 1));
20098                                                                                }
20099                                                                                Process process = modelFactory.createProcess(stmt);
20100                                                                                relation.setProcess(process);
20101                                                                                // the fields a STRUCT column carries (rule R8, Codex review of phase 3)
20102                                                                                feedBuiltFieldNodes(tableColumn, resultColumn, effectType, process, false);
20103                                                                        }
20104                                                                }
20105                                                        }
20106                                                }
20107                                        } else if (!subquery.isCombinedQuery()) {
20108                                                SelectResultSet resultSetModel = (SelectResultSet) modelManager
20109                                                                .getModel(subquery.getResultColumnList() != null ? subquery.getResultColumnList()
20110                                                                                : subquery.getTransformClause());
20111
20112                                                if (resultSetModel != null && !resultSetModel.getRelationRows().getHoldRelations().isEmpty()) {
20113                                                        ImpactRelationship impactRelation = modelFactory.createImpactRelation();
20114                                                        impactRelation.setEffectType(effectType);
20115                                                        impactRelation.addSource(new RelationRowsRelationshipElement<ResultSetRelationRows>(
20116                                                                        resultSetModel.getRelationRows()));
20117                                                        impactRelation.setTarget(new RelationRowsRelationshipElement<TableRelationRows>(
20118                                                                        tableModel.getRelationRows()));
20119                                                }
20120
20121                                                List<ResultColumn> columnsSnapshot = new ArrayList<ResultColumn>();
20122                                                columnsSnapshot.addAll(resultSetModel.getColumns());
20123                                                if(resultSetModel.isDetermined() && stmt.getColumnList() == null) {
20124                                                        tableModel.setDetermined(true);
20125                                                }
20126                                                for (int i = 0; i < columnsSnapshot.size(); i++) {
20127                                                        ResultColumn resultColumn = columnsSnapshot.get(i);
20128                                                        if (resultColumn.getColumnObject() instanceof TObjectName) {
20129                                                                TableColumn tableColumn;
20130                                                                if (!initColumn) {
20131                                                                        if (tableModel.isCreateTable() && !containStarColumn(tableModel.getColumns())) {
20132                                                                                if (tableModel.getColumns().size() <= i) {
20133                                                                                        continue;
20134                                                                                }
20135                                                                                tableColumn = tableModel.getColumns().get(i);
20136                                                                        } else {
20137                                                                                tableColumn = modelFactory.createInsertTableColumn(tableModel,
20138                                                                                                (TObjectName) resultColumn.getColumnObject());
20139                                                                                tableColumn.inheritIdentifier(resultColumn);
20140                                                                        }
20141                                                                        if (containStarColumn(tableColumns)) {
20142                                                                                getStarColumn(tableColumns)
20143                                                                                                .bindStarLinkColumn((TObjectName) resultColumn.getColumnObject());
20144                                                                        }
20145                                                                } else {
20146                                                                        TObjectName matchedColumnName = (TObjectName) resultColumn.getColumnObject();
20147                                                                        tableColumn = matchColumn(tableColumns, matchedColumnName);
20148                                                                        if (tableColumn == null) {
20149                                                                                if (!isEmptyCollection(valueMap)) {
20150                                                                                        int index = indexOfColumn(valueMap, matchedColumnName);
20151                                                                                        if (index != -1) {
20152                                                                                                if (!isEmptyCollection(keyMap) && index < keyMap.size()) {
20153                                                                                                        tableColumn = matchColumn(tableColumns, keyMap.get(index));
20154                                                                                                } else if (isEmptyCollection(keyMap) && index < tableColumns.size()) {
20155                                                                                                        tableColumn = tableColumns.get(index);
20156                                                                                                } else {
20157                                                                                                        continue;
20158                                                                                                }
20159                                                                                        } else {
20160                                                                                                continue;
20161                                                                                        }
20162                                                                                } else if (!isEmptyCollection(keyMap) && i < keyMap.size()) {
20163                                                                                        tableColumn = matchColumn(tableColumns, keyMap.get(i));
20164                                                                                } else if (isEmptyCollection(keyMap) && isEmptyCollection(valueMap)
20165                                                                                                && i < tableColumns.size()) {
20166                                                                                        tableColumn = tableColumns.get(i);
20167                                                                                } else {
20168                                                                                        continue;
20169                                                                                }
20170                                                                        }
20171                                                                }
20172
20173                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
20174                                                                relation.setEffectType(effectType);
20175                                                                relation.setTarget(new TableColumnRelationshipElement(tableColumn));
20176                                                                relation.addSource(new ResultColumnRelationshipElement(resultColumn));
20177                                                                Process process = modelFactory.createProcess(stmt);
20178                                                                relation.setProcess(process);
20179                                                                feedBuiltFieldNodes(tableColumn, resultColumn, effectType, process, false);
20180                                                        } else {
20181                                                                TAliasClause alias = ((TResultColumn) resultColumn.getColumnObject()).getAliasClause();
20182                                                                if (alias != null && alias.getAliasName() != null) {
20183                                                                        TableColumn tableColumn;
20184                                                                        if (!initColumn) {
20185                                                                                if (tableModel.isCreateTable() && !containStarColumn(tableModel.getColumns())) {
20186                                                                                        if (tableModel.getColumns().size() <= i) {
20187                                                                                                if (tableModel.isPath()) {
20188                                                                                                        tableColumn = tableModel.getColumns().get(0);
20189                                                                                                } else {
20190                                                                                                        continue;
20191                                                                                                }
20192                                                                                        } else {
20193                                                                                                tableColumn = tableModel.getColumns().get(i);
20194                                                                                        }
20195                                                                                } else {
20196                                                                                        tableColumn = modelFactory.createInsertTableColumn(tableModel,
20197                                                                                                        alias.getAliasName());
20198                                                                                        if (containStarColumn(resultSetModel)) {
20199                                                                                                tableColumn.notBindStarLinkColumn(true);
20200                                                                                        }
20201                                                                                }
20202                                                                                if (containStarColumn(tableColumns)) {
20203                                                                                        getStarColumn(tableColumns).bindStarLinkColumn(alias.getAliasName());
20204                                                                                }
20205                                                                        } else {
20206                                                                                TObjectName matchedColumnName = alias.getAliasName();
20207                                                                                tableColumn = matchColumn(tableColumns, matchedColumnName);
20208                                                                                if (tableColumn == null) {
20209                                                                                        if (!isEmptyCollection(valueMap)) {
20210                                                                                                int index = indexOfColumn(valueMap, matchedColumnName);
20211                                                                                                if (index != -1) {
20212                                                                                                        if (!isEmptyCollection(keyMap) && index < keyMap.size()) {
20213                                                                                                                tableColumn = matchColumn(tableColumns, keyMap.get(index));
20214                                                                                                        } else if (isEmptyCollection(keyMap)
20215                                                                                                                        && index < tableColumns.size()) {
20216                                                                                                                tableColumn = tableColumns.get(index);
20217                                                                                                        } else {
20218                                                                                                                continue;
20219                                                                                                        }
20220                                                                                                } else {
20221                                                                                                        continue;
20222                                                                                                }
20223                                                                                        } else if (!isEmptyCollection(keyMap) && i < keyMap.size()) {
20224                                                                                                tableColumn = matchColumn(tableColumns, keyMap.get(i));
20225                                                                                        } else {
20226                                                                                                tableColumn = modelFactory.createInsertTableColumn(tableModel,
20227                                                                                                                alias.getAliasName());
20228                                                                                        }
20229                                                                                }
20230
20231                                                                        }
20232
20233                                                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
20234                                                                        relation.setEffectType(effectType);
20235                                                                        relation.setTarget(new TableColumnRelationshipElement(tableColumn));
20236                                                                        relation.addSource(new ResultColumnRelationshipElement(resultColumn));
20237                                                                        Process process = modelFactory.createProcess(stmt);
20238                                                                        relation.setProcess(process);
20239                                                                        feedBuiltFieldNodes(tableColumn, resultColumn, effectType, process, false);
20240
20241                                                                } else if (((TResultColumn) resultColumn.getColumnObject()).getFieldAttr() != null) {
20242                                                                        TObjectName fieldAttr = ((TResultColumn) resultColumn.getColumnObject())
20243                                                                                        .getFieldAttr();
20244
20245                                                                        Object model = modelManager.getModel( resultColumn.getColumnObject());
20246                                                                        
20247                                                                        TableColumn tableColumn;
20248                                                                        if (!initColumn) {
20249                                                                                if (tableModel.isCreateTable() && !containStarColumn(tableModel.getColumns())) {
20250                                                                                        if (DlineageUtil.isStarName(fieldAttr)) {
20251                                                                                                int starIndex = 0;
20252                                                                                                for (TableColumn column : tableModel.getColumns()) {
20253                                                                                                        starIndex++;
20254                                                                                                        DataFlowRelationship relation = modelFactory
20255                                                                                                                        .createDataFlowRelation();
20256                                                                                                        relation.setEffectType(effectType);
20257                                                                                                        relation.setTarget(new TableColumnRelationshipElement(column));
20258                                                                                                        if (resultColumn.getStarLinkColumnList().size() == tableModel
20259                                                                                                                        .getColumns().size()) {
20260                                                                                                                ResultColumn expandStarColumn = modelFactory.createResultColumn(
20261                                                                                                                                resultSetModel,
20262                                                                                                                                resultColumn.getStarLinkColumnList().get(starIndex - 1),
20263                                                                                                                                false);
20264                                                                                                                relation.addSource(
20265                                                                                                                                new ResultColumnRelationshipElement(expandStarColumn));
20266                                                                                                        } else {
20267                                                                                                                relation.addSource(
20268                                                                                                                                new ResultColumnRelationshipElement(resultColumn));
20269                                                                                                        }
20270                                                                                                        Process process = modelFactory.createProcess(stmt);
20271                                                                                                        relation.setProcess(process);
20272                                                                                                }
20273                                                                                                continue;
20274                                                                                        }
20275                                                                                        if (tableModel.getColumns().size() <= i) {
20276                                                                                                continue;
20277                                                                                        }
20278                                                                                        tableColumn = tableModel.getColumns().get(i);
20279                                                                                } else {
20280                                                                                        if(model instanceof LinkedHashMap) {
20281                                                                                                LinkedHashMap<String, ResultColumn> resultColumns = (LinkedHashMap<String, ResultColumn>)model;
20282                                                                                                for(String key: resultColumns.keySet()) {
20283                                                                                                        tableColumn = itemColumnTarget(tableModel, resultColumns.get(key));
20284                                                                                                        DataFlowRelationship relation = modelFactory
20285                                                                                                                        .createDataFlowRelation();
20286                                                                                                        relation.setEffectType(effectType);
20287                                                                                                        relation.setTarget(new TableColumnRelationshipElement(tableColumn));
20288                                                                                                        relation.addSource(
20289                                                                                                                        new ResultColumnRelationshipElement(resultColumns.get(key)));
20290                                                                                                        Process process = modelFactory.createProcess(stmt);
20291                                                                                                        relation.setProcess(process);
20292                                                                                                }
20293                                                                                                continue;
20294                                                                                        }
20295                                                                                        else {
20296                                                                                                tableColumn = modelFactory.createInsertTableColumn(tableModel, fieldAttr);
20297                                                                                        }
20298                                                                                }
20299                                                                        } else if (tableModel.isDetermined() && i < tableModel.getColumns().size()) {
20300                                                                                if (!isEmptyCollection(valueMap)) {
20301                                                                                        TObjectName matchedColumnName = fieldAttr;
20302                                                                                        int index = indexOfColumn(valueMap, matchedColumnName);
20303                                                                                        if (index != -1) {
20304                                                                                                if (!isEmptyCollection(keyMap) && index < keyMap.size()) {
20305                                                                                                        tableColumn = matchColumn(tableColumns, keyMap.get(index));
20306                                                                                                } else if (isEmptyCollection(keyMap)
20307                                                                                                                && index < tableColumns.size()) {
20308                                                                                                        tableColumn = tableColumns.get(index);
20309                                                                                                } else {
20310                                                                                                        continue;
20311                                                                                                }
20312                                                                                        } else {
20313                                                                                                continue;
20314                                                                                        }
20315                                                                                } else if (!isEmptyCollection(keyMap) && i < keyMap.size()) {
20316                                                                                        tableColumn = matchColumn(tableColumns, keyMap.get(i));
20317                                                                                } else {
20318                                                                                        tableColumn = tableModel.getColumns().get(i);
20319                                                                                }
20320                                                                        } else {
20321                                                                                TObjectName matchedColumnName = fieldAttr;
20322                                                                                tableColumn = matchColumn(tableColumns, matchedColumnName);
20323                                                                                if (tableColumn == null) {
20324                                                                                        if (!isEmptyCollection(valueMap)) {
20325                                                                                                int index = indexOfColumn(valueMap, matchedColumnName);
20326                                                                                                if (index != -1) {
20327                                                                                                        if (!isEmptyCollection(keyMap) && index < keyMap.size()) {
20328                                                                                                                tableColumn = matchColumn(tableColumns, keyMap.get(index));
20329                                                                                                        } else if (isEmptyCollection(keyMap)
20330                                                                                                                        && index < tableColumns.size()) {
20331                                                                                                                tableColumn = tableColumns.get(index);
20332                                                                                                        } else {
20333                                                                                                                continue;
20334                                                                                                        }
20335                                                                                                } else {
20336                                                                                                        continue;
20337                                                                                                }
20338                                                                                        } else if (!isEmptyCollection(keyMap) && i < keyMap.size()) {
20339                                                                                                tableColumn = matchColumn(tableColumns, keyMap.get(i));
20340                                                                                        } else {
20341                                                                                                tableColumn = modelFactory.createInsertTableColumn(tableModel,
20342                                                                                                                fieldAttr);
20343                                                                                        }
20344                                                                                }
20345                                                                        }
20346
20347                                                                        if (!DlineageUtil.isStarName((tableColumn.getColumnObject()))
20348                                                                                        && DlineageUtil.isStarName((fieldAttr))) {
20349                                                                                TObjectName columnObject = fieldAttr;
20350                                                                                TTable sourceTable = columnObject.getSourceTable();
20351                                                                                if (columnObject.getTableToken() != null && sourceTable != null) {
20352                                                                                        TObjectName[] columns = modelManager.getTableColumns(sourceTable);
20353                                                                                        for (int j = 0; j < columns.length; j++) {
20354                                                                                                TObjectName columnName = columns[j];
20355                                                                                                if (columnName == null || DlineageUtil.isStarName((columnName))) {
20356                                                                                                        continue;
20357                                                                                                }
20358                                                                                                resultColumn.bindStarLinkColumn(columnName);
20359                                                                                        }
20360                                                                                } else {
20361                                                                                        TTableList tables = stmt.getTables();
20362                                                                                        for (int k = 0; k < tables.size(); k++) {
20363                                                                                                TTable tableElement = tables.getTable(k);
20364                                                                                                TObjectName[] columns = modelManager.getTableColumns(tableElement);
20365                                                                                                for (int j = 0; j < columns.length; j++) {
20366                                                                                                        TObjectName columnName = columns[j];
20367                                                                                                        if (columnName == null || DlineageUtil.isStarName((columnName))) {
20368                                                                                                                continue;
20369                                                                                                        }
20370                                                                                                        resultColumn.bindStarLinkColumn(columnName);
20371                                                                                                }
20372                                                                                        }
20373                                                                                }
20374                                                                        }
20375
20376                                                                        if (DlineageUtil.isStarName((tableColumn.getColumnObject())) && resultColumn != null
20377                                                                                        && resultColumn.hasStarLinkColumn()) {
20378                                                                                tableColumn.shareStarLinksFrom(resultColumn);
20379                                                                        }
20380
20381                                                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
20382                                                                        relation.setEffectType(effectType);
20383                                                                        relation.setTarget(new TableColumnRelationshipElement(tableColumn));
20384                                                                        relation.addSource(new ResultColumnRelationshipElement(resultColumn));
20385
20386                                                                        if (tableColumn.isStarColumn() && resultColumn.isStarColumn()) {
20387                                                                                tableColumn.getTable().setStarStmt("insert");
20388                                                                        }
20389
20390                                                                        Process process = modelFactory.createProcess(stmt);
20391                                                                        relation.setProcess(process);
20392                                                                } else if (((TResultColumn) resultColumn.getColumnObject()).getExpr()
20393                                                                                .getExpressionType() == EExpressionType.simple_constant_t) {
20394                                                                        if (!initColumn) {
20395                                                                                TableColumn tableColumn;
20396                                                                                if (tableModel.isCreateTable() && !containStarColumn(tableModel.getColumns())) {
20397                                                                                        if (tableModel.getColumns().size() <= i) {
20398                                                                                                continue;
20399                                                                                        }
20400                                                                                        tableColumn = tableModel.getColumns().get(i);
20401                                                                                } else {
20402                                                                                        tableColumn = modelFactory.createInsertTableColumn(tableModel,
20403                                                                                                        ((TResultColumn) resultColumn.getColumnObject()).getExpr()
20404                                                                                                                        .getConstantOperand(),
20405                                                                                                        i);
20406                                                                                }
20407
20408                                                                                if (DlineageUtil.isTempTable(tableModel, option.getVendor()) && sqlenv != null
20409                                                                                                && tableModel.getDatabase() != null && tableModel.getSchema() != null) {
20410                                                                                        TSQLSchema schema = tableMetadataEnvironment(tableModel).getSQLSchema(
20411                                                                                                        tableModel.getDatabase() + "." + tableModel.getSchema(), true);
20412                                                                                        if (schema != null) {
20413                                                                                                TSQLTable tempTable = schema.createTable(
20414                                                                                                                DlineageUtil.getSimpleTableName(tableModel.getName()));
20415                                                                                                if (tableColumn.isStarColumn()) {
20416                                                                                                        addUnresolvedStar(tempTable);
20417                                                                                                } else {
20418                                                                                                        tempTable.addColumn(tableColumn.getName());
20419                                                                                                }
20420                                                                                        }
20421                                                                                }
20422                                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
20423                                                                                relation.setEffectType(effectType);
20424                                                                                relation.setTarget(new TableColumnRelationshipElement(tableColumn));
20425                                                                                relation.addSource(new ResultColumnRelationshipElement(resultColumn));
20426                                                                                Process process = modelFactory.createProcess(stmt);
20427                                                                                relation.setProcess(process);
20428                                                                        } else {
20429                                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
20430                                                                                relation.setEffectType(effectType);
20431                                                                                relation.setTarget(
20432                                                                                                new TableColumnRelationshipElement(tableModel.getColumns().get(i)));
20433                                                                                relation.addSource(new ResultColumnRelationshipElement(resultColumn));
20434                                                                                Process process = modelFactory.createProcess(stmt);
20435                                                                                relation.setProcess(process);
20436                                                                        }
20437                                                                } else {
20438                                                                        if (!initColumn) {
20439                                                                                TableColumn tableColumn;
20440                                                                                if (tableModel.isCreateTable() && !containStarColumn(tableModel.getColumns())) {
20441                                                                                        if (tableModel.getColumns().size() <= i) {
20442                                                                                                continue;
20443                                                                                        }
20444                                                                                        tableColumn = tableModel.getColumns().get(i);
20445                                                                                } else {
20446                                                                                        tableColumn = modelFactory.createInsertTableColumn(tableModel,
20447                                                                                                        ((TResultColumn) resultColumn.getColumnObject()).getExpr(), i);
20448                                                                                }
20449                                                                                if (DlineageUtil.isTempTable(tableModel, option.getVendor()) && sqlenv != null
20450                                                                                                && tableModel.getDatabase() != null && tableModel.getSchema() != null) {
20451                                                                                        TSQLSchema schema = tableMetadataEnvironment(tableModel).getSQLSchema(
20452                                                                                                        tableModel.getDatabase() + "." + tableModel.getSchema(), true);
20453                                                                                        if (schema != null) {
20454                                                                                                TSQLTable tempTable = schema.createTable(
20455                                                                                                                DlineageUtil.getSimpleTableName(tableModel.getName()));
20456                                                                                                if (tableColumn.isStarColumn()) {
20457                                                                                                        addUnresolvedStar(tempTable);
20458                                                                                                } else {
20459                                                                                                        tempTable.addColumn(tableColumn.getName());
20460                                                                                                }
20461                                                                                        }
20462                                                                                }
20463                                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
20464                                                                                relation.setEffectType(effectType);
20465                                                                                relation.setTarget(new TableColumnRelationshipElement(tableColumn));
20466                                                                                relation.addSource(new ResultColumnRelationshipElement(resultColumn));
20467                                                                                Process process = modelFactory.createProcess(stmt);
20468                                                                                relation.setProcess(process);
20469                                                                        } else {
20470                                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
20471                                                                                relation.setEffectType(effectType);
20472                                                                                relation.setTarget(
20473                                                                                                new TableColumnRelationshipElement(tableModel.getColumns().get(i)));
20474                                                                                relation.addSource(new ResultColumnRelationshipElement(resultColumn));
20475                                                                                Process process = modelFactory.createProcess(stmt);
20476                                                                                relation.setProcess(process);
20477                                                                        }
20478                                                                }
20479                                                        }
20480                                                }
20481                                        } else if (stmt.getSubQuery() != null) {
20482                                                ResultSet resultSetModel = (ResultSet) modelManager.getModel(stmt.getSubQuery());
20483                                                if (resultSetModel != null) {
20484
20485                                                        if (!resultSetModel.getRelationRows().getHoldRelations().isEmpty()) {
20486                                                                ImpactRelationship impactRelation = modelFactory.createImpactRelation();
20487                                                                impactRelation.setEffectType(effectType);
20488                                                                impactRelation.addSource(new RelationRowsRelationshipElement<ResultSetRelationRows>(
20489                                                                                resultSetModel.getRelationRows()));
20490                                                                impactRelation.setTarget(new RelationRowsRelationshipElement<TableRelationRows>(
20491                                                                                tableModel.getRelationRows()));
20492                                                        }
20493
20494                                                        // a star of unknown width in a select: each listed column reads, from each select, the items that
20495                                                        // can stand at its position (Mantis 4914)
20496                                                        List<SelectPositions> setSelects = stmt.getColumnList() != null && stmt.getColumnList().size() > 0
20497                                                                        ? setOperationPositions(stmt.getSubQuery(), stmt.getColumnList().size()) : null;
20498                                                        if (setSelects != null) {
20499                                                                for (int i = 0; i < stmt.getColumnList().size(); i++) {
20500                                                                        TableColumn tableColumn = modelFactory.createInsertTableColumn(tableModel,
20501                                                                                        stmt.getColumnList().getObjectName(i));
20502                                                                        if (tableColumn != null && hasCandidates(setSelects, i)) {
20503                                                                                feedSelectPositions(tableColumn, setSelects, i, effectType, modelFactory.createProcess(stmt));
20504                                                                        }
20505                                                                }
20506                                                        } else if(stmt.getColumnList()!=null && stmt.getColumnList().size()>0) {
20507                                                                for(int i=0;i<stmt.getColumnList().size();i++) {
20508                                                                        TableColumn tableColumn = modelFactory.createInsertTableColumn(tableModel,
20509                                                                                        stmt.getColumnList().getObjectName(i));
20510                                                                }
20511                                                                
20512                                                                List<TableColumn> columns = tableModel.getColumns();
20513                                                                int resultSetSize = resultSetModel.getColumns().size();
20514                                                                int starIndex = 0; 
20515                                                                int j = 0;
20516                                                                boolean fromStruct = false;
20517                                                                for (int i = 0; i < columns.size() && j < resultSetSize; i++) {
20518                                                                        String column = columns.get(i).getName();
20519                                                                        ResultColumn resultColumn = resultSetModel.getColumns().get(j);
20520                                                                        if (!resultColumn.isStarColumn()) {
20521                                                                                if (resultColumn.getName().equals(resultColumn.getRefColumnName())
20522                                                                                                && isStarNode(resultColumn.getColumnObject())
20523                                                                                                && resultSetSize == 1) {
20524                                                                                        starIndex++;
20525                                                                                        if (resultSetSize - j == columns.size() - i) {
20526                                                                                                j++;
20527
20528                                                                                        }
20529                                                                                }
20530                                                                                else {
20531                                                                                        j++;
20532                                                                                }
20533                                                                        } else {
20534                                                                                starIndex++;
20535                                                                                if (resultSetSize - j == columns.size() - i) {
20536                                                                                        j++;
20537
20538                                                                                }
20539                                                                        }
20540                                                                        if (column != null) {
20541                                                                                TableColumn tableColumn;
20542                                                                                // if (!initColumn) {
20543                                                                                tableColumn = matchColumn(tableModel.getColumns(), columns.get(i));
20544                                                                                if (tableColumn == null) {
20545                                                                                        if (tableModel.isCreateTable()
20546                                                                                                        && !containStarColumn(tableModel.getColumns())) {
20547                                                                                                if (tableModel.getColumns().size() <= i) {
20548                                                                                                        continue;
20549                                                                                                }
20550                                                                                                tableColumn = tableModel.getColumns().get(i);
20551                                                                                        } else {
20552                                                                                                TObjectName columnName = new TObjectName();
20553                                                                                                columnName.setString(column);
20554                                                                                                tableColumn = modelFactory.createTableColumn(tableModel, columnName,
20555                                                                                                                false);
20556                                                                                        }
20557                                                                                }
20558                                                                                else if (!resultColumn.isStruct() && tableColumn.isStruct() && columns.size() != resultSetSize) {
20559                                                                                        j--;
20560                                                                                        fromStruct = true;
20561                                                                                }
20562                                                                                if(fromStruct && !tableColumn.isStruct()) {
20563                                                                                        fromStruct = false;
20564                                                                                        resultColumn = resultSetModel.getColumns().get(j);
20565                                                                                        j++;
20566                                                                                }
20567                                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
20568                                                                                relation.setEffectType(effectType);
20569                                                                                relation.setTarget(new TableColumnRelationshipElement(tableColumn));
20570                                                                                if (resultColumn.hasStarLinkColumn()
20571                                                                                                && resultColumn.starLinkCount() > starIndex - 1) {
20572                                                                                        ResultColumn expandStarColumn = modelFactory.createResultColumn(
20573                                                                                                        resultSetModel, resultColumn.getStarLinkColumnName(starIndex - 1),
20574                                                                                                        false);
20575                                                                                        relation.addSource(new ResultColumnRelationshipElement(expandStarColumn));
20576                                                                                } else {
20577                                                                                        relation.addSource(new ResultColumnRelationshipElement(resultColumn, starIndex - 1));
20578                                                                                }
20579                                                                                Process process = modelFactory.createProcess(stmt);
20580                                                                                relation.setProcess(process);
20581                                                                                // the fields a STRUCT column carries (rule R8, Codex review of phase 3)
20582                                                                                feedBuiltFieldNodes(tableColumn, resultColumn, effectType, process, false);
20583                                                                        }
20584                                                                }
20585                                                        }
20586                                                        else {
20587                                                                for (int i = 0; i < resultSetModel.getColumns().size(); i++) {
20588                                                                        ResultColumn resultColumn = resultSetModel.getColumns().get(i);
20589                                                                        TAliasClause alias = null;
20590                                                                        if(resultColumn.getColumnObject() instanceof TResultColumn) {
20591                                                                                alias = ((TResultColumn) resultColumn.getColumnObject()).getAliasClause();
20592                                                                        }
20593                                                                        if (stmt.getColumnList() != null) {
20594                                                                                if (i < stmt.getColumnList().size()) {
20595                                                                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
20596                                                                                        relation.setEffectType(effectType);
20597                                                                                        TableColumn tableColumn = modelFactory.createInsertTableColumn(tableModel,
20598                                                                                                        stmt.getColumnList().getObjectName(i));
20599                                                                                        relation.setTarget(new TableColumnRelationshipElement(tableColumn));
20600                                                                                        relation.addSource(new ResultColumnRelationshipElement(resultColumn));
20601                                                                                        Process process = modelFactory.createProcess(stmt);
20602                                                                                        relation.setProcess(process);
20603                                                                                }
20604                                                                        } else {
20605                                                                                if (alias != null && alias.getAliasName() != null) {
20606                                                                                        TableColumn tableColumn;
20607                                                                                        if (!initColumn) {
20608                                                                                                if (tableModel.isCreateTable()
20609                                                                                                                && !containStarColumn(tableModel.getColumns())) {
20610                                                                                                        if (tableModel.getColumns().size() <= i) {
20611                                                                                                                continue;
20612                                                                                                        }
20613                                                                                                        tableColumn = tableModel.getColumns().get(i);
20614                                                                                                } else {
20615                                                                                                        tableColumn = modelFactory.createInsertTableColumn(tableModel,
20616                                                                                                                        alias.getAliasName());
20617                                                                                                }
20618                                                                                        } else {
20619                                                                                                tableColumn = matchColumn(tableColumns, alias.getAliasName());
20620                                                                                                if (tableColumn == null) {
20621                                                                                                        continue;
20622                                                                                                }
20623                                                                                        }
20624        
20625                                                                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
20626                                                                                        relation.setEffectType(effectType);
20627                                                                                        relation.setTarget(new TableColumnRelationshipElement(tableColumn));
20628                                                                                        relation.addSource(new ResultColumnRelationshipElement(resultColumn));
20629                                                                                        Process process = modelFactory.createProcess(stmt);
20630                                                                                        relation.setProcess(process);
20631                                                                                } else if (resultColumn.getColumnObject() instanceof TObjectName 
20632                                                                                                || ( resultColumn.getColumnObject() instanceof TResultColumn && ((TResultColumn) resultColumn.getColumnObject())
20633                                                                                                .getFieldAttr() != null)) {
20634                                                                                        TObjectName fieldAttr = null;
20635                                                                                        if (resultColumn.getColumnObject() instanceof TObjectName) {
20636                                                                                                fieldAttr = (TObjectName)resultColumn.getColumnObject();
20637                                                                                        }
20638                                                                                        else if (resultColumn.getColumnObject() instanceof TResultColumn) {
20639                                                                                                fieldAttr = ((TResultColumn) resultColumn.getColumnObject())
20640                                                                                                                .getFieldAttr();
20641                                                                                        }
20642                                
20643                                                                                        TableColumn tableColumn;
20644                                                                                        if (!initColumn) {
20645                                                                                                if (tableModel.isCreateTable()
20646                                                                                                                && !containStarColumn(tableModel.getColumns())) {
20647                                                                                                        if (tableModel.getColumns().size() <= i) {
20648                                                                                                                continue;
20649                                                                                                        }
20650                                                                                                        tableColumn = tableModel.getColumns().get(i);
20651                                                                                                } else {
20652                                                                                                        tableColumn = modelFactory.createInsertTableColumn(tableModel,
20653                                                                                                                        fieldAttr);
20654                                                                                                        tableColumn.inheritIdentifier(resultColumn);
20655                                                                                                }
20656                                                                                        } else {
20657                                                                                                tableColumn = matchColumn(tableColumns, fieldAttr);
20658                                                                                                if (tableColumn == null) {
20659                                                                                                        continue;
20660                                                                                                }
20661                                                                                        }
20662        
20663                                                                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
20664                                                                                        relation.setEffectType(effectType);
20665                                                                                        relation.setTarget(new TableColumnRelationshipElement(tableColumn));
20666                                                                                        relation.addSource(new ResultColumnRelationshipElement(resultColumn));
20667                                                                                        Process process = modelFactory.createProcess(stmt);
20668                                                                                        relation.setProcess(process);
20669                                                                                } else if (((TResultColumn) resultColumn.getColumnObject()).getExpr()
20670                                                                                                .getExpressionType() == EExpressionType.simple_constant_t) {
20671                                                                                        if (!initColumn) {
20672                                                                                                TableColumn tableColumn;
20673                                                                                                if (tableModel.isCreateTable()
20674                                                                                                                && !containStarColumn(tableModel.getColumns())) {
20675                                                                                                        if (tableModel.getColumns().size() <= i) {
20676                                                                                                                continue;
20677                                                                                                        }
20678                                                                                                        tableColumn = tableModel.getColumns().get(i);
20679                                                                                                } else {
20680                                                                                                        tableColumn = modelFactory.createInsertTableColumn(tableModel,
20681                                                                                                                        ((TResultColumn) resultColumn.getColumnObject()).getExpr()
20682                                                                                                                                        .getConstantOperand(),
20683                                                                                                                        i);
20684                                                                                                }
20685                                                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
20686                                                                                                relation.setEffectType(effectType);
20687                                                                                                relation.setTarget(new TableColumnRelationshipElement(tableColumn));
20688                                                                                                relation.addSource(new ResultColumnRelationshipElement(resultColumn));
20689                                                                                                Process process = modelFactory.createProcess(stmt);
20690                                                                                                relation.setProcess(process);
20691                                                                                        }
20692                                                                                } else {
20693                                                                                        if (!initColumn) {
20694                                                                                                TableColumn tableColumn;
20695                                                                                                if (tableModel.isCreateTable()
20696                                                                                                                && !containStarColumn(tableModel.getColumns())) {
20697                                                                                                        if (tableModel.getColumns().size() <= i) {
20698                                                                                                                continue;
20699                                                                                                        }
20700                                                                                                        tableColumn = tableModel.getColumns().get(i);
20701                                                                                                } else {
20702                                                                                                        tableColumn = modelFactory.createInsertTableColumn(tableModel,
20703                                                                                                                        ((TResultColumn) resultColumn.getColumnObject()).getExpr(), i);
20704                                                                                                }
20705                                                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
20706                                                                                                relation.setEffectType(effectType);
20707                                                                                                relation.setTarget(new TableColumnRelationshipElement(tableColumn));
20708                                                                                                relation.addSource(new ResultColumnRelationshipElement(resultColumn));
20709                                                                                                Process process = modelFactory.createProcess(stmt);
20710                                                                                                relation.setProcess(process);
20711                                                                                        }
20712                                                                                }
20713                                                                        }
20714                                                                }
20715                                                        }
20716                                                }
20717                                        }
20718                                }
20719                        } else if (stmt.getColumnList() != null && stmt.getColumnList().size() > 0) {
20720                                TObjectNameList items = stmt.getColumnList();
20721                                TMultiTargetList values = stmt.getValues();
20722                                if (values != null) {
20723                                        for (int k = 0; values != null && k < values.size(); k++) {
20724                                                int j = 0;
20725                                                for (int i = 0; i < items.size(); i++) {
20726                                                        TObjectName column = items.getObjectName(i);
20727                                                        TableColumn tableColumn;
20728                                                        if (!initColumn) {
20729                                                                if (tableModel.isCreateTable() && !containStarColumn(tableModel.getColumns())) {
20730                                                                        if (tableModel.getColumns().size() <= i) {
20731                                                                                continue;
20732                                                                        }
20733                                                                        tableColumn = tableModel.getColumns().get(i);
20734                                                                } else {
20735                                                                        tableColumn = modelFactory.createInsertTableColumn(tableModel, column);
20736                                                                }
20737                                                        } else {
20738                                                                tableColumn = matchColumn(tableColumns, column);
20739                                                                if (tableColumn == null) {
20740                                                                        continue;
20741                                                                }
20742                                                        }
20743                                                        TResultColumn columnObject = values.getMultiTarget(k).getColumnList().getResultColumn(j);
20744                                                        if (columnObject == null) {
20745                                                                continue;
20746                                                        }
20747                                                        TExpression valueExpr = columnObject.getExpr();
20748                                                        columnsInExpr visitor = new columnsInExpr();
20749                                                        valueExpr.inOrderTraverse(visitor);
20750                                                        List<TObjectName> objectNames = visitor.getObjectNames();
20751                                                        List<TParseTreeNode> constants = visitor.getConstants();
20752                                                        List<TParseTreeNode> functions = visitor.getFunctions();
20753                                                        List<TSelectSqlStatement> subquerys = visitor.getSubquerys();
20754
20755                                                        Process process = modelFactory.createProcess(stmt);
20756
20757
20758
20759                                                        if (functions != null && !functions.isEmpty()) {
20760                                                                analyzeFunctionDataFlowRelation(tableColumn, functions, effectType, process);
20761                                                        }
20762
20763                                                        if (subquerys != null && !subquerys.isEmpty()) {
20764                                                                analyzeSubqueryDataFlowRelation(tableColumn, subquerys, effectType, process);
20765                                                        }
20766                                                        if (objectNames != null && !objectNames.isEmpty()) {
20767                                                                analyzeDataFlowRelation(tableColumn, objectNames, null, effectType, functions,
20768                                                                                process, i);
20769                                                        }
20770                                                        //insert into values generate too many constant relations, ignore constant relations.
20771                                                        if (constants != null && !constants.isEmpty()) {
20772                                                                if (!option.isIgnoreInsertIntoValues() || stmt.getParentStmt() != null) {
20773                                                                        analyzeConstantDataFlowRelation(tableColumn, constants, effectType,
20774                                                                                        functions, process);
20775                                                                }
20776                                                        }
20777                                                        j++;
20778                                                }
20779                                        }
20780                                } else if (stmt.getExecuteStmt() != null && stmt.getExecuteStmt().getModuleName() != null) {
20781                                        analyzeCustomSqlStmt(stmt.getExecuteStmt());
20782                                        Procedure procedure = modelManager.getProcedure(nameAliasKey(stmt.getExecuteStmt().getModuleName(), null));
20783                                        if (gudusoft.gsqlparser.dlineage.routine.RoutineShadow.enabled()) shadowCallee("insertExec", stmt.getExecuteStmt().getModuleName(), procedure, new Integer[] {null}, gudusoft.gsqlparser.catalog.runtime.CatalogObjectKind.PROCEDURE);
20784                                        if ((procedure != null && procedure.getProcedureObject() instanceof TStoredProcedureSqlStatement)
20785                                                        || isSpExecuteExternalScript(stmt.getExecuteStmt().getModuleName())) {
20786                                                List<ResultSet> resultSetModels = calleeResultSetsForInsertExec(stmt.getExecuteStmt(), procedure);
20787                                                if (resultSetModels != null) {
20788                                                        for (ResultSet resultSetModel : resultSetModels) {
20789                                                                if (resultSetModel != null) {
20790                                                                        for (int i = 0; i < resultSetModel.getColumns().size(); i++) {
20791                                                                                ResultColumn resultColumn = resultSetModel.getColumns().get(i);
20792                                                                                Transform transform = new Transform();
20793                                                                                transform.setType(Transform.FUNCTION);
20794                                                                                transform.setCode(stmt.getExecuteStmt().getModuleName());
20795                                                                                resultColumn.setTransform(transform);
20796                                                                                
20797                                                                                if (stmt.getColumnList() != null) {
20798                                                                                        if (i < stmt.getColumnList().size()) {
20799                                                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
20800                                                                                                relation.setEffectType(effectType);
20801                                                                                                TableColumn tableColumn = modelFactory.createInsertTableColumn(tableModel,
20802                                                                                                                stmt.getColumnList().getObjectName(i));                                                                                         
20803                                                                                                relation.setTarget(new TableColumnRelationshipElement(tableColumn));
20804                                                                                                relation.addSource(new ResultColumnRelationshipElement(resultColumn));
20805                                                                                                Process process = modelFactory.createProcess(stmt);
20806                                                                                                relation.setProcess(process);                                   
20807                                                                                        }
20808                                                                                } else {
20809                                                                                        if (resultColumn.getColumnObject() instanceof TObjectName) {
20810                                                                                                TObjectName fieldAttr = ((TObjectName) resultColumn.getColumnObject());
20811                                                                                                TableColumn tableColumn;
20812                                                                                                if (!initColumn) {
20813                                                                                                        if (tableModel.isCreateTable()
20814                                                                                                                        && !containStarColumn(tableModel.getColumns())) {
20815                                                                                                                if (tableModel.getColumns().size() <= i) {
20816                                                                                                                        continue;
20817                                                                                                                }
20818                                                                                                                tableColumn = tableModel.getColumns().get(i);
20819                                                                                                        } else {
20820                                                                                                                tableColumn = modelFactory.createInsertTableColumn(tableModel,
20821                                                                                                                                fieldAttr);
20822                                                                                                        }
20823                                                                                                } else {
20824                                                                                                        tableColumn = matchColumn(tableColumns, fieldAttr);
20825                                                                                                        if (tableColumn == null) {
20826                                                                                                                continue;
20827                                                                                                        }
20828                                                                                                }
20829        
20830                                                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
20831                                                                                                relation.setEffectType(effectType);
20832                                                                                                relation.setTarget(new TableColumnRelationshipElement(tableColumn));
20833                                                                                                relation.addSource(new ResultColumnRelationshipElement(resultColumn));
20834                                                                                                Process process = modelFactory.createProcess(stmt);
20835                                                                                                relation.setProcess(process);
20836
20837                                                                                                
20838                                                                                        } else {
20839                                                                                                TAliasClause alias = ((TResultColumn) resultColumn.getColumnObject())
20840                                                                                                                .getAliasClause();
20841                                                                                                if (alias != null && alias.getAliasName() != null) {
20842                                                                                                        TableColumn tableColumn;
20843                                                                                                        if (!initColumn) {
20844                                                                                                                if (tableModel.isCreateTable()
20845                                                                                                                                && !containStarColumn(tableModel.getColumns())) {
20846                                                                                                                        if (tableModel.getColumns().size() <= i) {
20847                                                                                                                                continue;
20848                                                                                                                        }
20849                                                                                                                        tableColumn = tableModel.getColumns().get(i);
20850                                                                                                                } else {
20851                                                                                                                        tableColumn = modelFactory.createInsertTableColumn(tableModel,
20852                                                                                                                                        alias.getAliasName());
20853                                                                                                                }
20854                                                                                                        } else {
20855                                                                                                                tableColumn = matchColumn(tableColumns, alias.getAliasName());
20856                                                                                                                if (tableColumn == null) {
20857                                                                                                                        continue;
20858                                                                                                                }
20859                                                                                                        }
20860                                                                                                        
20861                                                                                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
20862                                                                                                        relation.setEffectType(effectType);
20863                                                                                                        relation.setTarget(new TableColumnRelationshipElement(tableColumn));
20864                                                                                                        relation.addSource(new ResultColumnRelationshipElement(resultColumn));
20865                                                                                                        Process process = modelFactory.createProcess(stmt);
20866                                                                                                        relation.setProcess(process);
20867                                                                                                } else if (((TResultColumn) resultColumn.getColumnObject())
20868                                                                                                                .getFieldAttr() != null) {
20869                                                                                                        TObjectName fieldAttr = ((TResultColumn) resultColumn.getColumnObject())
20870                                                                                                                        .getFieldAttr();
20871                                                                                                        TableColumn tableColumn;
20872                                                                                                        if (!initColumn) {
20873                                                                                                                if (tableModel.isCreateTable()
20874                                                                                                                                && !containStarColumn(tableModel.getColumns())) {
20875                                                                                                                        if (tableModel.getColumns().size() <= i) {
20876                                                                                                                                continue;
20877                                                                                                                        }
20878                                                                                                                        tableColumn = tableModel.getColumns().get(i);
20879                                                                                                                } else {
20880                                                                                                                        tableColumn = modelFactory.createInsertTableColumn(tableModel,
20881                                                                                                                                        fieldAttr);
20882                                                                                                                }
20883                                                                                                        } else {
20884                                                                                                                tableColumn = matchColumn(tableColumns, fieldAttr);
20885                                                                                                                if (tableColumn == null) {
20886                                                                                                                        continue;
20887                                                                                                                }
20888                                                                                                        }
20889                                                                                                        
20890                                                                                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
20891                                                                                                        relation.setEffectType(effectType);
20892                                                                                                        relation.setTarget(new TableColumnRelationshipElement(tableColumn));
20893                                                                                                        relation.addSource(new ResultColumnRelationshipElement(resultColumn));
20894                                                                                                        Process process = modelFactory.createProcess(stmt);
20895                                                                                                        relation.setProcess(process);                                                                                                   
20896                                                                                                } else if (((TResultColumn) resultColumn.getColumnObject()).getExpr()
20897                                                                                                                .getExpressionType() == EExpressionType.simple_constant_t) {
20898                                                                                                        if (!initColumn) {
20899                                                                                                                TableColumn tableColumn;
20900                                                                                                                if (tableModel.isCreateTable()
20901                                                                                                                                && !containStarColumn(tableModel.getColumns())) {
20902                                                                                                                        if (tableModel.getColumns().size() <= i) {
20903                                                                                                                                continue;
20904                                                                                                                        }
20905                                                                                                                        tableColumn = tableModel.getColumns().get(i);
20906                                                                                                                } else {
20907                                                                                                                        tableColumn = modelFactory.createInsertTableColumn(tableModel,
20908                                                                                                                                        ((TResultColumn) resultColumn.getColumnObject()).getExpr()
20909                                                                                                                                                        .getConstantOperand(),
20910                                                                                                                                        i);
20911                                                                                                                }
20912                                                                                                                
20913                                                                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
20914                                                                                                                relation.setEffectType(effectType);
20915                                                                                                                relation.setTarget(new TableColumnRelationshipElement(tableColumn));
20916                                                                                                                relation.addSource(new ResultColumnRelationshipElement(resultColumn));
20917                                                                                                                Process process = modelFactory.createProcess(stmt);
20918                                                                                                                relation.setProcess(process);
20919                                                                                                        }
20920                                                                                                } else {
20921                                                                                                        if (!initColumn) {
20922                                                                                                                TableColumn tableColumn;
20923                                                                                                                if (tableModel.isCreateTable()
20924                                                                                                                                && !containStarColumn(tableModel.getColumns())) {
20925                                                                                                                        if (tableModel.getColumns().size() <= i) {
20926                                                                                                                                continue;
20927                                                                                                                        }
20928                                                                                                                        tableColumn = tableModel.getColumns().get(i);
20929                                                                                                                } else {
20930                                                                                                                        tableColumn = modelFactory.createInsertTableColumn(tableModel,
20931                                                                                                                                        ((TResultColumn) resultColumn.getColumnObject()).getExpr(),
20932                                                                                                                                        i);
20933                                                                                                                }
20934                                                                                                                
20935                                                                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
20936                                                                                                                relation.setEffectType(effectType);
20937                                                                                                                relation.setTarget(new TableColumnRelationshipElement(tableColumn));
20938                                                                                                                relation.addSource(new ResultColumnRelationshipElement(resultColumn));
20939                                                                                                                Process process = modelFactory.createProcess(stmt);
20940                                                                                                                relation.setProcess(process);
20941                                                                                                        }
20942                                                                                                }
20943                                                                                        }
20944                                                                                }
20945                                                                        }
20946                                                                }
20947                                                        }
20948                                                }
20949                                        }
20950                                        else if (procedure!=null && procedure.getProcedureObject() instanceof TObjectName) {
20951                                                TObjectName functionName = new TObjectName();
20952                                                functionName.setString(procedure.getName());
20953                                                Function function = (Function)createFunction(functionName);
20954                                                // The name above is display text with no tokens: the result stands for this routine.
20955                                                function.setAliasKey(procedure.getAliasKey());
20956                                                if (stmt.getColumnList() != null) {
20957                                                        for (int i = 0; i < stmt.getColumnList().size(); i++) {
20958                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
20959                                                                relation.setEffectType(effectType);
20960                                                                TableColumn tableColumn = modelFactory.createInsertTableColumn(tableModel,
20961                                                                                stmt.getColumnList().getObjectName(i));
20962                                                                relation.setTarget(new TableColumnRelationshipElement(tableColumn));
20963                                                                relation.addSource(new ResultColumnRelationshipElement(function.getColumns().get(0)));
20964                                                                Process process = modelFactory.createProcess(stmt);
20965                                                                relation.setProcess(process);
20966                                                        }
20967                                                }
20968                                        }
20969                                } else if (stmt.getExecuteStmt() != null) {
20970                                        // INSERT INTO t EXEC(@sql) / EXEC('...'): the string form has no module name,
20971                                        // so the branch above never saw it - no dynamic site was recorded and the
20972                                        // inner statement was never analyzed. Analyze the EXEC like its top-level
20973                                        // twin (site + inner lineage); the INSERT target itself stays unlinked.
20974                                        analyzeCustomSqlStmt(stmt.getExecuteStmt());
20975                                }
20976                        } else if (stmt.getValues() != null && stmt.getValues().size() > 0 && tableModel.isCreateTable() && !tableModel.getColumns().isEmpty()) {
20977                                for (int k = 0; stmt.getValues() != null && k < stmt.getValues().size(); k++) {
20978                                        TResultColumnList columns = stmt.getValues().getMultiTarget(k).getColumnList();
20979                                        boolean allConstant = true;
20980                                        Process process = modelFactory.createProcess(stmt);
20981                                        int columnSize = tableModel.getColumns().size();
20982                                        for (int x = 0; x < columns.size(); x++) {
20983                                                if (x >= columnSize) {
20984                                                        break;
20985                                                }
20986                                                TableColumn tableColumn = tableModel.getColumns().get(x);
20987                                                TResultColumn columnObject = columns.getResultColumn(x);
20988                                                if (columnObject == null) {
20989                                                        continue;
20990                                                }
20991                                                TExpression valueExpr = columnObject.getExpr();
20992                                                columnsInExpr visitor = new columnsInExpr();
20993                                                valueExpr.inOrderTraverse(visitor);
20994                                                List<TObjectName> objectNames = visitor.getObjectNames();
20995                                                List<TParseTreeNode> constants = visitor.getConstants();
20996                                                List<TParseTreeNode> functions = visitor.getFunctions();
20997                                                List<TSelectSqlStatement> subquerys = visitor.getSubquerys();
20998
20999                                                if (functions != null && !functions.isEmpty()) {
21000                                                        analyzeFunctionDataFlowRelation(tableColumn, functions, effectType, process);
21001                                                        allConstant = false;
21002                                                }
21003
21004                                                if (subquerys != null && !subquerys.isEmpty()) {
21005                                                        analyzeSubqueryDataFlowRelation(tableColumn, subquerys, effectType, process);
21006                                                        allConstant = false;
21007                                                }
21008                                                if (objectNames != null && !objectNames.isEmpty()) {
21009                                                        analyzeDataFlowRelation(tableColumn, objectNames, null, effectType, functions,
21010                                                                        process);
21011                                                        allConstant = false;
21012                                                }
21013                                                //insert into values generate too many constant relations, ignore constant relations.
21014                                                if (constants != null && !constants.isEmpty() && stmt.getParentStmt() != null) {
21015                                                        analyzeConstantDataFlowRelation(tableColumn, constants, effectType, functions,
21016                                                                        process);
21017                                                        allConstant = false;
21018                                                }
21019                                        }
21020                                        
21021                                        if(allConstant) {
21022                                                modelManager.unbindProcessModel(stmt);
21023                                                tableModel.removeProcess(process);
21024                                        }
21025                                }
21026                        } else if (stmt.getRecordName() != null) {
21027                                // The record INSERT ... VALUES rec names.
21028                                Table recordTable = modelManager.visibleLocal(withoutColon(stmt.getRecordName().toString()),
21029                                                LocalSymbolKey.Kind.VARIABLE);
21030                                if (recordTable != null) {
21031                                        // Oracle fills the table by position. A record declared as a row of this very table has the
21032                                        // table's columns as its fields, in order, so each field fills the column of its name; any other
21033                                        // record is still paired by its fields' own names, which position does not bear out.
21034                                        boolean rowOfTarget = recordTable instanceof Variable
21035                                                        && ((Variable) recordTable).getRowTypeAnchor() == tableModel;
21036                                        for (int i = 0; i < recordTable.getColumns().size(); i++) {
21037                                                TableColumn sourceTableColumn = recordTable.getColumns().get(i);
21038                                                TObjectName field = sourceTableColumn.getColumnObject();
21039                                                TableColumn targetTableColumn = modelFactory.createTableColumn(tableModel,
21040                                                                rowOfTarget ? recordFieldColumnName(field) : field, false);
21041                                                if (targetTableColumn != null) {
21042                                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
21043                                                        relation.setEffectType(effectType);
21044                                                        relation.setTarget(new TableColumnRelationshipElement(targetTableColumn));
21045                                                        relation.addSource(new TableColumnRelationshipElement(sourceTableColumn));
21046                                                        Process process = modelFactory.createProcess(stmt);
21047                                                        relation.setProcess(process);
21048                                                } else if (sourceTableColumn.isStarColumn() && tableModel.isCreateTable()) {
21049                                                        for (TableColumn column : tableModel.getColumns()) {
21050                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
21051                                                                relation.setEffectType(effectType);
21052                                                                relation.setTarget(new TableColumnRelationshipElement(column));
21053                                                                relation.addSource(new TableColumnRelationshipElement(sourceTableColumn));
21054                                                                Process process = modelFactory.createProcess(stmt);
21055                                                                relation.setProcess(process);
21056                                                        }
21057                                                }
21058                                        }
21059                                }
21060                        } else if (stmt.getInsertSource() == EInsertSource.values_function && stmt.getFunctionCall() != null) {
21061                                // The collection or cursor VALUES f(...) names; else a table of that name.
21062                                Table cursor = modelManager.visibleLocal(stmt.getFunctionCall().getFunctionName().toString(),
21063                                                LocalSymbolKey.Kind.CURSOR);
21064                                if (cursor == null) {
21065                                        cursor = modelManager.getTableByName(
21066                                                        DlineageUtil.getTableIdentityKey(stmt.getFunctionCall().getFunctionName().toString()));
21067                                }
21068                                if (cursor != null) {
21069                                        TObjectName starColumn = DlineageUtil.syntheticStarName();
21070                                        TableColumn insertColumn = modelFactory.createTableColumn(tableModel, starColumn, true);
21071                                        insertColumn.setShowStar(false);
21072                                        insertColumn.setExpandStar(true);
21073                                        for (int j = 0; j < cursor.getColumns().size(); j++) {
21074                                                DataFlowRelationship dataflowRelation = modelFactory.createDataFlowRelation();
21075                                                dataflowRelation.setEffectType(effectType);
21076                                                dataflowRelation.addSource(new TableColumnRelationshipElement(cursor.getColumns().get(j)));
21077                                                dataflowRelation.setTarget(new TableColumnRelationshipElement(insertColumn));
21078                                                Process process = modelFactory.createProcess(stmt);
21079                                                dataflowRelation.setProcess(process);
21080                                        }
21081                                }
21082
21083                        } else if (stmt.getInsertSource() == EInsertSource.values && stmt.getValues() != null) {
21084                                TObjectName starColumn = DlineageUtil.syntheticStarName();
21085                                TableColumn insertColumn = modelFactory.createTableColumn(tableModel, starColumn, true);
21086                                insertColumn.setShowStar(false);
21087                                insertColumn.setExpandStar(true);
21088                                for (int k = 0; stmt.getValues() != null && k < stmt.getValues().size(); k++) {
21089                                        TResultColumnList columns = stmt.getValues().getMultiTarget(k).getColumnList();
21090                                        for (int x = 0; x < columns.size(); x++) {
21091                                                TResultColumn columnObject = columns.getResultColumn(x);
21092                                                if (columnObject == null) {
21093                                                        continue;
21094                                                }
21095                                                TExpression valueExpr = columnObject.getExpr();
21096                                                columnsInExpr visitor = new columnsInExpr();
21097                                                valueExpr.inOrderTraverse(visitor);
21098                                                List<TObjectName> objectNames = visitor.getObjectNames();
21099                                                List<TParseTreeNode> constants = visitor.getConstants();
21100                                                List<TParseTreeNode> functions = visitor.getFunctions();
21101                                                List<TSelectSqlStatement> subquerys = visitor.getSubquerys();
21102
21103                                                Process process = modelFactory.createProcess(stmt);
21104                                                if (functions != null && !functions.isEmpty()) {
21105                                                        analyzeFunctionDataFlowRelation(insertColumn, functions, effectType, process);
21106                                                }
21107
21108                                                if (subquerys != null && !subquerys.isEmpty()) {
21109                                                        analyzeSubqueryDataFlowRelation(insertColumn, subquerys, effectType, process);
21110                                                }
21111                                                if (objectNames != null && !objectNames.isEmpty()) {
21112                                                        analyzeDataFlowRelation(insertColumn, objectNames, null, effectType, functions,
21113                                                                        process);
21114                                                }
21115                                                //insert into values generate too many constant relations, ignore constant relations.
21116                                                if (constants != null && !constants.isEmpty() && stmt.getParentStmt() != null) {
21117                                                        analyzeConstantDataFlowRelation(insertColumn, constants, effectType, functions,
21118                                                                        process);
21119                                                }
21120                                        }
21121                                }
21122                        }else if (stmt.getExecuteStmt() != null && stmt.getExecuteStmt().getModuleName() != null) {
21123                                analyzeCustomSqlStmt(stmt.getExecuteStmt());
21124                                Procedure procedure = modelManager.getProcedure(nameAliasKey(stmt.getExecuteStmt().getModuleName(), null));
21125                                if (gudusoft.gsqlparser.dlineage.routine.RoutineShadow.enabled()) shadowCallee("insertExec", stmt.getExecuteStmt().getModuleName(), procedure, new Integer[] {null}, gudusoft.gsqlparser.catalog.runtime.CatalogObjectKind.PROCEDURE);
21126                                if ((procedure != null && procedure.getProcedureObject() instanceof TStoredProcedureSqlStatement)
21127                                                || isSpExecuteExternalScript(stmt.getExecuteStmt().getModuleName())) {
21128                                        List<ResultSet> resultSetModels = calleeResultSetsForInsertExec(stmt.getExecuteStmt(), procedure);
21129                                        if (resultSetModels != null) {
21130                                                for (ResultSet resultSetModel : resultSetModels) {
21131                                                        if (resultSetModel != null) {
21132                                                                for (int i = 0; i < resultSetModel.getColumns().size(); i++) {
21133                                                                        ResultColumn resultColumn = resultSetModel.getColumns().get(i);
21134                                                                        
21135                                                                        Transform transform = new Transform();
21136                                                                        transform.setType(Transform.FUNCTION);
21137                                                                        transform.setCode(stmt.getExecuteStmt().getModuleName());
21138                                                                        resultColumn.setTransform(transform);
21139                                                                        
21140                                                                        TAliasClause alias = null;
21141
21142                                                                        if (resultColumn.getColumnObject() instanceof TResultColumn) {
21143                                                                                alias = ((TResultColumn) resultColumn.getColumnObject())
21144                                                                                                .getAliasClause();
21145                                                                        }
21146                                                                        
21147                                                                        if (alias != null && alias.getAliasName() != null) {
21148                                                                                TableColumn tableColumn;
21149                                                                                if (!initColumn) {
21150                                                                                        if (tableModel.isCreateTable()
21151                                                                                                        && !containStarColumn(tableModel.getColumns())) {
21152                                                                                                if(resultColumn.isStarColumn()) {
21153                                                                                                        for(TableColumn tableColumnItem: tableModel.getColumns()) {
21154                                                                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
21155                                                                                                                relation.setEffectType(effectType);
21156                                                                                                                relation.setTarget(new TableColumnRelationshipElement(tableColumnItem));
21157                                                                                                                relation.addSource(new ResultColumnRelationshipElement(resultColumn));
21158                                                                                                                Process process = modelFactory.createProcess(stmt);
21159                                                                                                                relation.setProcess(process);
21160                                                                                                        }
21161                                                                                                        continue;
21162                                                                                                }
21163                                                                                                else {
21164                                                                                                        if (tableModel.getColumns().size() <= i) {
21165                                                                                                                continue;
21166                                                                                                        }
21167                                                                                                        tableColumn = tableModel.getColumns().get(i);
21168                                                                                                }
21169                                                                                        } else {
21170                                                                                                tableColumn = modelFactory.createInsertTableColumn(tableModel,
21171                                                                                                                alias.getAliasName());
21172                                                                                        }
21173                                                                                } else {
21174                                                                                        tableColumn = matchColumn(tableColumns, alias.getAliasName());
21175                                                                                        if (tableColumn == null) {
21176                                                                                                continue;
21177                                                                                        }
21178                                                                                }
21179                                        
21180                                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
21181                                                                                relation.setEffectType(effectType);
21182                                                                                relation.setTarget(new TableColumnRelationshipElement(tableColumn));
21183                                                                                relation.addSource(new ResultColumnRelationshipElement(resultColumn));
21184                                                                                Process process = modelFactory.createProcess(stmt);
21185                                                                                relation.setProcess(process);
21186                                                                        } else if (resultColumn.getColumnObject() instanceof TObjectName
21187                                                                                        || (resultColumn.getColumnObject() instanceof TResultColumn
21188                                                                                                        && ((TResultColumn) resultColumn.getColumnObject())
21189                                                                                                                        .getFieldAttr() != null)) {
21190                                                                                TObjectName fieldAttr = null;
21191                                                                                if (resultColumn.getColumnObject() instanceof TObjectName) {
21192                                                                                        fieldAttr = (TObjectName) resultColumn.getColumnObject();
21193                                                                                } else {
21194                                                                                        fieldAttr = ((TResultColumn) resultColumn.getColumnObject()).getFieldAttr();
21195                                                                                }
21196                                                                                TableColumn tableColumn;
21197                                                                                if (!initColumn) {
21198                                                                                        if (tableModel.isCreateTable()
21199                                                                                                        && !containStarColumn(tableModel.getColumns())) {
21200                                                                                                if(resultColumn.isStarColumn()) {
21201                                                                                                        for(TableColumn tableColumnItem: tableModel.getColumns()) {
21202                                                                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
21203                                                                                                                relation.setEffectType(effectType);
21204                                                                                                                relation.setTarget(new TableColumnRelationshipElement(tableColumnItem));
21205                                                                                                                relation.addSource(new ResultColumnRelationshipElement(resultColumn));
21206                                                                                                                Process process = modelFactory.createProcess(stmt);
21207                                                                                                                relation.setProcess(process);
21208                                                                                                        }
21209                                                                                                        continue;
21210                                                                                                }
21211                                                                                                else {
21212                                                                                                        if (tableModel.getColumns().size() <= i) {
21213                                                                                                                continue;
21214                                                                                                        }
21215                                                                                                        tableColumn = tableModel.getColumns().get(i);
21216                                                                                                }
21217                                                                                        } else {
21218                                                                                                tableColumn = modelFactory.createInsertTableColumn(tableModel,
21219                                                                                                                fieldAttr);
21220                                                                                        }
21221                                                                                } else {
21222                                                                                        tableColumn = matchColumn(tableColumns, fieldAttr);
21223                                                                                        if (tableColumn == null) {
21224                                                                                                continue;
21225                                                                                        }
21226                                                                                }
21227                                                                                
21228                                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
21229                                                                                relation.setEffectType(effectType);
21230                                                                                relation.setTarget(new TableColumnRelationshipElement(tableColumn));
21231                                                                                relation.addSource(new ResultColumnRelationshipElement(resultColumn));
21232                                                                                Process process = modelFactory.createProcess(stmt);
21233                                                                                relation.setProcess(process);
21234                                                                        } else if (((TResultColumn) resultColumn.getColumnObject()).getExpr()
21235                                                                                        .getExpressionType() == EExpressionType.simple_constant_t) {
21236                                                                                if (!initColumn) {
21237                                                                                        TableColumn tableColumn;
21238                                                                                        if (tableModel.isCreateTable()
21239                                                                                                        && !containStarColumn(tableModel.getColumns())) {
21240                                                                                                if(resultColumn.isStarColumn()) {
21241                                                                                                        for(TableColumn tableColumnItem: tableModel.getColumns()) {
21242                                                                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
21243                                                                                                                relation.setEffectType(effectType);
21244                                                                                                                relation.setTarget(new TableColumnRelationshipElement(tableColumnItem));
21245                                                                                                                relation.addSource(new ResultColumnRelationshipElement(resultColumn));
21246                                                                                                                Process process = modelFactory.createProcess(stmt);
21247                                                                                                                relation.setProcess(process);
21248                                                                                                        }
21249                                                                                                        continue;
21250                                                                                                }
21251                                                                                                else {
21252                                                                                                        if (tableModel.getColumns().size() <= i) {
21253                                                                                                                continue;
21254                                                                                                        }
21255                                                                                                        tableColumn = tableModel.getColumns().get(i);
21256                                                                                                }
21257                                                                                        } else {
21258                                                                                                tableColumn = modelFactory.createInsertTableColumn(tableModel,
21259                                                                                                                ((TResultColumn) resultColumn.getColumnObject()).getExpr()
21260                                                                                                                                .getConstantOperand(),
21261                                                                                                                i);
21262                                                                                        }
21263                                                                                        
21264                                                                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
21265                                                                                        relation.setEffectType(effectType);
21266                                                                                        relation.setTarget(new TableColumnRelationshipElement(tableColumn));
21267                                                                                        relation.addSource(new ResultColumnRelationshipElement(resultColumn));
21268                                                                                        Process process = modelFactory.createProcess(stmt);
21269                                                                                        relation.setProcess(process);
21270                                                                                }
21271                                                                        } else {
21272                                                                                if (!initColumn) {
21273                                                                                        TableColumn tableColumn;
21274                                                                                        if (tableModel.isCreateTable()
21275                                                                                                        && !containStarColumn(tableModel.getColumns())) {
21276                                                                                                if(resultColumn.isStarColumn()) {
21277                                                                                                        for(TableColumn tableColumnItem: tableModel.getColumns()) {
21278                                                                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
21279                                                                                                                relation.setEffectType(effectType);
21280                                                                                                                relation.setTarget(new TableColumnRelationshipElement(tableColumnItem));
21281                                                                                                                relation.addSource(new ResultColumnRelationshipElement(resultColumn));
21282                                                                                                                Process process = modelFactory.createProcess(stmt);
21283                                                                                                                relation.setProcess(process);
21284                                                                                                        }
21285                                                                                                        continue;
21286                                                                                                }
21287                                                                                                else {
21288                                                                                                        if (tableModel.getColumns().size() <= i) {
21289                                                                                                                continue;
21290                                                                                                        }
21291                                                                                                        tableColumn = tableModel.getColumns().get(i);
21292                                                                                                }
21293                                                                                        } else {
21294                                                                                                tableColumn = modelFactory.createInsertTableColumn(tableModel,
21295                                                                                                                ((TResultColumn) resultColumn.getColumnObject()).getExpr(),
21296                                                                                                                i);
21297                                                                                        }
21298                                                                                        
21299                                                                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
21300                                                                                        relation.setEffectType(effectType);
21301                                                                                        relation.setTarget(new TableColumnRelationshipElement(tableColumn));
21302                                                                                        relation.addSource(new ResultColumnRelationshipElement(resultColumn));
21303                                                                                        Process process = modelFactory.createProcess(stmt);
21304                                                                                        relation.setProcess(process);
21305                                                                                }
21306                                                                        }
21307                                                                }
21308                                                        }
21309                                                }
21310                                        }
21311                                }
21312                                else if (procedure!=null && procedure.getProcedureObject() instanceof TObjectName) {
21313                                        TObjectName functionName = new TObjectName();
21314                                        functionName.setString(procedure.getName());
21315                                        Function function = (Function)createFunction(functionName);
21316                                        // The name above is display text with no tokens: the result stands for this routine.
21317                                        function.setAliasKey(procedure.getAliasKey());
21318                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
21319                                        relation.setEffectType(effectType);
21320                                        TObjectName starColumn = DlineageUtil.syntheticStarName();
21321                                        TableColumn tableColumn = modelFactory.createInsertTableColumn(tableModel,
21322                                                        starColumn);
21323                                        tableColumn.setExpandStar(false);
21324                                        relation.setTarget(new TableColumnRelationshipElement(tableColumn));
21325                                        relation.addSource(new ResultColumnRelationshipElement(function.getColumns().get(0)));
21326                                        Process process = modelFactory.createProcess(stmt);
21327                                        relation.setProcess(process);
21328                                }
21329                        } else if (stmt.getExecuteStmt() != null) {
21330                                // INSERT INTO t EXEC(@sql) / EXEC('...'): the string form has no module name,
21331                                // so the branch above never saw it - no dynamic site was recorded and the
21332                                // inner statement was never analyzed. Analyze the EXEC like its top-level
21333                                // twin (site + inner lineage); the INSERT target itself stays unlinked.
21334                                analyzeCustomSqlStmt(stmt.getExecuteStmt());
21335                        }
21336                }
21337                if (capturedInto != null) {
21338                        whenScopes.add(new WhenScope(capturedInto.first, columnsWrittenSince(relationsBefore, capturedTable),
21339                                        capturedInto.second));
21340                }
21341                
21342                if(stmt.getOnDuplicateKeyUpdate()!=null) {
21343                        TTable table = stmt.getTargetTable();
21344                        Table tableModel = modelFactory.createTable(table);
21345                        for(TResultColumn column: stmt.getOnDuplicateKeyUpdate()) {
21346                                if(column.getExpr()==null || column.getExpr().getExpressionType() != EExpressionType.assignment_t) {
21347                                        continue;
21348                                }
21349                                TExpression left = column.getExpr().getLeftOperand();
21350                                TExpression right = column.getExpr().getRightOperand();
21351                                TObjectName columnObject = left.getObjectOperand();
21352                                if (columnObject != null) {
21353                                        TableColumn tableColumn = modelFactory.createTableColumn(tableModel, columnObject, false);
21354                                        if (tableColumn != null) {
21355                                                columnsInExpr visitor = new columnsInExpr();
21356                                                right.inOrderTraverse(visitor);
21357                                                List<TObjectName> objectNames = visitor.getObjectNames();
21358                                                List<TParseTreeNode> functions = visitor.getFunctions();
21359                                                List<TParseTreeNode> constants = visitor.getConstants();
21360                                                List<TSelectSqlStatement> subquerys = visitor.getSubquerys();
21361
21362                                                if (functions != null && !functions.isEmpty()) {
21363                                                        analyzeFunctionDataFlowRelation(tableColumn, functions, EffectType.update);
21364                                                }
21365                                                if (subquerys != null && !subquerys.isEmpty()) {
21366                                                        analyzeSubqueryDataFlowRelation(tableColumn, subquerys, EffectType.update);
21367                                                }
21368                                                if (objectNames != null && !objectNames.isEmpty()) {
21369                                                        analyzeDataFlowRelation(tableColumn, objectNames, EffectType.update, functions);
21370                                                }
21371                                                if (constants != null && !constants.isEmpty()) {
21372                                                        analyzeConstantDataFlowRelation(tableColumn, constants, EffectType.update, functions);
21373                                                }
21374                                        }
21375                                }
21376                        }
21377                }
21378
21379                if (!whenScopes.isEmpty() && stmt.getSubQuery() != null) {
21380                        Collections.sort(whenScopes, new Comparator<WhenScope>() {
21381                                @Override
21382                                public int compare(WhenScope a, WhenScope b) {
21383                                        return a.position < b.position ? -1 : (a.position == b.position ? 0 : 1);
21384                                }
21385                        });
21386                        TSelectSqlStatement subquery = stmt.getSubQuery();
21387                        Object model = modelManager.getModel(subquery);
21388                        if (!(model instanceof ResultSet) && subquery.getResultColumnList() != null) {
21389                                model = modelManager.getModel(subquery.getResultColumnList());
21390                        }
21391                        ResultSet subqueryRows = model instanceof ResultSet ? (ResultSet) model : null;
21392                        Map<TExpression, List<Pair<ResultColumn, TObjectName>>> conditionReads = new IdentityHashMap<TExpression, List<Pair<ResultColumn, TObjectName>>>();
21393                        for (WhenScope scope : whenScopes) {
21394                                analyzeInsertWhenImpact(subquery, subqueryRows, scope, conditionReads, effectType);
21395                        }
21396                }
21397        }
21398
21399        /**
21400         * Check whether a SELECT statement actually returns a result set to the client.
21401         * A SELECT does NOT return a result set when:
21402         * <ul>
21403         *   <li>It has an INTO clause ({@code SELECT ... INTO @var})</li>
21404         *   <li>It has an INTO TABLE clause ({@code SELECT ... INTO TABLE t})</li>
21405         *   <li>All result columns are variable assignments ({@code SELECT @var = expr}) —
21406         *       MSSQL-only, detected via {@code EExpressionType.sqlserver_proprietary_column_alias_t}</li>
21407         * </ul>
21408         */
21409        private static boolean isResultSetReturningSelect(TSelectSqlStatement select) {
21410                if (select.getIntoClause() != null || select.getIntoTableClause() != null) {
21411                        return false;
21412                }
21413                if (select.getResultColumnList() != null && select.getResultColumnList().size() > 0) {
21414                        boolean allAssignments = true;
21415                        for (int i = 0; i < select.getResultColumnList().size(); i++) {
21416                                TResultColumn col = select.getResultColumnList().getResultColumn(i);
21417                                if (col.getExpr() == null
21418                                                || col.getExpr().getExpressionType() != EExpressionType.sqlserver_proprietary_column_alias_t) {
21419                                        allAssignments = false;
21420                                        break;
21421                                }
21422                        }
21423                        if (allAssignments) {
21424                                return false;
21425                        }
21426                }
21427                return true;
21428        }
21429
21430        private List<TSelectSqlStatement> getLastSelectStmt(TStoredProcedureSqlStatement procedureStmt) {
21431                List<TSelectSqlStatement> stmts = new ArrayList<TSelectSqlStatement>();
21432                if (procedureStmt.getBodyStatements().size() > 0) {
21433                        for (int j = procedureStmt.getBodyStatements().size() - 1; j >= 0; j--) {
21434                                TCustomSqlStatement stmtItem = procedureStmt.getBodyStatements().get(j);
21435                                if (stmtItem instanceof TReturnStmt || stmtItem instanceof TMssqlReturn) {
21436                                        if (stmtItem.getStatements() != null) {
21437                                                List<TSelectSqlStatement> item = getLastSelectStmt(stmtItem);
21438                                                if (item != null && !item.isEmpty()) {
21439                                                        stmts.addAll(item);
21440                                                        if(option.getVendor()!=EDbVendor.dbvmssql && option.getVendor()!=EDbVendor.dbvazuresql) {
21441                                                                break;
21442                                                        }
21443                                                }
21444                                        }
21445                                        break;
21446                                }
21447                                if (stmtItem instanceof TSelectSqlStatement && !((TSelectSqlStatement) stmtItem).isQueryOfCTE()) {
21448                                        if (isResultSetReturningSelect((TSelectSqlStatement) stmtItem)) {
21449                                                if(!(DlineageUtil.getTopBasicStmt(stmtItem) instanceof TSelectSqlStatement)) {
21450                                                        continue;
21451                                                }
21452                                                stmts.add((TSelectSqlStatement) stmtItem);
21453                                                if(option.getVendor()!=EDbVendor.dbvmssql && option.getVendor()!=EDbVendor.dbvazuresql) {
21454                                                        break;
21455                                                }
21456                                        }
21457                                } else if (stmtItem.getStatements() != null) {
21458                                        List<TSelectSqlStatement> item = getLastSelectStmt(stmtItem);
21459                                        if (item != null && !item.isEmpty()) {
21460                                                stmts.addAll(item);
21461                                                if(option.getVendor()!=EDbVendor.dbvmssql && option.getVendor()!=EDbVendor.dbvazuresql) {
21462                                                        break;
21463                                                }
21464                                        }
21465                                }
21466                        }
21467                }
21468                return stmts;
21469        }
21470
21471        private List<TSelectSqlStatement> getLastSelectStmt(TCustomSqlStatement stmt) {
21472                List<TSelectSqlStatement> stmts = new ArrayList<TSelectSqlStatement>();
21473                for (int j = stmt.getStatements().size() - 1; j >= 0; j--) {
21474                        TCustomSqlStatement stmtItem = stmt.getStatements().get(j);
21475                        if (stmtItem instanceof TReturnStmt || stmtItem instanceof TMssqlReturn) {
21476                                if (stmtItem.getStatements() != null) {
21477                                        List<TSelectSqlStatement> item = getLastSelectStmt(stmtItem);
21478                                        if (item != null && !item.isEmpty()) {
21479                                                stmts.addAll(item);
21480                                                if(option.getVendor()!=EDbVendor.dbvmssql && option.getVendor()!=EDbVendor.dbvazuresql) {
21481                                                        break;
21482                                                }
21483                                        }
21484                                }
21485                                break;
21486                        }
21487                        if (stmtItem instanceof TSelectSqlStatement && !((TSelectSqlStatement) stmtItem).isQueryOfCTE()) {
21488                                if (isResultSetReturningSelect((TSelectSqlStatement) stmtItem)) {
21489                                        if(!(DlineageUtil.getTopBasicStmt(stmtItem) instanceof TSelectSqlStatement)) {
21490                                                continue;
21491                                        }
21492                                        stmts.add((TSelectSqlStatement) stmtItem);
21493                                        if(option.getVendor()!=EDbVendor.dbvmssql && option.getVendor()!=EDbVendor.dbvazuresql) {
21494                                                break;
21495                                        }
21496                                }
21497                        } else if (stmtItem.getStatements() != null) {
21498                                List<TSelectSqlStatement> item = getLastSelectStmt(stmtItem);
21499                                if (item != null && !item.isEmpty()) {
21500                                        stmts.addAll(item);
21501                                        if(option.getVendor()!=EDbVendor.dbvmssql && option.getVendor()!=EDbVendor.dbvazuresql) {
21502                                                break;
21503                                        }
21504                                }
21505                        }
21506                }
21507                return stmts;
21508        }
21509
21510        private TableColumn getStarColumn(List<TableColumn> columns) {
21511                for (TableColumn column : columns) {
21512                        if (column.isStarColumn()) {
21513                                return column;
21514                        }
21515                }
21516                return null;
21517        }
21518
21519        private boolean containStarColumn(List<TableColumn> columns) {
21520                if (columns == null)
21521                        return false;
21522                for (TableColumn column : columns) {
21523                        if (column.isStarColumn()) {
21524                                return true;
21525                        }
21526                }
21527                return false;
21528        }
21529
21530        private boolean containStarColumn(ResultSet resultSet) {
21531                if (resultSet == null || resultSet.getColumns() == null)
21532                        return false;
21533                for (ResultColumn column : resultSet.getColumns()) {
21534                        if (column.isStarColumn()) {
21535                                return true;
21536                        }
21537                }
21538                return false;
21539        }
21540
21541        private int indexOfColumn(List<TResultColumn> columns, TObjectName objectName) {
21542                for (int i = 0; i < columns.size(); i++) {
21543                        if (columns.get(i).toString().trim().equalsIgnoreCase(objectName.toString().trim())) {
21544                                return i;
21545                        }
21546                }
21547                return -1;
21548        }
21549
21550        private boolean isEmptyCollection(Collection<?> keyMap) {
21551                return keyMap == null || keyMap.isEmpty();
21552        }
21553
21554        private void analyzeUpdateStmt(TUpdateSqlStatement stmt) {
21555                if (stmt.getResultColumnList() == null)
21556                        return;
21557
21558                TTable targetTable = stmt.getTargetTable();
21559                Process process = modelFactory.createProcess(stmt);
21560                Table tableModel = null;
21561                QueryTable cteTargetModel = null;
21562                CteWriteTargetResolution cteTargetResolution = null;
21563                if (targetTable.getCTE() != null) {
21564                        cteTargetModel = analyzeCteWriteTarget(targetTable, process);
21565                        cteTargetResolution = resolveCteWriteTarget(targetTable, collectUpdateCteWriteIntent(stmt));
21566                        if (cteTargetResolution.isWritable()) {
21567                                tableModel = modelFactory.createTable(cteTargetResolution.baseTable);
21568                                tableModel.addProcess(process);
21569                                createProjectedWriteImpact(targetTable.getCTE().getSubquery(), tableModel,
21570                                                EffectType.update, process);
21571                        }
21572                        if (cteTargetResolution.failureReason != null) {
21573                                addCteWriteTargetHint(targetTable, cteTargetResolution.failureReason,
21574                                                cteTargetResolution.isWritable());
21575                        }
21576                } else if (targetTable.getSubquery() != null
21577                                || (targetTable.getLinkTable() != null
21578                                                && targetTable.getLinkTable().getSubquery() != null)) {
21579                        TTable projectedTarget = targetTable.getSubquery() != null
21580                                        ? targetTable : targetTable.getLinkTable();
21581                        TSelectSqlStatement projectedSubquery = projectedTarget.getSubquery();
21582                        TObjectNameList exposedColumns = getProjectedTargetColumns(projectedTarget);
21583                        cteTargetModel = analyzeProjectedWriteTarget(targetTable, projectedSubquery,
21584                                        exposedColumns, process);
21585                        cteTargetResolution = resolveProjectedWriteTarget(projectedSubquery, exposedColumns,
21586                                        collectUpdateCteWriteIntent(stmt));
21587                        if (cteTargetResolution.isWritable()) {
21588                                tableModel = modelFactory.createTable(cteTargetResolution.baseTable);
21589                                tableModel.addProcess(process);
21590                                createProjectedWriteImpact(projectedSubquery, tableModel, EffectType.update, process);
21591                        }
21592                        if (cteTargetResolution.failureReason != null) {
21593                                addCteWriteTargetHint(targetTable, cteTargetResolution.failureReason,
21594                                                cteTargetResolution.isWritable());
21595                        }
21596                } else {
21597                        tableModel = modelFactory.createTable(targetTable);
21598                        tableModel.addProcess(process);
21599                }
21600
21601                for (int i = 0; i < stmt.tables.size(); i++) {
21602                        TTable tableElement = stmt.tables.getTable(i);
21603                        if (tableElement == targetTable && cteTargetModel != null) {
21604                                continue;
21605                        }
21606                        if (tableElement.getSubquery() != null) {
21607                                QueryTable queryTable = modelFactory.createQueryTable(tableElement);
21608                                TSelectSqlStatement subquery = tableElement.getSubquery();
21609                                analyzeSelectStmt(subquery);
21610
21611                                if (subquery.getSetOperatorType() != ESetOperatorType.none) {
21612                                        SelectSetResultSet selectSetResultSetModel = (SelectSetResultSet) modelManager.getModel(subquery);
21613                                        for (int j = 0; j < selectSetResultSetModel.getColumns().size(); j++) {
21614                                                ResultColumn sourceColumn = selectSetResultSetModel.getColumns().get(j);
21615                                                ResultColumn targetColumn = modelFactory.createSelectSetResultColumn(queryTable, sourceColumn);
21616                                                DataFlowRelationship selectSetRalation = modelFactory.createDataFlowRelation();
21617                                                selectSetRalation.setEffectType(EffectType.select);
21618                                                selectSetRalation.setTarget(new ResultColumnRelationshipElement(targetColumn));
21619                                                selectSetRalation.addSource(new ResultColumnRelationshipElement(sourceColumn));
21620                                                selectSetRalation.setProcess(process);
21621                                        }
21622                                }
21623
21624                                ResultSet resultSetModel = (ResultSet) modelManager.getModel(tableElement.getSubquery());
21625                                if (resultSetModel != null && resultSetModel != queryTable
21626                                                && !resultSetModel.getRelationRows().getHoldRelations().isEmpty()) {
21627                                        ImpactRelationship impactRelation = modelFactory.createImpactRelation();
21628                                        impactRelation.setEffectType(EffectType.update);
21629                                        impactRelation.addSource(new RelationRowsRelationshipElement<ResultSetRelationRows>(
21630                                                        resultSetModel.getRelationRows()));
21631                                        impactRelation.setTarget(
21632                                                        new RelationRowsRelationshipElement<ResultSetRelationRows>(queryTable.getRelationRows()));
21633                                }
21634
21635                        } else if (tableElement.getCTE() != null) {
21636                                QueryTable queryTable = modelFactory.createQueryTable(tableElement);
21637
21638                                TObjectNameList cteColumns = tableElement.getCTE().getColumnList();
21639                                if (cteColumns != null) {
21640                                        for (int j = 0; j < cteColumns.size(); j++) {
21641                                                modelFactory.createResultColumn(queryTable, cteColumns.getObjectName(j));
21642                                        }
21643                                }
21644                                TSelectSqlStatement subquery = tableElement.getCTE().getSubquery();
21645                                if (subquery != null && !stmtStack.contains(subquery)) {
21646                                        analyzeSelectStmt(subquery);
21647
21648                                        ResultSet resultSetModel = (ResultSet) modelManager.getModel(subquery);
21649                                        if (resultSetModel != null && resultSetModel != queryTable
21650                                                        && !resultSetModel.getRelationRows().getHoldRelations().isEmpty()) {
21651                                                ImpactRelationship impactRelation = modelFactory.createImpactRelation();
21652                                                impactRelation.setEffectType(EffectType.select);
21653                                                impactRelation.addSource(new RelationRowsRelationshipElement<ResultSetRelationRows>(
21654                                                                resultSetModel.getRelationRows()));
21655                                                impactRelation.setTarget(new RelationRowsRelationshipElement<ResultSetRelationRows>(
21656                                                                queryTable.getRelationRows()));
21657                                        }
21658
21659                                        if (resultSetModel == null) {
21660                                                // a CTE defined by VALUES has no result set model (Mantis 5010): reading it threw
21661                                                // NullPointerException
21662                                                if (subquery.getValueClause() != null) {
21663                                                        analyzeValuesCte(queryTable, subquery.getValueClause(), cteColumns, process);
21664                                                }
21665                                        } else if (cteColumns != null) {
21666                                                // a column list names positions, which a star of unknown width or a list shorter than the query
21667                                                // sets apart from the indexes of the items: each CTE column reads the items that can stand at its
21668                                                // position, from each select of a set operation, as a query reading the CTE does (Mantis 5003).
21669                                                // Pairing them one by one ran past a short list.
21670                                                List<SelectPositions> setSelects = subquery.getSetOperatorType() != ESetOperatorType.none
21671                                                                ? setOperationPositions(subquery, queryTable.getColumns().size(),
21672                                                                                shorterList(cteColumns, tableElement))
21673                                                                : null;
21674                                                mapCteColumnsByCandidates(queryTable, setSelects != null ? setSelects
21675                                                                : Collections.singletonList(selectPositions(resultSetModel.getColumns(),
21676                                                                                queryTable.getColumns().size(), shorterList(cteColumns, tableElement))),
21677                                                                cteColumns, process);
21678                                        } else if (subquery.getSetOperatorType() != ESetOperatorType.none) {
21679                                                SelectSetResultSet selectSetResultSetModel = (SelectSetResultSet) modelManager
21680                                                                .getModel(subquery);
21681                                                for (int j = 0; j < selectSetResultSetModel.getColumns().size(); j++) {
21682                                                        ResultColumn sourceColumn = selectSetResultSetModel.getColumns().get(j);
21683                                                        ResultColumn targetColumn = modelFactory.createSelectSetResultColumn(queryTable, sourceColumn);
21684                                                        for (TObjectName starLinkColumn : sourceColumn.getStarLinkColumnList()) {
21685                                                                targetColumn.bindStarLinkColumn(starLinkColumn);
21686                                                        }
21687                                                        createCteSelectRelation(process, targetColumn, sourceColumn);
21688                                                }
21689                                        } else {
21690                                                for (int j = 0; j < resultSetModel.getColumns().size(); j++) {
21691                                                        ResultColumn sourceColumn = resultSetModel.getColumns().get(j);
21692                                                        ResultColumn targetColumn = modelFactory.createSelectSetResultColumn(queryTable, sourceColumn);
21693                                                        for (TObjectName starLinkColumn : sourceColumn.getStarLinkColumnList()) {
21694                                                                targetColumn.bindStarLinkColumn(starLinkColumn);
21695                                                        }
21696                                                        createCteSelectRelation(process, targetColumn, sourceColumn);
21697                                                }
21698                                        }
21699                                } else if (tableElement.getCTE().getUpdateStmt() != null) {
21700                                        analyzeCustomSqlStmt(tableElement.getCTE().getUpdateStmt());
21701                                } else if (tableElement.getCTE().getInsertStmt() != null) {
21702                                        analyzeCustomSqlStmt(tableElement.getCTE().getInsertStmt());
21703                                } else if (tableElement.getCTE().getDeleteStmt() != null) {
21704                                        analyzeCustomSqlStmt(tableElement.getCTE().getDeleteStmt());
21705                                } else if (tableElement.getCTE().getMergeStmt() != null) {
21706                                        analyzeCustomSqlStmt(tableElement.getCTE().getMergeStmt());
21707                                }
21708                        } else {
21709                                modelFactory.createTable(stmt.tables.getTable(i));
21710                        }
21711                }
21712
21713                for (int i = 0; i < stmt.getResultColumnList().size(); i++) {
21714                        TResultColumn field = stmt.getResultColumnList().getResultColumn(i);
21715
21716                        if (field.getExpr().getExpressionType() == EExpressionType.function_t) {
21717                                // Handle SQL Server XML modify() method for data lineage
21718                                TFunctionCall funcCall = field.getExpr().getFunctionCall();
21719                                if (tableModel != null && funcCall != null
21720                                                && funcCall.getFunctionType() == EFunctionType.xmlmodify_t) {
21721                                        analyzeXmlModifyFunction(stmt, tableModel, process, funcCall);
21722                                }
21723                                continue;
21724                        }
21725
21726                        TExpression expression = updateSetTarget(field.getExpr().getLeftOperand());
21727                        if (expression == null) {
21728                                ErrorInfo errorInfo = new ErrorInfo();
21729                                errorInfo.setErrorType(ErrorInfo.ANALYZE_ERROR);
21730                                errorInfo.setErrorMessage(
21731                                                "Can't get result column expression. Expression is " + field.getExpr().toString());
21732                                errorInfo.setStartPosition(new Pair3<Long, Long, String>(field.getExpr().getStartToken().lineNo,
21733                                                field.getExpr().getStartToken().columnNo, ModelBindingManager.getGlobalHash()));
21734                                errorInfo.setEndPosition(new Pair3<Long, Long, String>(field.getExpr().getEndToken().lineNo,
21735                                                field.getExpr().getEndToken().columnNo + field.getExpr().getEndToken().getAstext().length(),
21736                                                ModelBindingManager.getGlobalHash()));
21737                                errorInfo.fillInfo(this);
21738                                errorInfos.add(errorInfo);
21739                                continue;
21740                        }
21741                        if (expression.getExpressionType() == EExpressionType.list_t) {
21742                                TExpression setExpression = field.getExpr().getRightOperand();
21743                                if (setExpression != null && setExpression.getSubQuery() != null) {
21744                                        TSelectSqlStatement query = setExpression.getSubQuery();
21745                                        analyzeSelectStmt(query);
21746
21747                                        SelectResultSet resultSetModel = (SelectResultSet) modelManager
21748                                                        .getModel(query.getResultColumnList());
21749
21750                                        if (resultSetModel != null && !resultSetModel.getRelationRows().getHoldRelations().isEmpty()) {
21751                                                ImpactRelationship impactRelation = modelFactory.createImpactRelation();
21752                                                impactRelation.setEffectType(EffectType.update);
21753                                                impactRelation.addSource(new RelationRowsRelationshipElement<ResultSetRelationRows>(
21754                                                                resultSetModel.getRelationRows()));
21755                                                if (tableModel != null) {
21756                                                        impactRelation.setTarget(new RelationRowsRelationshipElement<TableRelationRows>(
21757                                                                        tableModel.getRelationRows()));
21758                                                } else if (cteTargetModel != null) {
21759                                                        impactRelation.setTarget(new RelationRowsRelationshipElement<ResultSetRelationRows>(
21760                                                                        cteTargetModel.getRelationRows()));
21761                                                }
21762                                        }
21763
21764                                        TExpressionList columnList = expression.getExprList();
21765                                        for (int j = 0; j < columnList.size(); j++) {
21766                                                TObjectName column = columnList.getExpression(j).getObjectOperand();
21767
21768                                                if (column.getDbObjectType() == EDbObjectType.variable) {
21769                                                        continue;
21770                                                }
21771
21772                                                if (column.getColumnNameOnly().startsWith("@") && (option.getVendor() == EDbVendor.dbvmssql
21773                                                                || option.getVendor() == EDbVendor.dbvazuresql)) {
21774                                                        continue;
21775                                                }
21776
21777                                                if (column.getColumnNameOnly().startsWith(":") && (option.getVendor() == EDbVendor.dbvhana
21778                                                                || option.getVendor() == EDbVendor.dbvteradata)) {
21779                                                        continue;
21780                                                }
21781
21782                                                TObjectName targetColumn = getCteBaseColumn(cteTargetResolution, column);
21783                                                TableColumn tableColumn = tableModel == null || targetColumn == null ? null
21784                                                                : modelFactory.createTableColumn(tableModel, targetColumn, false);
21785                                                if (resultSetModel == null) {
21786                                                        if (tableColumn != null && query.getValueClause() != null) {
21787                                                                analyzeUpdateSetValues(tableColumn, query.getValueClause(), j, process);
21788                                                        }
21789                                                        continue;
21790                                                }
21791                                                ResultColumn resultColumn = resultSetModel.getColumns().get(j);
21792                                                if (tableColumn != null) {
21793                                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
21794                                                        relation.setEffectType(EffectType.update);
21795                                                        relation.setTarget(new TableColumnRelationshipElement(tableColumn));
21796                                                        relation.addSource(new ResultColumnRelationshipElement(resultColumn));
21797                                                        relation.setProcess(process);
21798                                                }
21799
21800                                        }
21801                                }
21802                        } else if (expression.getExpressionType() == EExpressionType.simple_object_name_t) {
21803                                TExpression setExpression = field.getExpr().getRightOperand();
21804                                if (setExpression != null && setExpression.getSubQuery() != null) {
21805                                        TSelectSqlStatement query = setExpression.getSubQuery();
21806                                        analyzeSelectStmt(query);
21807
21808                                        SelectResultSet resultSetModel = (SelectResultSet) modelManager
21809                                                        .getModel(query.getResultColumnList());
21810
21811                                        if (resultSetModel != null && !resultSetModel.getRelationRows().getHoldRelations().isEmpty()) {
21812                                                ImpactRelationship impactRelation = modelFactory.createImpactRelation();
21813                                                impactRelation.setEffectType(EffectType.update);
21814                                                impactRelation.addSource(new RelationRowsRelationshipElement<ResultSetRelationRows>(
21815                                                                resultSetModel.getRelationRows()));
21816                                                if (tableModel != null) {
21817                                                        impactRelation.setTarget(new RelationRowsRelationshipElement<TableRelationRows>(
21818                                                                        tableModel.getRelationRows()));
21819                                                } else if (cteTargetModel != null) {
21820                                                        impactRelation.setTarget(new RelationRowsRelationshipElement<ResultSetRelationRows>(
21821                                                                        cteTargetModel.getRelationRows()));
21822                                                }
21823                                        }
21824
21825                                        TObjectName column = expression.getObjectOperand();
21826                                        TObjectName targetColumn = getCteBaseColumn(cteTargetResolution, column);
21827                                        TableColumn tableColumn = tableModel == null || targetColumn == null ? null
21828                                                        : modelFactory.createTableColumn(tableModel, targetColumn, false);
21829                                        if (resultSetModel == null) {
21830                                                if (tableColumn != null && query.getValueClause() != null) {
21831                                                        analyzeUpdateSetValues(tableColumn, query.getValueClause(), 0, process);
21832                                                }
21833                                        } else {
21834                                                ResultColumn resultColumn = resultSetModel.getColumns().get(0);
21835                                                if (tableColumn != null) {
21836                                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
21837                                                        relation.setEffectType(EffectType.update);
21838                                                        relation.setTarget(new TableColumnRelationshipElement(tableColumn));
21839                                                        relation.addSource(new ResultColumnRelationshipElement(resultColumn));
21840                                                        relation.setProcess(process);
21841                                                }
21842                                        }
21843                                } else if (setExpression != null) {
21844                                        // ResultSet resultSet = modelFactory.createResultSet(stmt,
21845                                        // true);
21846
21847                                        ResultSet resultSet = modelFactory.createResultSet(stmt, false);
21848
21849                                        createPseudoImpactRelation(stmt, resultSet, EffectType.update);
21850
21851                                        TObjectName columnObject = expression.getObjectOperand();
21852
21853                                        ResultColumn updateColumn = modelFactory.createUpdateResultColumn(resultSet, columnObject);
21854
21855                                        columnsInExpr visitor = new columnsInExpr();
21856                                        field.getExpr().getRightOperand().inOrderTraverse(visitor);
21857
21858                                        List<TObjectName> objectNames = visitor.getObjectNames();
21859                                        List<TParseTreeNode> functions = visitor.getFunctions();
21860                                        List<TSelectSqlStatement> subquerys = visitor.getSubquerys();
21861
21862                                        if (functions != null && !functions.isEmpty()) {
21863                                                analyzeFunctionDataFlowRelation(updateColumn, functions, EffectType.update);
21864                                        }
21865
21866                                        if (subquerys != null && !subquerys.isEmpty()) {
21867                                                analyzeSubqueryDataFlowRelation(updateColumn, subquerys, EffectType.update);
21868                                        }
21869
21870                                        Transform transform = new Transform();
21871                                        transform.setType(Transform.EXPRESSION);
21872                                        transform.setCode(setExpression);
21873                                        updateColumn.setTransform(transform);
21874                                        analyzeDataFlowRelation(updateColumn, objectNames, EffectType.update, functions);
21875
21876                                        List<TParseTreeNode> constants = visitor.getConstants();
21877                                        analyzeConstantDataFlowRelation(updateColumn, constants, EffectType.update, functions);
21878
21879                                        TObjectName targetColumn = getCteBaseColumn(cteTargetResolution, columnObject);
21880                                        TableColumn tableColumn = tableModel == null || targetColumn == null ? null
21881                                                        : modelFactory.createTableColumn(tableModel, targetColumn, false);
21882                                        if(tableColumn!=null) {
21883                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
21884                                                relation.setEffectType(EffectType.update);
21885                                                relation.setTarget(new TableColumnRelationshipElement(tableColumn));
21886                                                relation.addSource(new ResultColumnRelationshipElement(updateColumn));
21887                                                relation.setProcess(process);
21888                                        }
21889                                        
21890                                        ImpactRelationship impactRelation = modelFactory.createImpactRelation();
21891                                        impactRelation.setEffectType(EffectType.update);
21892                                        impactRelation.addSource(
21893                                                        new RelationRowsRelationshipElement<ResultSetRelationRows>(resultSet.getRelationRows()));
21894                                        if (tableModel != null) {
21895                                                impactRelation.setTarget(new RelationRowsRelationshipElement<TableRelationRows>(
21896                                                                tableModel.getRelationRows()));
21897                                        } else if (cteTargetModel != null) {
21898                                                impactRelation.setTarget(new RelationRowsRelationshipElement<ResultSetRelationRows>(
21899                                                                cteTargetModel.getRelationRows()));
21900                                        }
21901                                }
21902                        } else {
21903                                addUnanalyzedSetAssignmentError("Unsupported UPDATE SET target", expression, field.getExpr());
21904                        }
21905                }
21906
21907                if (stmt.getJoins() != null && stmt.getJoins().size() > 0) {
21908                        for (int i = 0; i < stmt.getJoins().size(); i++) {
21909                                TJoin join = stmt.getJoins().getJoin(i);
21910                                if (join.getJoinItems() != null) {
21911                                        for (int j = 0; j < join.getJoinItems().size(); j++) {
21912                                                TJoinItem joinItem = join.getJoinItems().getJoinItem(j);
21913                                                TExpression expr = joinItem.getOnCondition();
21914                                                analyzeFilterCondition(null, expr, joinItem.getJoinType(), JoinClauseType.on,
21915                                                                EffectType.update);
21916                                        }
21917                                }
21918                        }
21919                }
21920
21921                if (stmt.getWhereClause() != null && stmt.getWhereClause().getCondition() != null) {
21922                        analyzeFilterCondition(null, stmt.getWhereClause().getCondition(), null, JoinClauseType.where,
21923                                        EffectType.update);
21924                }
21925
21926                if (stmt.getOutputClause() != null) {
21927                        TOutputClause outputClause = stmt.getOutputClause();
21928                        if (outputClause.getSelectItemList() != null) {
21929                                ResultSet resultSet = modelFactory.createResultSet(outputClause, false);
21930                                for (int j = 0; j < outputClause.getSelectItemList().size(); j++) {
21931                                        TResultColumn sourceColumn = outputClause.getSelectItemList().getResultColumn(j);
21932                                        ResultColumn sourceColumnModel = modelFactory.createResultColumn(resultSet, sourceColumn);
21933                                        analyzeResultColumn(sourceColumn, EffectType.select);
21934
21935                                        if (outputClause.getIntoTable() != null) {
21936                                                Table intoTableModel = modelFactory.createTableByName(outputClause.getIntoTable());
21937                                                intoTableModel.addProcess(process);
21938                                                if (outputClause.getIntoColumnList() != null) {
21939                                                        TableColumn intoTableColumn = modelFactory.createInsertTableColumn(intoTableModel,
21940                                                                        outputClause.getIntoColumnList().getObjectName(j));
21941
21942                                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
21943                                                        relation.setEffectType(EffectType.insert);
21944                                                        relation.setTarget(new TableColumnRelationshipElement(intoTableColumn));
21945                                                        relation.addSource(new ResultColumnRelationshipElement(sourceColumnModel));
21946                                                } else if (sourceColumn.getAliasClause() != null
21947                                                                || sourceColumn.getExpr().getObjectOperand() != null) {
21948                                                        TObjectName tableColumnObject = null;
21949                                                        if (sourceColumn.getAliasClause() != null) {
21950                                                                tableColumnObject = sourceColumn.getAliasClause().getAliasName();
21951                                                        } else {
21952                                                                tableColumnObject = sourceColumn.getExpr().getObjectOperand();
21953                                                        }
21954
21955                                                        TableColumn intoTableColumn = modelFactory.createInsertTableColumn(intoTableModel,
21956                                                                        tableColumnObject);
21957                                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
21958                                                        relation.setEffectType(EffectType.insert);
21959                                                        relation.setTarget(new TableColumnRelationshipElement(intoTableColumn));
21960                                                        relation.addSource(new ResultColumnRelationshipElement(sourceColumnModel));
21961                                                }
21962                                        }
21963                                }
21964                        }
21965                }
21966        }
21967
21968        /**
21969         * Analyzes SQL Server XML modify() function to extract data lineage from sql:column() references
21970         * in XQuery expressions.
21971         *
21972         * Example SQL:
21973         * SET [Demographics].modify('... sql:column("deleted.LineTotal") ...')
21974         *
21975         * This extracts:
21976         * - Target column: Demographics (from the XML column being modified)
21977         * - Source column: deleted.LineTotal (from sql:column() reference in XQuery)
21978         */
21979        private void analyzeXmlModifyFunction(TUpdateSqlStatement stmt, Table tableModel, Process process, TFunctionCall funcCall) {
21980                TObjectName funcName = funcCall.getFunctionName();
21981                if (funcName == null || funcName.getPartToken() == null) {
21982                        return;
21983                }
21984
21985                // Get the XML column being modified (e.g., [Demographics] from "[Demographics].modify")
21986                String xmlColumnName = funcName.getPartToken().astext;
21987
21988                // Get the XQuery argument
21989                if (funcCall.getArgs() == null || funcCall.getArgs().size() == 0) {
21990                        return;
21991                }
21992
21993                String xqueryString = funcCall.getArgs().getExpression(0).toString();
21994
21995                // Extract sql:column() references from the XQuery string
21996                List<String> sqlColumnRefs = extractSqlColumnReferences(xqueryString);
21997                if (sqlColumnRefs.isEmpty()) {
21998                        return;
21999                }
22000
22001                // Extract XPath target from XQuery (e.g., /IndividualSurvey/TotalPurchaseYTD)
22002                String xpathTarget = extractXPathTarget(xqueryString);
22003
22004                // Build full target column name including XML path
22005                String fullTargetColumnName = xmlColumnName;
22006                if (xpathTarget != null && !xpathTarget.isEmpty()) {
22007                        fullTargetColumnName = xmlColumnName + "." + xpathTarget;
22008                }
22009
22010                // Create the target table column for the XML column being modified
22011                TableColumn targetTableColumn = modelFactory.createInsertTableColumn(tableModel, fullTargetColumnName);
22012
22013                // Create source-to-target relationships for each sql:column reference
22014                for (String sqlColRef : sqlColumnRefs) {
22015                        // Parse the column reference (e.g., "deleted.LineTotal" -> table="deleted", column="LineTotal")
22016                        String[] parts = sqlColRef.split("\\.", 2);
22017                        String sourceTableName = parts.length > 1 ? parts[0] : null;
22018                        String sourceColumnName = parts.length > 1 ? parts[1] : parts[0];
22019
22020                        // Find the source table in the statement's tables
22021                        TTable sourceTable = null;
22022                        if (sourceTableName != null && stmt.tables != null) {
22023                                for (int j = 0; j < stmt.tables.size(); j++) {
22024                                        TTable t = stmt.tables.getTable(j);
22025                                        String tableName = t.getTableName().toString();
22026                                        String alias = t.getAliasName();
22027                                        if (SQLUtil.compareIdentifier(option.getVendor(), ESQLDataObjectType.dotTable, tableName, sourceTableName) ||
22028                                                (alias != null && SQLUtil.sameName(option.getVendor(), ESQLDataObjectType.dotTable, alias, sourceTableName))) {
22029                                                sourceTable = t;
22030                                                break;
22031                                        }
22032                                }
22033                        }
22034
22035                        if (sourceTable != null && targetTableColumn != null) {
22036                                // Create a table model for the source if needed
22037                                Table sourceTableModel = modelFactory.createTable(sourceTable);
22038                                TableColumn sourceColumn = modelFactory.createInsertTableColumn(sourceTableModel, sourceColumnName);
22039
22040                                if (sourceColumn != null) {
22041                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
22042                                        relation.setEffectType(EffectType.update);
22043                                        relation.setTarget(new TableColumnRelationshipElement(targetTableColumn));
22044                                        relation.addSource(new TableColumnRelationshipElement(sourceColumn));
22045                                        relation.setProcess(process);
22046                                        relation.setFunction("modify");
22047                                }
22048                        }
22049                }
22050        }
22051
22052        /**
22053         * Extracts sql:column() references from an XQuery string.
22054         * Example: 'sql:column("deleted.LineTotal")' -> ["deleted.LineTotal"]
22055         */
22056        private List<String> extractSqlColumnReferences(String xquery) {
22057                List<String> refs = new ArrayList<>();
22058                if (xquery == null) {
22059                        return refs;
22060                }
22061
22062                int startIdx = 0;
22063                while ((startIdx = xquery.indexOf("sql:column(", startIdx)) >= 0) {
22064                        int parenStart = startIdx + "sql:column(".length();
22065                        int parenEnd = xquery.indexOf(")", parenStart);
22066                        if (parenEnd < 0) {
22067                                break;
22068                        }
22069
22070                        String arg = xquery.substring(parenStart, parenEnd).trim();
22071                        // Remove quotes (single or double)
22072                        if ((arg.startsWith("\"") && arg.endsWith("\"")) ||
22073                                (arg.startsWith("'") && arg.endsWith("'"))) {
22074                                arg = arg.substring(1, arg.length() - 1);
22075                        }
22076
22077                        if (!arg.isEmpty()) {
22078                                refs.add(arg);
22079                        }
22080
22081                        startIdx = parenEnd + 1;
22082                }
22083
22084                return refs;
22085        }
22086
22087        /**
22088         * Extracts the XPath target from an XQuery modify expression.
22089         * Example: 'replace value of (/IndividualSurvey/TotalPurchaseYTD)[1]' -> "IndividualSurvey.TotalPurchaseYTD"
22090         */
22091        private String extractXPathTarget(String xquery) {
22092                if (xquery == null) {
22093                        return null;
22094                }
22095
22096                // Look for patterns like "(/path/to/element)" or "(/path/to/element)[1]"
22097                int replaceIdx = xquery.indexOf("replace value of");
22098                if (replaceIdx < 0) {
22099                        return null;
22100                }
22101
22102                int parenStart = xquery.indexOf("(/", replaceIdx);
22103                if (parenStart < 0) {
22104                        return null;
22105                }
22106
22107                int parenEnd = xquery.indexOf(")", parenStart);
22108                if (parenEnd < 0) {
22109                        return null;
22110                }
22111
22112                String xpath = xquery.substring(parenStart + 1, parenEnd);
22113                // Remove any predicates like [1]
22114                int bracketIdx = xpath.indexOf("[");
22115                if (bracketIdx > 0) {
22116                        xpath = xpath.substring(0, bracketIdx);
22117                }
22118
22119                // Convert XPath to dot notation (e.g., /IndividualSurvey/TotalPurchaseYTD -> IndividualSurvey.TotalPurchaseYTD)
22120                if (xpath.startsWith("/")) {
22121                        xpath = xpath.substring(1);
22122                }
22123                xpath = xpath.replace("/", ".");
22124
22125                return xpath;
22126        }
22127
22128        private void analyzeConstantDataFlowRelation(Object modelObject, List<TParseTreeNode> constants,
22129                        EffectType effectType, List<TParseTreeNode> functions) {
22130                analyzeConstantDataFlowRelation(modelObject, constants, effectType, functions, null);
22131        }
22132
22133        private void analyzeConstantDataFlowRelation(Object modelObject, List<TParseTreeNode> constants,
22134                        EffectType effectType, List<TParseTreeNode> functions, Process process) {
22135                if (constants == null || constants.size() == 0)
22136                        return;
22137
22138                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
22139                relation.setEffectType(effectType);
22140                relation.setProcess(process);
22141
22142                if (functions != null && !functions.isEmpty()) {
22143                        relation.setFunction(getFunctionName(functions.get(0)));
22144                }
22145
22146                if (modelObject instanceof ResultColumn) {
22147                        relation.setTarget(new ResultColumnRelationshipElement((ResultColumn) modelObject));
22148
22149                } else if (modelObject instanceof TableColumn) {
22150                        relation.setTarget(new TableColumnRelationshipElement((TableColumn) modelObject));
22151
22152                } else {
22153                        throw new UnsupportedOperationException();
22154                }
22155
22156                if (option.isShowConstantTable()) {
22157                        Table constantTable = null;
22158                        if(modelObject instanceof FunctionResultColumn && ((FunctionResultColumn)modelObject).getFunction() instanceof TFunctionCall) {
22159                                TFunctionCall function = (TFunctionCall)((FunctionResultColumn)modelObject).getFunction();
22160                                if(function.getFunctionType() == EFunctionType.struct_t) {
22161                                        constantTable = modelFactory.createConstantsTable(String.valueOf(function.toString().hashCode()));
22162                                }
22163                        }
22164                        if(constantTable == null) {
22165                                constantTable = modelFactory.createConstantsTable(stmtStack.peek());
22166                        }
22167                        for (int i = 0; i < constants.size(); i++) {
22168                                TParseTreeNode constant = constants.get(i);
22169                                if (constant instanceof TConstant) {
22170                                        TableColumn constantColumn = modelFactory.createTableColumn(constantTable, (TConstant) constant);
22171                                        relation.addSource(new ConstantRelationshipElement(constantColumn));
22172                                } else if (constant instanceof TObjectName) {
22173                                        TableColumn constantColumn = modelFactory.createTableColumn(constantTable, (TObjectName) constant,
22174                                                        false);
22175                                        if(constantColumn == null) {
22176                                                continue;
22177                                        }
22178                                        relation.addSource(new ConstantRelationshipElement(constantColumn));
22179                                }
22180                        }
22181                }
22182
22183        }
22184
22185        private String getFunctionName(TParseTreeNode parseTreeNode) {
22186                if (parseTreeNode instanceof TFunctionCall) {
22187                        return ((TFunctionCall) parseTreeNode).getFunctionName().toString();
22188                }
22189                if (parseTreeNode instanceof TCaseExpression) {
22190                        return "case-when";
22191                }
22192                return null;
22193        }
22194
22195        private void analyzeCreateViewStmt(TCustomSqlStatement stmt, TSelectSqlStatement subquery,
22196                        TViewAliasClause viewAlias, TObjectName viewName) {
22197
22198                if (subquery != null) {
22199                        TTableList tables = subquery.getTables();
22200                        if (tables != null) {
22201                                for (int i = 0; i < tables.size(); i++) {
22202                                        TTable table = tables.getTable(i);
22203                                        TCustomSqlStatement createView = viewDDLMap
22204                                                        .get(DlineageUtil.getTableIdentityKey(table.getTableName().toString()));
22205                                        if (createView != null) {
22206                                                analyzeCustomSqlStmt(createView);
22207                                        }
22208                                }
22209                        }
22210                        analyzeSelectStmt(subquery);
22211                }
22212
22213        
22214                if (viewAlias != null && viewAlias.getViewAliasItemList() != null) {
22215                        TViewAliasItemList viewItems = viewAlias.getViewAliasItemList();
22216                        Table viewModel = modelFactory.createView(stmt, viewName, true);
22217                        viewModel.setFromDDL(true);
22218                        viewModel.setDetermined(true);
22219                        Process process = modelFactory.createProcess(stmt);
22220                        viewModel.addProcess(process);
22221                        ResultSet resultSetModel = (ResultSet) modelManager.getModel(subquery);
22222                        if (resultSetModel != null) {
22223                                int resultSetSize = resultSetModel.getColumns().size();
22224                                int viewItemSize = viewItems.size();
22225                                int j = 0;
22226                                int viewColumnSize = viewItemSize;
22227                                if (resultSetModel.isDetermined() && resultSetSize > viewColumnSize) {
22228                                        viewColumnSize = resultSetSize;
22229                                }
22230                                // two or more stars of unknown width leave the boundary between them unknown: each view column reads every
22231                                // select item that can stand at its position, as an INSERT column list does (Mantis 4915); a set
22232                                // operation with a star of unknown width, from each of its selects (Mantis 4914)
22233                                List<TObjectName> aliases = viewColumnAliases(viewItems);
22234                                List<SelectPositions> setSelects = subquery != null && subquery.isCombinedQuery()
22235                                                ? setOperationPositions(subquery, aliases.size(), shorterList(aliases)) : null;
22236                                boolean byCandidates = setSelects != null || starColumnCount(resultSetModel.getColumns()) > 1;
22237                                if (byCandidates) {
22238                                        mapViewColumnsByCandidates(viewModel, aliases, setSelects != null ? setSelects
22239                                                        : Collections.singletonList(
22240                                                                        selectPositions(resultSetModel.getColumns(), aliases.size(), shorterList(aliases))),
22241                                                        EffectType.create_view, process);
22242                                }
22243                                for (int i = 0; !byCandidates && i < viewColumnSize && j < resultSetSize; i++) {
22244                                        ResultColumn resultColumn = resultSetModel.getColumns().get(j);                                 
22245                                        if (i < viewItemSize) {
22246                                                TObjectName alias = viewItems.getViewAliasItem(i).getAlias();
22247                                                List<ResultColumn> structItem = null;
22248
22249                                                if (!resultSetModel.getColumns().get(j).isStarColumn()) {
22250                                                        // a select item that builds a STRUCT is one position, all of its fields (rule R8)
22251                                                        int width = selectItemWidth(resultSetModel.getColumns(), j);
22252                                                        if (width > 1 || structFieldPaths.containsKey(resultColumn)) {
22253                                                                structItem = new ArrayList<ResultColumn>(resultSetModel.getColumns().subList(j, j + width));
22254                                                        }
22255                                                        j += width;
22256                                                } else {
22257                                                        if (selectItemsFrom(resultSetModel.getColumns(), j, resultSetSize) == viewItems.size() - i) {
22258                                                                j++;
22259                                                        }
22260                                                }
22261
22262                                                if (alias != null && structItem != null) {
22263                                                        TableColumn viewColumn = modelFactory.createViewColumn(viewModel, alias, i, true);
22264                                                        appendTableColumnToSQLEnv(viewModel, viewColumn);
22265                                                        feedFromStructItem(viewColumn, structItem, alias, EffectType.create_view, process, true);
22266                                                } else if (alias != null) {
22267                                                        TableColumn viewColumn = modelFactory.createViewColumn(viewModel, alias, i, true);
22268                                                        appendTableColumnToSQLEnv(viewModel, viewColumn);
22269                                                        if (resultColumn != null) {
22270                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
22271                                                                relation.setEffectType(EffectType.create_view);
22272                                                                relation.setTarget(new ViewColumnRelationshipElement(viewColumn));
22273                                                                relation.addSource(positionSource(resultColumn));
22274                                                                relation.setProcess(process);
22275                                                        }
22276                                                } else if (resultColumn.getColumnObject() instanceof TObjectName) {
22277                                                        TableColumn viewColumn = modelFactory.createViewColumn(viewModel,
22278                                                                        (TObjectName) resultColumn.getColumnObject(), i, true);
22279                                                        appendTableColumnToSQLEnv(viewModel, viewColumn);
22280                                                        if (resultColumn != null) {
22281                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
22282                                                                relation.setEffectType(EffectType.create_view);
22283                                                                relation.setTarget(new ViewColumnRelationshipElement(viewColumn));
22284                                                                relation.addSource(new ResultColumnRelationshipElement(resultColumn));
22285                                                                relation.setProcess(process);
22286                                                        }
22287                                                } else if (resultColumn.getColumnObject() instanceof TResultColumn) {
22288                                                        TableColumn viewColumn = modelFactory.createViewColumn(viewModel,
22289                                                                        ((TResultColumn) resultColumn.getColumnObject()).getFieldAttr(), i, true);
22290                                                        appendTableColumnToSQLEnv(viewModel, viewColumn);
22291                                                        ResultColumn column = (ResultColumn) modelManager.getModel(resultColumn.getColumnObject());
22292                                                        if (column != null && column.hasStarLinkColumn()) {
22293                                                                viewColumn.shareStarLinksFrom(column);
22294                                                        }
22295                                                        if (resultColumn != null) {
22296                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
22297                                                                relation.setEffectType(EffectType.create_view);
22298                                                                relation.setTarget(new ViewColumnRelationshipElement(viewColumn));
22299                                                                relation.addSource(new ResultColumnRelationshipElement(resultColumn));
22300                                                                relation.setProcess(process);
22301                                                        }
22302                                                }
22303                                        }
22304                                        else if(resultSetModel.isDetermined()){
22305                                                TObjectName viewColumnName = new TObjectName();
22306                                                viewColumnName.setString(resultColumn.getName());
22307                                                TableColumn viewColumn = modelFactory.createViewColumn(viewModel, viewColumnName, viewModel.getColumns().size(), true);
22308                                                viewColumn.inheritIdentifier(resultColumn);
22309                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
22310                                                relation.setEffectType(EffectType.create_view);
22311                                                relation.setTarget(new ViewColumnRelationshipElement(viewColumn));
22312                                                relation.addSource(new ResultColumnRelationshipElement(resultColumn));
22313                                                relation.setProcess(process);
22314                                                j++;
22315                                        }
22316                                }
22317                                if (resultSetModel != null && !resultSetModel.getRelationRows().getHoldRelations().isEmpty()) {
22318                                        ImpactRelationship impactRelation = modelFactory.createImpactRelation();
22319                                        impactRelation.setEffectType(EffectType.create_view);
22320                                        impactRelation.addSource(new RelationRowsRelationshipElement<ResultSetRelationRows>(
22321                                                        resultSetModel.getRelationRows()));
22322                                        impactRelation.setTarget(
22323                                                        new RelationRowsRelationshipElement<TableRelationRows>(viewModel.getRelationRows()));
22324                                }
22325                        }
22326
22327                        if (subquery.getResultColumnList() == null && subquery.getValueClause() != null
22328                                        && subquery.getValueClause().getValueRows().size() == viewItems.size()) {
22329                                for (int i = 0; i < viewItems.size(); i++) {
22330                                        TObjectName alias = viewItems.getViewAliasItem(i).getAlias();
22331
22332                                        if (alias != null) {
22333                                                TableColumn viewColumn = modelFactory.createViewColumn(viewModel, alias, i, true);
22334                                                appendTableColumnToSQLEnv(viewModel, viewColumn);
22335                                                TExpression expression = subquery.getValueClause().getValueRows().getValueRowItem(i).getExpr();
22336
22337                                                columnsInExpr visitor = new columnsInExpr();
22338                                                expression.inOrderTraverse(visitor);
22339                                                List<TObjectName> objectNames = visitor.getObjectNames();
22340                                                List<TParseTreeNode> functions = visitor.getFunctions();
22341
22342                                                if (functions != null && !functions.isEmpty()) {
22343                                                        analyzeFunctionDataFlowRelation(viewColumn, functions, EffectType.select);
22344
22345                                                }
22346
22347                                                List<TSelectSqlStatement> subquerys = visitor.getSubquerys();
22348                                                if (subquerys != null && !subquerys.isEmpty()) {
22349                                                        analyzeSubqueryDataFlowRelation(viewColumn, subquerys, EffectType.select);
22350                                                }
22351
22352                                                analyzeDataFlowRelation(viewColumn, objectNames, EffectType.select, functions);
22353                                                List<TParseTreeNode> constants = visitor.getConstants();
22354                                                analyzeConstantDataFlowRelation(viewColumn, constants, EffectType.select, functions);
22355                                        }
22356                                }
22357                        }
22358
22359                } else {
22360                        if (viewName == null) {
22361                                ErrorInfo errorInfo = new ErrorInfo();
22362                                errorInfo.setErrorType(ErrorInfo.ANALYZE_ERROR);
22363                                errorInfo.setErrorMessage("Can't get view name. CreateView is " + stmt.toString());
22364                                errorInfo.setStartPosition(new Pair3<Long, Long, String>(stmt.getStartToken().lineNo,
22365                                                stmt.getStartToken().columnNo, ModelBindingManager.getGlobalHash()));
22366                                errorInfo.setEndPosition(new Pair3<Long, Long, String>(stmt.getEndToken().lineNo,
22367                                                stmt.getEndToken().columnNo + stmt.getEndToken().getAstext().length(),
22368                                                ModelBindingManager.getGlobalHash()));
22369                                errorInfo.fillInfo(this);
22370                                errorInfos.add(errorInfo);
22371                                return;
22372                        }
22373                        Table viewModel = modelFactory.createView(stmt, viewName);
22374                        Process process = modelFactory.createProcess(stmt);
22375                        viewModel.addProcess(process);
22376                        if (subquery != null && !subquery.isCombinedQuery()) {
22377                                SelectResultSet resultSetModel = (SelectResultSet) modelManager
22378                                                .getModel(subquery.getResultColumnList());
22379
22380                                boolean determined = false;
22381                                if (!containStarColumn(resultSetModel)) {
22382                                        viewModel.setCreateTable(true);
22383                                        determined = true;
22384                                        viewModel.setFromDDL(true);
22385                                }
22386                                for (int i = 0; i < resultSetModel.getColumns().size(); i++) {
22387                                        ResultColumn resultColumn = resultSetModel.getColumns().get(i);
22388                                        if (resultColumn.getColumnObject() instanceof TResultColumn) {
22389                                                TResultColumn columnObject = ((TResultColumn) resultColumn.getColumnObject());
22390
22391                                                TAliasClause alias = ((TResultColumn) resultColumn.getColumnObject()).getAliasClause();
22392                                                if (alias != null && alias.getAliasName() != null) {
22393                                                        TableColumn viewColumn = modelFactory.createViewColumn(viewModel, alias.getAliasName(), i,
22394                                                                        determined);
22395                                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
22396                                                        relation.setEffectType(EffectType.create_view);
22397                                                        relation.setTarget(new ViewColumnRelationshipElement(viewColumn));
22398                                                        relation.addSource(new ResultColumnRelationshipElement(resultColumn));
22399                                                        relation.setProcess(process);
22400                                                        if (determined) {
22401                                                                appendTableColumnToSQLEnv(viewModel, viewColumn);
22402                                                        }
22403                                                } else if (columnObject.getFieldAttr() != null) {
22404                                                        TObjectName viewColumnObject = columnObject.getFieldAttr();
22405                                                        TableColumn viewColumn;
22406                                                        Object model = modelManager.getModel(resultColumn.getColumnObject());
22407                                                        // customer.* expanded to the fields of customer (STRUCT tree) is a wildcard item too
22408                                                        if (DlineageUtil.isWildcard(viewColumnObject) || (nestedProfile().buildsDeclaredTree()
22409                                                                        && viewColumnObject.getStructStarPath() != null)) {
22410                                                                if (model instanceof LinkedHashMap) {
22411                                                                        String columnName = getColumnNameOnly(resultColumn.getName());
22412                                                                        LinkedHashMap<String, ResultColumn> resultColumns = (LinkedHashMap<String, ResultColumn>) model;
22413                                                                        if (resultColumn.isStarColumn()) {
22414                                                                                for (String key : resultColumns.keySet()) {
22415                                                                                        ResultColumn column = resultColumns.get(key);
22416                                                                                        viewColumn = itemColumnTarget(viewModel, column);
22417                                                                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
22418                                                                                        relation.setEffectType(EffectType.create_view);
22419                                                                                        relation.setTarget(new ViewColumnRelationshipElement(viewColumn));
22420                                                                                        relation.addSource(new ResultColumnRelationshipElement(column));
22421                                                                                        relation.setProcess(process);
22422                                                                                }
22423                                                                                continue;
22424                                                                        } else if (resultColumns.containsKey(columnName)) {
22425                                                                                ResultColumn column = resultColumns.get(columnName);
22426                                                                                viewColumn = modelFactory.createInsertTableColumn(viewModel, column);
22427                                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
22428                                                                                relation.setEffectType(EffectType.create_view);
22429                                                                                relation.setTarget(new ViewColumnRelationshipElement(viewColumn));
22430                                                                                relation.addSource(new ResultColumnRelationshipElement(column));
22431                                                                                relation.setProcess(process);
22432                                                                                continue;
22433                                                                        }
22434                                                                }
22435                                                        }
22436                                                        
22437                                                        if (!SQLUtil.isEmpty(viewColumnObject.getColumnNameOnly())
22438                                                                        && viewColumnObject.getPropertyToken() != null) {
22439                                                                TObjectName object = new TObjectName();
22440                                                                // object.setString(viewColumnObject.getPropertyToken().astext);
22441                                                                // the view column is named after the last field: payload.address.city gives city
22442                                                                TSourceToken leaf = DlineageUtil.propertyLeafToken(viewColumnObject);
22443                                                                object.setPartToken(leaf);
22444                                                                object.setStartToken(leaf);
22445                                                                object.setEndToken(leaf);
22446                                                                if (!structStarOfUnknownFields(viewColumnObject, resultColumn)) {
22447                                                                        viewColumn = modelFactory.createViewColumn(viewModel, object, i, determined);
22448                                                                } else {
22449                                                                        // STRUCT tree: customer.* without declared fields is one column standing for every
22450                                                                        // field of customer, as its result column is; not a wildcard over a relation, whose
22451                                                                        // expansion published the struct itself as a view column (Mantis 4827)
22452                                                                        viewColumn = modelFactory.createStructStarViewColumn(viewModel, object, i, determined);
22453                                                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
22454                                                                        relation.setEffectType(EffectType.create_view);
22455                                                                        relation.setTarget(new ViewColumnRelationshipElement(viewColumn));
22456                                                                        relation.addSource(new ResultColumnRelationshipElement(resultColumn));
22457                                                                        relation.setProcess(process);
22458                                                                        continue;
22459                                                                }
22460                                                        } else {
22461                                                                viewColumn = modelFactory.createViewColumn(viewModel, columnObject.getFieldAttr(),
22462                                                                                i, determined);
22463                                                        }
22464                                                        
22465                                                        if(determined) {
22466                                                                appendTableColumnToSQLEnv(viewModel, viewColumn);
22467                                                        }
22468                                                        
22469                                                        if (model instanceof ResultColumn) {
22470                                                                ResultColumn column = (ResultColumn) modelManager
22471                                                                                .getModel(resultColumn.getColumnObject());
22472                                                                if (column != null && column.hasStarLinkColumn()) {
22473                                                                        viewColumn.shareStarLinksFrom(column);
22474                                                                }
22475                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
22476                                                                relation.setEffectType(EffectType.create_view);
22477                                                                relation.setTarget(new ViewColumnRelationshipElement(viewColumn));
22478                                                                relation.addSource(new ResultColumnRelationshipElement(resultColumn));
22479                                                                if (sqlenv == null) {
22480                                                                        if (resultColumn.isShowStar()) {
22481                                                                                relation.setShowStarRelation(true);
22482                                                                                viewColumn.setShowStar(true);
22483                                                                                resultColumn.setShowStar(true);
22484                                                                                setSourceShowStar(resultColumn);
22485                                                                        }
22486                                                                }
22487                                                                if (viewColumn.isStarColumn() && resultColumn.isStarColumn()) {
22488                                                                        viewModel.setStarStmt("create_view");
22489                                                                }
22490                                                                relation.setProcess(process);
22491                                                        }
22492                                                } else if (resultColumn.getAlias() != null && columnObject.getExpr()
22493                                                                .getExpressionType() == EExpressionType.sqlserver_proprietary_column_alias_t) {
22494                                                        TableColumn viewColumn = modelFactory.createViewColumn(viewModel,
22495                                                                        columnObject.getExpr().getLeftOperand().getObjectOperand(), i, determined);
22496                                                        if(determined) {
22497                                                                appendTableColumnToSQLEnv(viewModel, viewColumn);
22498                                                        }
22499                                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
22500                                                        relation.setEffectType(EffectType.create_view);
22501                                                        relation.setTarget(new ViewColumnRelationshipElement(viewColumn));
22502                                                        relation.addSource(new ResultColumnRelationshipElement(resultColumn));
22503                                                        relation.setProcess(process);
22504                                                } else {
22505                                                        TGSqlParser parser = columnObject.getGsqlparser();
22506                                                        TObjectName viewColumnName = parser
22507                                                                        .parseObjectName(generateQuotedName(parser, columnObject.toString()));
22508                                                        if (viewColumnName == null) {
22509                                                                ErrorInfo errorInfo = new ErrorInfo();
22510                                                                errorInfo.setErrorType(ErrorInfo.ANALYZE_ERROR);
22511                                                                errorInfo.setErrorMessage(
22512                                                                                "Can't parse view column. Column is " + columnObject.toString());
22513                                                                errorInfo.setStartPosition(new Pair3<Long, Long, String>(
22514                                                                                columnObject.getStartToken().lineNo, columnObject.getStartToken().columnNo,
22515                                                                                ModelBindingManager.getGlobalHash()));
22516                                                                errorInfo
22517                                                                                .setEndPosition(new Pair3<Long, Long, String>(columnObject.getEndToken().lineNo,
22518                                                                                                columnObject.getEndToken().columnNo
22519                                                                                                                + columnObject.getEndToken().getAstext().length(),
22520                                                                                                ModelBindingManager.getGlobalHash()));
22521                                                                errorInfo.fillInfo(this);
22522                                                                errorInfos.add(errorInfo);
22523                                                        } else {
22524                                                                TableColumn viewColumn = modelFactory.createViewColumn(viewModel, viewColumnName, i,
22525                                                                                determined);
22526                                                                if(determined) {
22527                                                                        appendTableColumnToSQLEnv(viewModel, viewColumn);
22528                                                                }
22529                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
22530                                                                relation.setEffectType(EffectType.create_view);
22531                                                                relation.setTarget(new ViewColumnRelationshipElement(viewColumn));
22532                                                                relation.addSource(new ResultColumnRelationshipElement(resultColumn));
22533                                                                relation.setProcess(process);
22534                                                        }
22535                                                }
22536                                        } else if (resultColumn.getColumnObject() instanceof TObjectName) {
22537                                                TObjectName viewColumnName = (TObjectName) resultColumn.getColumnObject();
22538                                                StructFieldPath rowField = structFieldPaths.get(resultColumn);
22539                                                if (rowField != null && rowField.alias != null) {
22540                                                        // STRUCT tree: a field of a ROW the query builds, contact.city of CAST(ROW(..) AS ROW(city ..))
22541                                                        // AS contact, is the field node contact.city below the view's declared column contact, so a
22542                                                        // later read of v.contact.city binds to it (Mantis 4834)
22543                                                        TObjectName alias = rowField.alias;
22544                                                        boolean first = modelManager.getViewModel(new Pair<Table, TObjectName>(viewModel, alias)) == null;
22545                                                        TableColumn declared = modelFactory.createViewColumn(viewModel, alias, i, determined);
22546                                                        TableColumn node = declared == null ? null
22547                                                                        : modelFactory.createFieldPath(declared, rowField.segments(), alias);
22548                                                        if (node != null) {
22549                                                                if (determined && first) {
22550                                                                        appendTableColumnToSQLEnv(viewModel, declared);
22551                                                                }
22552                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
22553                                                                relation.setEffectType(EffectType.create_view);
22554                                                                relation.setTarget(new ViewColumnRelationshipElement(node));
22555                                                                relation.addSource(new ResultColumnRelationshipElement(resultColumn));
22556                                                                relation.setProcess(process);
22557                                                                continue;
22558                                                        }
22559                                                }
22560                                                TableColumn viewColumn = modelFactory.createViewColumn(viewModel, viewColumnName, i, determined);
22561                                                if(determined) {
22562                                                        appendTableColumnToSQLEnv(viewModel, viewColumn);
22563                                                }
22564                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
22565                                                relation.setEffectType(EffectType.create_view);
22566                                                relation.setTarget(new ViewColumnRelationshipElement(viewColumn));
22567                                                relation.addSource(new ResultColumnRelationshipElement(resultColumn));
22568                                                relation.setProcess(process);
22569                                        }
22570                                }
22571                                if (!resultSetModel.getRelationRows().getHoldRelations().isEmpty()) {
22572                                        ImpactRelationship impactRelation = modelFactory.createImpactRelation();
22573                                        impactRelation.setEffectType(EffectType.create_view);
22574                                        impactRelation.addSource(new RelationRowsRelationshipElement<ResultSetRelationRows>(
22575                                                        resultSetModel.getRelationRows()));
22576                                        impactRelation.setTarget(
22577                                                        new RelationRowsRelationshipElement<TableRelationRows>(viewModel.getRelationRows()));
22578                                }
22579                        } else if (subquery != null && subquery.isCombinedQuery()) {
22580                                SelectSetResultSet resultSetModel = (SelectSetResultSet) modelManager.getModel(subquery);
22581
22582                                boolean determined = false;
22583                                if (!containStarColumn(resultSetModel)) {
22584                                        viewModel.setCreateTable(true);
22585                                        viewModel.setFromDDL(true);
22586                                        determined = true;
22587                                }
22588
22589                                for (int i = 0; i < resultSetModel.getColumns().size(); i++) {
22590                                        ResultColumn resultColumn = resultSetModel.getColumns().get(i);
22591
22592                                        if (resultColumn.getColumnObject() instanceof TResultColumn) {
22593                                                TResultColumn columnObject = ((TResultColumn) resultColumn.getColumnObject());
22594
22595                                                TAliasClause alias = columnObject.getAliasClause();
22596                                                if (alias != null && alias.getAliasName() != null) {
22597                                                        TableColumn viewColumn = modelFactory.createViewColumn(viewModel, alias.getAliasName(), i,
22598                                                                        determined);
22599                                                        if(determined) {
22600                                                                appendTableColumnToSQLEnv(viewModel, viewColumn);
22601                                                        }
22602                                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
22603                                                        relation.setEffectType(EffectType.create_view);
22604                                                        relation.setTarget(new ViewColumnRelationshipElement(viewColumn));
22605                                                        relation.addSource(new ResultColumnRelationshipElement(resultColumn));
22606                                                        relation.setProcess(process);
22607                                                } else if (columnObject.getFieldAttr() != null) {
22608                                                        TableColumn viewColumn = modelFactory.createViewColumn(viewModel,
22609                                                                        columnObject.getFieldAttr(), i, determined);
22610                                                        if(determined) {
22611                                                                appendTableColumnToSQLEnv(viewModel, viewColumn);
22612                                                        }
22613                                                        ResultColumn column = (ResultColumn) modelManager.getModel(resultColumn.getColumnObject());
22614                                                        if (column != null && column.hasStarLinkColumn()) {
22615                                                                viewColumn.shareStarLinksFrom(column);
22616                                                        }
22617                                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
22618                                                        relation.setEffectType(EffectType.create_view);
22619                                                        relation.setTarget(new ViewColumnRelationshipElement(viewColumn));
22620                                                        relation.addSource(new ResultColumnRelationshipElement(resultColumn));
22621                                                        relation.setProcess(process);
22622                                                } else if (resultColumn.getAlias() != null && columnObject.getExpr()
22623                                                                .getExpressionType() == EExpressionType.sqlserver_proprietary_column_alias_t) {
22624                                                        TableColumn viewColumn = modelFactory.createViewColumn(viewModel,
22625                                                                        columnObject.getExpr().getLeftOperand().getObjectOperand(), i, determined);
22626                                                        if(determined) {
22627                                                                appendTableColumnToSQLEnv(viewModel, viewColumn);
22628                                                        }
22629                                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
22630                                                        relation.setEffectType(EffectType.create_view);
22631                                                        relation.setTarget(new ViewColumnRelationshipElement(viewColumn));
22632                                                        relation.addSource(new ResultColumnRelationshipElement(resultColumn));
22633                                                        relation.setProcess(process);
22634                                                } else {
22635                                                        TGSqlParser parser = columnObject.getGsqlparser();
22636                                                        TObjectName viewColumnName = parser
22637                                                                        .parseObjectName(generateQuotedName(parser, columnObject.toString()));
22638                                                        TableColumn viewColumn = modelFactory.createViewColumn(viewModel, viewColumnName, i,
22639                                                                        determined);
22640                                                        if(determined) {
22641                                                                appendTableColumnToSQLEnv(viewModel, viewColumn);
22642                                                        }
22643                                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
22644                                                        relation.setEffectType(EffectType.create_view);
22645                                                        relation.setTarget(new ViewColumnRelationshipElement(viewColumn));
22646                                                        relation.addSource(new ResultColumnRelationshipElement(resultColumn));
22647                                                        relation.setProcess(process);
22648                                                }
22649                                        } else if (resultColumn.getColumnObject() instanceof TObjectName) {
22650                                                TableColumn viewColumn = modelFactory.createViewColumn(viewModel,
22651                                                                (TObjectName) resultColumn.getColumnObject(), i, determined);
22652                                                if(viewColumn!=null) {
22653                                                        if(determined) {
22654                                                                appendTableColumnToSQLEnv(viewModel, viewColumn);
22655                                                        }
22656                                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
22657                                                        relation.setEffectType(EffectType.create_view);
22658                                                        relation.setTarget(new ViewColumnRelationshipElement(viewColumn));
22659                                                        relation.addSource(new ResultColumnRelationshipElement(resultColumn));
22660                                                        relation.setProcess(process);
22661                                                }
22662                                        }
22663                                }
22664                                if (!resultSetModel.getRelationRows().getHoldRelations().isEmpty()) {
22665                                        ImpactRelationship impactRelation = modelFactory.createImpactRelation();
22666                                        impactRelation.setEffectType(EffectType.create_view);
22667                                        impactRelation.addSource(new RelationRowsRelationshipElement<ResultSetRelationRows>(
22668                                                        resultSetModel.getRelationRows()));
22669                                        impactRelation.setTarget(
22670                                                        new RelationRowsRelationshipElement<TableRelationRows>(viewModel.getRelationRows()));
22671                                }
22672                        }
22673                }
22674        }
22675
22676        private void setSourceShowStar(Object resultColumn) {
22677                for (Relationship relation : modelManager.getRelations()) {
22678                        Collection<RelationshipElement<?>> sources = (Collection<RelationshipElement<?>>)relation.getSources();
22679                        if (relation.getTarget().getElement() == resultColumn && sources != null) {
22680
22681                                ((AbstractRelationship) relation).setShowStarRelation(true);
22682                                for (RelationshipElement<?> source : sources) {
22683                                        Object column = source.getElement();
22684                                        if (column instanceof TableColumn) {
22685                                                if (((TableColumn) column).isShowStar())
22686                                                        continue;
22687                                                ((TableColumn) column).setShowStar(true);
22688                                        }
22689                                        if (column instanceof ResultColumn) {
22690                                                if (((ResultColumn) column).isShowStar())
22691                                                        continue;
22692                                                ((ResultColumn) column).setShowStar(true);
22693                                        }
22694                                        setSourceShowStar(column);
22695                                }
22696                        }
22697                }
22698        }
22699
22700        private String generateQuotedName(TGSqlParser parser, String name) {
22701                return "\"" + name + "\"";
22702        }
22703
22704        private void appendRelations(dataflow dataflow) {
22705                Relationship[] relations = modelManager.getRelations();
22706
22707                appendRelation(dataflow, relations, DataFlowRelationship.class);
22708                appendRelation(dataflow, relations, IndirectImpactRelationship.class);
22709                appendRecordSetRelation(dataflow, relations);
22710                appendCallRelation(dataflow, relations);
22711                appendRelation(dataflow, relations, ImpactRelationship.class);
22712                appendRelation(dataflow, relations, JoinRelationship.class);
22713                appendRelation(dataflow, relations, ERRelationship.class);
22714                appendRelation(dataflow, relations, CrudRelationship.class);
22715                reconcileCitedColumns(dataflow);
22716        }
22717
22718        /**
22719         * Invariant enforcement (codex-696 F5): a relationship must never cite a
22720         * column its parent element does not declare. Star/variant columns of
22721         * record variables can be cited raw (a whole-record source in a filter,
22722         * an unexpanded fill) while the model writer only emitted their
22723         * star-linked expansions — declare exactly the cited leftovers, nothing
22724         * more.
22725         */
22726        private void reconcileCitedColumns(dataflow dataflow) {
22727                // VARIABLES only: the undeclared-endpoint family is a record variable's
22728                // star/variant column cited raw. Declaring cited leftovers on tables or
22729                // resultsets changes what downstream consumers (JSON conversion,
22730                // normalized comparison) derive from the model — churn far beyond the
22731                // invariant this pass exists to keep.
22732                Map<String, table> parents = new HashMap<String, table>();
22733                if (dataflow.getVariables() != null) {
22734                        for (table t : dataflow.getVariables()) {
22735                                if (t.getId() != null) {
22736                                        parents.put(t.getId(), t);
22737                                }
22738                        }
22739                }
22740                if (dataflow.getRelationships() == null) {
22741                        return;
22742                }
22743                Set<String> declared = new HashSet<String>();
22744                for (table t : parents.values()) {
22745                        if (t.getColumns() == null) {
22746                                continue;
22747                        }
22748                        for (column c : t.getColumns()) {
22749                                declared.add(t.getId() + ":" + c.getId());
22750                        }
22751                }
22752                for (relationship relation : dataflow.getRelationships()) {
22753                        if (relation.getSources() != null) {
22754                                for (sourceColumn source : relation.getSources()) {
22755                                        declareCitedColumn(parents, declared, source.getParent_id(), source.getId(),
22756                                                        source.getColumn(), source.getCoordinate());
22757                                }
22758                        }
22759                        targetColumn target = relation.getTarget();
22760                        if (target != null) {
22761                                declareCitedColumn(parents, declared, target.getParent_id(), target.getId(),
22762                                                target.getColumn(), target.getCoordinate());
22763                        }
22764                }
22765        }
22766
22767        private void declareCitedColumn(Map<String, table> parents, Set<String> declared,
22768                        String parentId, String columnId, String columnName, String coordinate) {
22769                if (parentId == null || columnId == null || columnName == null) {
22770                        return;
22771                }
22772                String key = parentId + ":" + columnId;
22773                if (declared.contains(key)) {
22774                        return;
22775                }
22776                table parent = parents.get(parentId);
22777                if (parent == null) {
22778                        return;
22779                }
22780                column columnElement = new column();
22781                columnElement.setId(columnId);
22782                columnElement.setName(columnName);
22783                if (coordinate != null) {
22784                        columnElement.setCoordinate(coordinate);
22785                }
22786                parent.getColumns().add(columnElement);
22787                declared.add(key);
22788        }
22789        
22790        
22791        private relationship cloneRelationshipWithSingleSource(relationship orig, sourceColumn src) {
22792                relationship rel = new relationship();
22793                rel.setType(orig.getType());
22794                rel.setEffectType(orig.getEffectType());
22795                rel.setId(orig.getId() + "_" + src.getColumn());
22796                rel.setSqlHash(orig.getSqlHash());
22797                rel.setSqlComment(orig.getSqlComment());
22798                rel.setProcessId(orig.getProcessId());
22799                rel.setProcessType(orig.getProcessType());
22800                rel.setFunction(orig.getFunction());
22801                rel.setProcedureId(orig.getProcedureId());
22802
22803                targetColumn t = new targetColumn();
22804                t.setId(orig.getTarget().getId());
22805                t.setColumn(orig.getTarget().getColumn());
22806                t.setParent_id(orig.getTarget().getParent_id());
22807                t.setParent_name(orig.getTarget().getParent_name());
22808                t.setParent_alias(orig.getTarget().getParent_alias());
22809                t.setCoordinate(orig.getTarget().getCoordinate());
22810                rel.setTarget(t);
22811
22812                sourceColumn s = new sourceColumn();
22813                s.setId(src.getId());
22814                s.setColumn(src.getColumn());
22815                s.setParent_id(src.getParent_id());
22816                s.setParent_name(src.getParent_name());
22817                s.setCoordinate(src.getCoordinate());
22818                rel.addSource(s);
22819                return rel;
22820        }
22821
22822        private Set<Object> appendStarColumns = new HashSet<Object>();
22823        private void appendRelation(dataflow dataflow, Relationship[] relations, Class<? extends Relationship> clazz) {
22824                for (int i = 0; i < relations.length; i++) {
22825                        AbstractRelationship relation = (AbstractRelationship) relations[i];
22826                        if (relation.getClass() == clazz) {
22827                                if (relation.getSources() == null || relation.getTarget() == null) {
22828                                        continue;
22829                                }
22830                                Object targetElement = relation.getTarget().getElement();
22831                                boolean targetIsStar = targetElement instanceof Column && ((Column) targetElement).isStarColumn();
22832                                TObjectName targetColumnName = null;
22833                                if(relation.getTarget() instanceof ResultColumnRelationshipElement) {
22834                                        targetColumnName = ((ResultColumnRelationshipElement) relation.getTarget()).getColumnName();
22835                                }
22836                                if (targetElement instanceof ResultColumn) {
22837                                        ResultColumn targetColumn = (ResultColumn) targetElement;
22838                                        if (!targetColumn.isPseduo()) 
22839                                        {
22840                                                if (targetColumnName == null) 
22841                                                {
22842                                                        if (targetColumn.isStarColumn()) {
22843                                                                updateResultColumnStarLinks(dataflow, relation, -1);
22844                                                        }
22845
22846                                                        if (targetColumn.hasStarLinkColumn()) {
22847                                                                for (int j = 0; j < targetColumn.starLinkCount(); j++) {
22848                                                                        appendStarRelation(dataflow, relation, j);
22849                                                                }
22850
22851                                                                if (!containsStar(relation.getSources())) {
22852                                                                        continue;
22853                                                                }
22854                                                        }
22855                                                }
22856                                                else {
22857                                                        int index = targetColumn.starLinkIndexOf(targetColumnName);
22858                                                        if (index != -1) {
22859                                                                int size = targetColumn.starLinkCount();
22860                                                                if (targetColumn.isStarColumn()) {
22861                                                                        Object linkKey = targetColumn.starLinkKey(index);
22862                                                                        if (appendStarColumns.contains(linkKey)) {
22863                                                                                updateResultColumnStarLinks(dataflow, relation, index);
22864                                                                        }
22865                                                                        else {
22866                                                                                updateResultColumnStarLinks(dataflow, relation, -1);
22867                                                                                appendStarColumns.add(linkKey);
22868                                                                        }
22869                                                                }
22870                                                                appendStarRelation(dataflow, relation, index);
22871                                                                for(int j= size; j < targetColumn.starLinkCount(); j++) {
22872                                                                        appendStarRelation(dataflow, relation, j);
22873                                                                }
22874                                                                if (!containsStar(relation.getSources())) {
22875                                                                        continue;
22876                                                                }
22877                                                        }
22878                                                }
22879                                        }
22880                                } else if (targetElement instanceof TableColumn) {
22881                                        TableColumn targetColumn = (TableColumn) targetElement;
22882                                        if (!targetColumn.isPseduo()) {
22883                                                if (targetColumn.isStarColumn()) {
22884                                                        updateTableColumnStarLinks(dataflow, relation);
22885                                                }
22886
22887                                                // Star expansion selected by HOLDER type (#695): a
22888                                                // VARIABLE's variant column is serialized as its
22889                                                // star-linked fields (appendVariableModel), so a
22890                                                // relation targeting it must expand onto those fields
22891                                                // or it cites a column the model never declares. A
22892                                                // physical table's variant column keeps the historical
22893                                                // refusal (appendTableModel refuses variant).
22894                                                boolean variantOfVariable = targetColumn.isVariant()
22895                                                                && targetColumn.getTable() instanceof Variable;
22896                                                if (targetColumn.hasStarLinkColumn()
22897                                                                && ((!targetColumn.isVariant() && targetColumn.isExpandStar())
22898                                                                                || variantOfVariable)) {
22899                                                        for (int j = 0; j < targetColumn.starLinkCount(); j++) {
22900                                                                appendStarRelation(dataflow, relation, j);
22901                                                        }
22902                                                        if (!containsStar(relation.getSources())) {
22903                                                                continue;
22904                                                        }
22905                                                }
22906                                        }
22907                                }
22908
22909                                relationship relationElement = new relationship();
22910                                relationElement.setType(relation.getRelationshipType().name());
22911                                if (relation.getEffectType() != null) {
22912                                        relationElement.setEffectType(relation.getEffectType().name());
22913                                }
22914                                if (relation.getFunction() != null) {
22915                                        relationElement.setFunction(relation.getFunction());
22916                                }
22917                                relationElement.setSqlHash(relation.getSqlHash());
22918                                relationElement.setSqlComment(relation.getSqlComment());
22919
22920                                if (relation.getProcedureId() != null) {
22921                                        relationElement.setProcedureId(String.valueOf(relation.getProcedureId()));
22922                                }
22923                                relationElement.setId(String.valueOf(relation.getId()));
22924                                if (relation.getProcess() != null) {
22925                                        relationElement.setProcessId(String.valueOf(relation.getProcess().getId()));
22926                                        if (relation.getProcess().getGspObject() != null) {
22927                                                relationElement.setProcessType(relation.getProcess().getGspObject().sqlstatementtype.name());
22928                                        }
22929                                }
22930
22931                                if (relation.getPartition() != null) {
22932                                        relationElement.setPartition(relation.getPartition());
22933                                }
22934                                relationElement.setSqlHash(relation.getSqlHash());
22935                                relationElement.setSqlComment(relation.getSqlComment());
22936                                
22937                                if (relation.getProcedureId() != null) {
22938                                        relationElement.setProcedureId(String.valueOf(relation.getProcedureId()));
22939                                }
22940                                
22941                                if (relation instanceof JoinRelationship) {
22942                                        relationElement.setCondition(((JoinRelationship) relation).getJoinCondition());
22943                                        relationElement.setJoinType(((JoinRelationship) relation).getJoinType().name());
22944                                        relationElement.setClause(((JoinRelationship) relation).getJoinClauseType().name());
22945                                }
22946
22947                                if(relation instanceof ImpactRelationship){
22948                                        ImpactRelationship impactRelationship = (ImpactRelationship)relation;
22949                                        if(impactRelationship.getJoinClauseType()!=null){
22950                                                relationElement.setClause(impactRelationship.getJoinClauseType().name());
22951                                        }
22952                                }
22953
22954                                String targetName = null;
22955                                Object columnObject = null;
22956                                List<TObjectName> targetObjectNames = null;
22957
22958                                if (targetElement instanceof ResultSetRelationRows) {
22959                                        ResultSetRelationRows targetColumn = (ResultSetRelationRows) targetElement;
22960                                        targetColumn target = new targetColumn();
22961                                        target.setId(String.valueOf(targetColumn.getId()));
22962                                        target.setColumn(targetColumn.getName());
22963                                        target.setParent_id(String.valueOf(targetColumn.getHolder().getId()));
22964                                        target.setParent_name(getResultSetName(targetColumn.getHolder()));
22965                                        if (targetColumn.getStartPosition() != null && targetColumn.getEndPosition() != null) {
22966                                                target.setCoordinate(convertCoordinate(targetColumn.getStartPosition()) + ","
22967                                                                + convertCoordinate(targetColumn.getEndPosition()));
22968                                        }
22969                                        if (relation instanceof RecordSetRelationship) {
22970                                                target.setFunction(((RecordSetRelationship) relation).getAggregateFunction());
22971                                        }
22972                                        target.setSource("system");
22973                                        targetName = targetColumn.getName();
22974                                        relationElement.setTarget(target);
22975                                } else if (targetElement instanceof TableRelationRows) {
22976                                        TableRelationRows targetColumn = (TableRelationRows) targetElement;
22977                                        targetColumn target = new targetColumn();
22978                                        target.setId(String.valueOf(targetColumn.getId()));
22979                                        target.setColumn(targetColumn.getName());
22980                                        target.setParent_id(String.valueOf(targetColumn.getHolder().getId()));
22981                                        target.setParent_name(getTableName(targetColumn.getHolder()));
22982                                        if (targetColumn.getStartPosition() != null && targetColumn.getEndPosition() != null) {
22983                                                target.setCoordinate(convertCoordinate(targetColumn.getStartPosition()) + ","
22984                                                                + convertCoordinate(targetColumn.getEndPosition()));
22985                                        }
22986                                        if (relation instanceof RecordSetRelationship) {
22987                                                target.setFunction(((RecordSetRelationship) relation).getAggregateFunction());
22988                                        }
22989                                        target.setSource("system");
22990                                        targetName = targetColumn.getName();
22991                                        relationElement.setTarget(target);
22992                                } else if (targetElement instanceof ResultColumn) {
22993                                        ResultColumn targetColumn = (ResultColumn) targetElement;
22994                                        targetColumn target = new targetColumn();
22995                                        target.setId(String.valueOf(targetColumn.getId()));
22996                                        target.setColumn(targetColumn.getName());
22997                                        target.setStruct(targetColumn.isStruct());
22998                                        target.setParent_id(String.valueOf(targetColumn.getResultSet().getId()));
22999                                        target.setParent_name(getResultSetName(targetColumn.getResultSet()));
23000                                        if (targetColumn.getStartPosition() != null && targetColumn.getEndPosition() != null) {
23001                                                target.setCoordinate(convertCoordinate(targetColumn.getStartPosition()) + ","
23002                                                                + convertCoordinate(targetColumn.getEndPosition()));
23003                                        }
23004                                        if (relation instanceof RecordSetRelationship) {
23005                                                target.setFunction(((RecordSetRelationship) relation).getAggregateFunction());
23006                                        }
23007                                        targetName = targetColumn.getName();
23008                                        if (((ResultColumn) targetColumn).getColumnObject() instanceof TResultColumn) {
23009                                                columnsInExpr visitor = new columnsInExpr();
23010                                                ((TResultColumn) ((ResultColumn) targetColumn).getColumnObject()).getExpr()
23011                                                                .inOrderTraverse(visitor);
23012                                                targetObjectNames = visitor.getObjectNames();
23013                                        }
23014
23015                                        if (targetElement instanceof FunctionResultColumn) {
23016                                                columnObject = ((FunctionResultColumn) targetElement).getColumnObject();
23017                                        }
23018                                        if(targetColumn.isPseduo()) {
23019                                                target.setSource("system");
23020                                        }
23021                                        relationElement.setTarget(target);
23022                                } else if (targetElement instanceof TableColumn) {
23023                                        TableColumn targetColumn = (TableColumn) targetElement;
23024                                        targetColumn target = new targetColumn();
23025                                        target.setId(String.valueOf(targetColumn.getId()));
23026                                        target.setColumn(targetColumn.getName());
23027                                        target.setStruct(targetColumn.isStruct());
23028                                        target.setParent_id(String.valueOf(targetColumn.getTable().getId()));
23029                                        target.setParent_name(getTableName(targetColumn.getTable()));
23030                                        if (relation.getTarget() instanceof TableColumnRelationshipElement) {
23031                                                target.setParent_alias(((TableColumnRelationshipElement) relation.getTarget()).getTableAlias());
23032                                        }
23033                                        if (targetColumn.getStartPosition() != null && targetColumn.getEndPosition() != null) {
23034                                                target.setCoordinate(convertCoordinate(targetColumn.getStartPosition()) + ","
23035                                                                + convertCoordinate(targetColumn.getEndPosition()));
23036                                        }
23037                                        if (relation instanceof RecordSetRelationship) {
23038                                                target.setFunction(((RecordSetRelationship) relation).getAggregateFunction());
23039                                        }
23040                                        if(targetColumn.isPseduo()) {
23041                                                target.setSource("system");
23042                                        }
23043                                        targetName = targetColumn.getName();
23044                                        relationElement.setTarget(target);
23045                                } else if (targetElement instanceof Argument) {
23046                                        Argument targetColumn = (Argument) targetElement;
23047                                        targetColumn target = new targetColumn();
23048                                        target.setId(String.valueOf(targetColumn.getId()));
23049                                        target.setColumn(targetColumn.getName());
23050                                        target.setParent_id(String.valueOf(targetColumn.getProcedure().getId()));
23051                                        target.setParent_name(targetColumn.getProcedure().getName());
23052                                        if (targetColumn.getStartPosition() != null && targetColumn.getEndPosition() != null) {
23053                                                target.setCoordinate(convertCoordinate(targetColumn.getStartPosition()) + ","
23054                                                                + convertCoordinate(targetColumn.getEndPosition()));
23055                                        }
23056                                        if (relation instanceof RecordSetRelationship) {
23057                                                target.setFunction(((RecordSetRelationship) relation).getAggregateFunction());
23058                                        }
23059                                        targetName = targetColumn.getName();
23060                                        relationElement.setTarget(target);
23061                                } else if (targetElement instanceof Table) {
23062                                        Table table = (Table) targetElement;
23063                                        targetColumn target = new targetColumn();
23064                                        target.setTarget_id(String.valueOf(table.getId()));
23065                                        target.setTarget_name(getTableName(table));
23066                                        if (table.getStartPosition() != null && table.getEndPosition() != null) {
23067                                                target.setCoordinate(convertCoordinate(table.getStartPosition()) + ","
23068                                                                + convertCoordinate(table.getEndPosition()));
23069                                        }
23070                                        relationElement.setTarget(target);
23071                                } else {
23072                                        continue;
23073                                }
23074
23075                                Collection<RelationshipElement<?>> sourceElements = relation.getSources();
23076                                if (sourceElements.size() == 0) {
23077                                        if(clazz == CrudRelationship.class && option.getAnalyzeMode() == AnalyzeMode.crud) {
23078                                                dataflow.getRelationships().add(relationElement);
23079                                        }
23080                                        continue;
23081                                }
23082
23083                                boolean append = false;
23084                                for (RelationshipElement<?> sourceItem: relation.getSources()) {
23085                                        Object sourceElement = sourceItem.getElement();
23086                                        TObjectName sourceColumnName = null;
23087                                        if (sourceItem instanceof ResultColumnRelationshipElement) {
23088                                                sourceColumnName = ((ResultColumnRelationshipElement) sourceItem).getColumnName();
23089                                        }
23090//                                      if (sourceItem instanceof TableColumnRelationElement
23091//                                                      && (((TableColumnRelationElement) sourceItem).getColumnIndex() != null)) {
23092//                                              TableColumnRelationElement tableColumnRelationElement = (TableColumnRelationElement) sourceItem;
23093//                                              sourceElement = tableColumnRelationElement.getElement().getTable().getColumns()
23094//                                                              .get(tableColumnRelationElement.getColumnIndex() + 1);
23095//                                      }
23096                                        if (sourceElement instanceof ResultColumn) {
23097                                                ResultColumn sourceColumn = (ResultColumn) sourceElement;
23098                                                if (sourceColumn.hasStarLinkColumn() && !relation.isShowStarRelation() && !sourceColumn.isPseduo()) {
23099                                                        List<String> sourceStarColumnNames = sourceColumn.getStarLinkColumnNames();
23100                                                        sourceColumn source = new sourceColumn();
23101                                                        if (targetObjectNames != null && !targetObjectNames.isEmpty()) {
23102
23103                                                                for (int k = 0; k < targetObjectNames.size(); k++) {
23104                                                                        String targetObjectName = getColumnName(targetObjectNames.get(k));
23105                                                                        if(sourceColumn.getResultSet()!=null && sourceColumn.getResultSet().getColumns()!=null) {
23106                                                                                boolean find = false;
23107                                                                                for(ResultColumn column: sourceColumn.getResultSet().getColumns()) {
23108                                                                                        if(column.isStarColumn() == DlineageUtil.isStarName(targetObjectNames.get(k))
23109                                                                                                        && SQLUtil.sameName(option.getVendor(), ESQLDataObjectType.dotColumn, column.getName(), rawColumnSpelling(targetObjectNames.get(k)))) {
23110                                                                                                source = new sourceColumn();
23111                                                                                                source.setId(String.valueOf(column.getId()));
23112                                                                                                source.setColumn(targetObjectName);
23113                                                                                                source.setParent_id(String.valueOf(sourceColumn.getResultSet().getId()));
23114                                                                                                source.setParent_name(getResultSetName(sourceColumn.getResultSet()));
23115                                                                                                setSourceCoordinate(source, sourceItem, sourceColumn);
23116                                                                                                append = true;
23117                                                                                                relationElement.addSource(source);
23118                                                                                                find = true;
23119                                                                                                break;
23120                                                                                        }
23121                                                                                }
23122                                                                                if(find) {
23123                                                                                        continue;
23124                                                                                }
23125                                                                        }
23126                                                                        int link = sourceColumn.starLinkIndexOf(targetObjectNames.get(k));
23127                                                                        if (link >= 0) {
23128                                                                                source = new sourceColumn();
23129                                                                                source.setId(String.valueOf(sourceColumn.getId()) + "_" + link);
23130                                                                                source.setColumn(sourceColumn.starLinkName(link));
23131                                                                                source.setParent_id(String.valueOf(sourceColumn.getResultSet().getId()));
23132                                                                                source.setParent_name(getResultSetName(sourceColumn.getResultSet()));
23133                                                                                setSourceCoordinate(source, sourceItem, sourceColumn);
23134                                                                                append = true;
23135                                                                                relationElement.addSource(source);
23136                                                                        } else {
23137                                                                                source = new sourceColumn();
23138                                                                                source.setId(String.valueOf(sourceColumn.getId()));
23139                                                                                source.setColumn(relationElement.getTarget().getColumn());
23140                                                                                source.setParent_id(String.valueOf(sourceColumn.getResultSet().getId()));
23141                                                                                source.setParent_name(getResultSetName(sourceColumn.getResultSet()));
23142                                                                                setSourceCoordinate(source, sourceItem, sourceColumn);
23143                                                                                append = true;
23144                                                                                relationElement.addSource(source);
23145                                                                        }
23146                                                                }
23147                                                        } else {
23148                                                                if (columnObject instanceof TWhenClauseItemList) {
23149                                                                        TCaseExpression expr = (TCaseExpression)((FunctionResultColumn) targetElement).getResultSet().getGspObject();
23150                                                                        List<TExpression> directExpressions = new ArrayList<TExpression>();
23151                                                                        
23152//                                                                      TExpression inputExpr = expr.getInput_expr();
23153//                                                                      if (inputExpr != null) {
23154//                                                                              directExpressions.add(inputExpr);
23155//                                                                      }
23156                                                                        TExpression defaultExpr = expr.getElse_expr();
23157                                                                        if (defaultExpr != null) {
23158                                                                                directExpressions.add(defaultExpr);
23159                                                                        }
23160                                                                        TWhenClauseItemList list = expr.getWhenClauseItemList();
23161                                                                        for (int k = 0; k < list.size(); k++) {
23162                                                                                TWhenClauseItem element = list.getWhenClauseItem(k);
23163                                                                                directExpressions.add(element.getReturn_expr());
23164                                                                        }
23165                                                                        
23166                                                                        for (int k = 0; k < directExpressions.size(); k++) {
23167                                                                                columnsInExpr visitor = new columnsInExpr();
23168                                                                                directExpressions.get(k).inOrderTraverse(visitor);
23169                                                                                List<TObjectName> objectNames = visitor.getObjectNames();
23170                                                                                if (objectNames == null) {
23171                                                                                        continue;
23172                                                                                }
23173                                                                                for (int x = 0; x < objectNames.size(); x++) {
23174                                                                                        String objectName = getColumnName(objectNames.get(x));
23175                                                                                        int link = sourceColumn.starLinkIndexOf(objectNames.get(x));
23176                                                                                        if (link >= 0) {
23177
23178                                                                                                if(sourceColumn.getResultSet()!=null && sourceColumn.getResultSet().getColumns()!=null) {
23179                                                                                                        boolean find = false;
23180                                                                                                        for(ResultColumn column: sourceColumn.getResultSet().getColumns()) {
23181                                                                                                                if(SQLUtil.sameName(option.getVendor(), ESQLDataObjectType.dotColumn, column.getName(), rawColumnSpelling(objectNames.get(x)))) {
23182                                                                                                                        source = new sourceColumn();
23183                                                                                                                        source.setId(String.valueOf(column.getId()));
23184                                                                                                                        source.setColumn(objectName);
23185                                                                                                                        source.setParent_id(String.valueOf(sourceColumn.getResultSet().getId()));
23186                                                                                                                        source.setParent_name(getResultSetName(sourceColumn.getResultSet()));
23187                                                                                                                        setSourceCoordinate(source, sourceItem, sourceColumn);
23188                                                                                                                        append = true;
23189                                                                                                                        relationElement.addSource(source);
23190                                                                                                                        find = true;
23191                                                                                                                        break;
23192                                                                                                                }
23193                                                                                                        }
23194                                                                                                        if(find) {
23195                                                                                                                continue;
23196                                                                                                        }
23197                                                                                                }
23198                                                                                                
23199                                                                                                source.setId(String.valueOf(sourceColumn.getId()) + "_" + link);
23200                                                                                                source.setColumn(sourceColumn.starLinkName(link));
23201                                                                                        } else {
23202                                                                                                source.setId(String.valueOf(sourceColumn.getId()));
23203                                                                                                source.setColumn(sourceColumn.getName());
23204                                                                                        }
23205                                                                                        source.setParent_id(String.valueOf(sourceColumn.getResultSet().getId()));
23206                                                                                        source.setParent_name(getResultSetName(sourceColumn.getResultSet()));
23207                                                                                        setSourceCoordinate(source, sourceItem, sourceColumn);
23208                                                                                        append = true;
23209                                                                                        relationElement.addSource(source);
23210                                                                                }
23211                                                                        }
23212                                                                } else {
23213                                                                        String objectName = LegacyDlineageWireNamesV1.columnName(DlineageUtil.columnSegmentOf(targetName));
23214                                                                        boolean find = false;
23215                                                                        // a star read at a position its members' unknown order cannot tell: only an explicit
23216                                                                        // reference names a member, never the target's own name (Mantis 4856)
23217                                                                        boolean positionUnproven = sourceItem instanceof ResultColumnRelationshipElement
23218                                                                                        && ((ResultColumnRelationshipElement) sourceItem).isPositionUnproven();
23219
23220                                                                        int targetLink = positionUnproven ? -1 : sourceColumn.starLinkIndexOf(
23221                                                                                        targetElement instanceof Column ? (Column) targetElement : null, objectName);
23222                                                                        if (!find && targetLink >= 0) {
23223                                                                                source.setId(String.valueOf(sourceColumn.getId()) + "_" + targetLink);
23224                                                                                source.setColumn(sourceColumn.starLinkName(targetLink));
23225                                                                                find = true;
23226                                                                        }
23227
23228                                                                        if (!find && sourceColumnName != null) {
23229                                                                                objectName = getColumnName(sourceColumnName);
23230                                                                                
23231                                                                                if(sourceColumn.getResultSet()!=null && sourceColumn.getResultSet().getColumns()!=null) {
23232                                                                                        for(ResultColumn column: sourceColumn.getResultSet().getColumns()) {
23233                                                                                                if(SQLUtil.sameName(option.getVendor(), ESQLDataObjectType.dotColumn, column.getName(), rawColumnSpelling(sourceColumnName))) {
23234                                                                                                        source = new sourceColumn();
23235                                                                                                        source.setId(String.valueOf(column.getId()));
23236                                                                                                        source.setColumn(objectName);
23237                                                                                                        source.setParent_id(String.valueOf(sourceColumn.getResultSet().getId()));
23238                                                                                                        source.setParent_name(getResultSetName(sourceColumn.getResultSet()));
23239                                                                                                        setSourceCoordinate(source, sourceItem, sourceColumn);
23240                                                                                                        append = true;
23241                                                                                                        relationElement.addSource(source);
23242                                                                                                        find = true;
23243                                                                                                        break;
23244                                                                                                }
23245                                                                                        }
23246                                                                                        if(find) {
23247                                                                                                continue;
23248                                                                                        }
23249                                                                                }
23250                                                                                
23251                                                                                int link = sourceColumn.starLinkIndexOf(sourceColumnName);
23252                                                                                if (link >= 0) {
23253                                                                                        source.setId(String.valueOf(sourceColumn.getId()) + "_" + link);
23254                                                                                        source.setColumn(sourceColumn.starLinkName(link));
23255                                                                                        find = true;
23256                                                                                }
23257                                                                        }
23258                                                                        
23259                                                                        if (!find && sourceItem instanceof ResultColumnRelationshipElement) {
23260                                                                                int starIndex = ((ResultColumnRelationshipElement) sourceItem)
23261                                                                                                .getStarIndex();
23262                                                                                if (starIndex > -1 && sourceColumn.getStarLinkColumnList().size() > starIndex) {
23263                                                                                        objectName = getColumnName(sourceColumn.getStarLinkColumnList().get(starIndex));
23264                                                                                        source.setId(String.valueOf(sourceColumn.getId()) + "_"
23265                                                                                                        + starIndex);
23266                                                                                        source.setColumn(objectName);
23267                                                                                        find = true;
23268                                                                                }
23269                                                                        }
23270
23271                                                                        // A column list (CTAS, view, CTE) binds by position, and before normal
23272                                                                        // names were collision-free its names met the star's links through the
23273                                                                        // legacy fold: Snowflake "a_col" met the link of a_col. Where no link
23274                                                                        // carries the target's own name, keep that match rather than fan the
23275                                                                        // target out to the whole star.
23276                                                                        List<String> legacyLinks = find || sourceColumnName != null || positionUnproven
23277                                                                                        ? Collections.<String>emptyList()
23278                                                                                        : legacyStarLinks(sourceStarColumnNames, targetName);
23279                                                                        if (!legacyLinks.isEmpty()) {
23280                                                                                objectName = legacyLinks.get(0);
23281                                                                                source.setId(String.valueOf(sourceColumn.getId()) + "_"
23282                                                                                                + sourceStarColumnNames.indexOf(objectName));
23283                                                                                source.setColumn(objectName);
23284                                                                                find = true;
23285                                                                        }
23286
23287                                                                        if (!find) {
23288                                                                                source.setId(String.valueOf(sourceColumn.getId()));
23289                                                                                source.setColumn(sourceColumn.getName());
23290                                                                                source.setPositionUnproven(positionUnproven);
23291                                                                        }
23292
23293                                                                        source.setParent_id(String.valueOf(sourceColumn.getResultSet().getId()));
23294                                                                        source.setParent_name(getResultSetName(sourceColumn.getResultSet()));
23295                                                                        setSourceCoordinate(source, sourceItem, sourceColumn);
23296                                                                        append = true;
23297                                                                        relationElement.addSource(source);
23298                                                                        for (int m = 1; m < legacyLinks.size(); m++) {
23299                                                                                sourceColumn legacySource = new sourceColumn();
23300                                                                                legacySource.setId(String.valueOf(sourceColumn.getId()) + "_"
23301                                                                                                + sourceStarColumnNames.indexOf(legacyLinks.get(m)));
23302                                                                                legacySource.setColumn(legacyLinks.get(m));
23303                                                                                legacySource.setParent_id(String.valueOf(sourceColumn.getResultSet().getId()));
23304                                                                                legacySource.setParent_name(getResultSetName(sourceColumn.getResultSet()));
23305                                                                                setSourceCoordinate(legacySource, sourceItem, sourceColumn);
23306                                                                                relationElement.addSource(legacySource);
23307                                                                        }
23308                                                                }
23309                                                        }
23310
23311                                                } else {
23312                                                        sourceColumn source = new sourceColumn();
23313                                                        source.setId(String.valueOf(sourceColumn.getId()));
23314                                                        source.setColumn(sourceColumn.getName());
23315                                                        source.setStruct(sourceColumn.isStruct());
23316                                                        source.setParent_id(String.valueOf(sourceColumn.getResultSet().getId()));
23317                                                        source.setParent_name(getResultSetName(sourceColumn.getResultSet()));
23318                                                        setSourceCoordinate(source, sourceItem, sourceColumn);
23319                                                        append = true;
23320                                                        if (sourceItem.getTransforms() != null) {
23321                                                                for (Transform transform : sourceItem.getTransforms()) {
23322                                                                        source.addTransform(transform);
23323                                                                }
23324                                                        }
23325                                                        if(sourceColumn.isPseduo()) {
23326                                                                source.setSource("system");
23327                                                        }
23328                                                        relationElement.addSource(source);
23329                                                }
23330                                        } else if (sourceElement instanceof TableColumn) {
23331                                                TableColumn sourceColumn = (TableColumn) sourceElement;
23332                                                if (!sourceColumn.isPseduo() && sourceColumn.hasStarLinkColumn()
23333                                                                && sourceItem instanceof TableColumnRelationshipElement) {
23334                                                        sourceColumn source = new sourceColumn();
23335                                                        boolean find = false;
23336                                                        if (((TableColumnRelationshipElement) sourceItem).getColumnIndex() != null) {
23337                                                                int columnIndex = ((TableColumnRelationshipElement) sourceItem).getColumnIndex();
23338                                                                if (sourceColumn.starLinkCount() > columnIndex) {
23339                                                                        source = new sourceColumn();
23340                                                                        source.setId(String.valueOf(sourceColumn.getId()) + "_" + columnIndex);
23341                                                                        String targetObjectName = getColumnName(
23342                                                                                        sourceColumn.getStarLinkColumnList().get(columnIndex));
23343                                                                        source.setColumn(targetObjectName);
23344                                                                        source.setParent_id(String.valueOf(sourceColumn.getTable().getId()));
23345                                                                        source.setParent_name(sourceColumn.getTable().getName());
23346                                                                        if (sourceItem instanceof TableColumnRelationshipElement) {
23347                                                                                source.setParent_alias(
23348                                                                                                ((TableColumnRelationshipElement) sourceItem).getTableAlias());
23349                                                                        }
23350                                                                        setSourceCoordinate(source, sourceItem, sourceColumn);
23351                                                                        append = true;
23352                                                                        relationElement.addSource(source);
23353                                                                        find = true;
23354                                                                }
23355                                                        }
23356
23357                                                        if (!find) {
23358                                                                String objectName = LegacyDlineageWireNamesV1.columnName(DlineageUtil.columnSegmentOf(targetName));
23359
23360                                                                table tableElement = null;
23361                                                                if (dataflow.getTables() != null) {
23362                                                                        for (table t : dataflow.getTables()) {
23363                                                                                if (t.getId().equals(String.valueOf(sourceColumn.getTable().getId()))) {
23364                                                                                        tableElement = t;
23365                                                                                        break;
23366                                                                                }
23367                                                                        }
23368                                                                }
23369                                                                if (tableElement == null && dataflow.getViews() != null) {
23370                                                                        for (table t : dataflow.getViews()) {
23371                                                                                if (t.getId().equals(String.valueOf(sourceColumn.getTable().getId()))) {
23372                                                                                        tableElement = t;
23373                                                                                        break;
23374                                                                                }
23375                                                                        }
23376                                                                }
23377                                                                if (tableElement == null && dataflow.getVariables() != null) {
23378                                                                        for (table t : dataflow.getVariables()) {
23379                                                                                if (t.getId().equals(String.valueOf(sourceColumn.getTable().getId()))) {
23380                                                                                        tableElement = t;
23381                                                                                        break;
23382                                                                                }
23383                                                                        }
23384                                                                }
23385
23386                                                                if(tableElement!=null) {
23387                                                                        for (column column : tableElement.getColumns()) {
23388                                                                                // A star target names no column: the table's column named * is not it.
23389                                                                                if (column.getName() != null && column.standsForStar() == targetIsStar
23390                                                                                                && SQLUtil.sameName(option.getVendor(), ESQLDataObjectType.dotColumn, column.getName(), DlineageUtil.columnSegmentOf(targetName))) {
23391                                                                                        source = new sourceColumn();
23392                                                                                        source.setId(String.valueOf(column.getId()));
23393                                                                                        source.setColumn(objectName);
23394                                                                                        source.setParent_id(String.valueOf(sourceColumn.getTable().getId()));
23395                                                                                        source.setParent_name(sourceColumn.getTable().getName());
23396                                                                                        if (sourceItem instanceof TableColumnRelationshipElement) {
23397                                                                                                source.setParent_alias(
23398                                                                                                                ((TableColumnRelationshipElement) sourceItem).getTableAlias());
23399                                                                                        }
23400                                                                                        setSourceCoordinate(source, sourceItem, sourceColumn);
23401                                                                                        if (sourceItem.getTransforms() != null) {
23402                                                                                                for (Transform transform : sourceItem.getTransforms()) {
23403                                                                                                        source.addTransform(transform);
23404                                                                                                }
23405                                                                                        }
23406                                                                                        if (sourceColumn.getCandidateParents() != null) {
23407                                                                                                for(Object item: sourceColumn.getCandidateParents()) {
23408                                                                                                        candidateTable candidateParent = new candidateTable();
23409                                                                                                        if(item instanceof Table) {
23410                                                                                                                candidateParent.setId(String.valueOf(((Table)item).getId()));
23411                                                                                                                candidateParent.setName(getTableName((Table)item));
23412                                                                                                                source.addCandidateParent(candidateParent);
23413                                                                                                        }
23414                                                                                                        else if(item instanceof ResultSet) {
23415                                                                                                                candidateParent.setId(String.valueOf(((ResultSet)item).getId()));
23416                                                                                                                candidateParent.setName(getResultSetName((ResultSet)item));
23417                                                                                                                source.addCandidateParent(candidateParent);
23418                                                                                                        }
23419                                                                                                }
23420                                                                                        }
23421                                                                                        append = true;
23422                                                                                        relationElement.addSource(source);
23423                                                                                        find = true;
23424                                                                                        break;
23425                                                                                }
23426                                                                        }
23427                                                                }
23428                                                        }
23429
23430                                                        if(!find) {
23431                                                                source = new sourceColumn();
23432                                                                source.setId(String.valueOf(sourceColumn.getId()));
23433                                                                source.setColumn(sourceColumn.getName());
23434                                                                source.setParent_id(String.valueOf(sourceColumn.getTable().getId()));
23435                                                                source.setParent_name(sourceColumn.getTable().getName());
23436                                                                if (sourceItem instanceof TableColumnRelationshipElement) {
23437                                                                        source.setParent_alias(
23438                                                                                        ((TableColumnRelationshipElement) sourceItem).getTableAlias());
23439                                                                }
23440                                                                setSourceCoordinate(source, sourceItem, sourceColumn);
23441                                                                if (sourceItem.getTransforms() != null) {
23442                                                                        for (Transform transform : sourceItem.getTransforms()) {
23443                                                                                source.addTransform(transform);
23444                                                                        }
23445                                                                }
23446                                                                if (sourceColumn.getCandidateParents() != null) {
23447                                                                        for(Object item: sourceColumn.getCandidateParents()) {
23448                                                                                candidateTable candidateParent = new candidateTable();
23449                                                                                if(item instanceof Table) {
23450                                                                                        candidateParent.setId(String.valueOf(((Table)item).getId()));
23451                                                                                        candidateParent.setName(getTableName((Table)item));
23452                                                                                        source.addCandidateParent(candidateParent);
23453                                                                                }
23454                                                                                else if(item instanceof ResultSet) {
23455                                                                                        candidateParent.setId(String.valueOf(((ResultSet)item).getId()));
23456                                                                                        candidateParent.setName(getResultSetName((ResultSet)item));
23457                                                                                        source.addCandidateParent(candidateParent);
23458                                                                                }
23459                                                                        }
23460                                                                }
23461                                                                append = true;
23462                                                                relationElement.addSource(source);
23463                                                        }
23464
23465                                                } else {
23466                                                        sourceColumn source = new sourceColumn();
23467                                                        source.setId(String.valueOf(sourceColumn.getId()));
23468                                                        source.setColumn(sourceColumn.getName());
23469                                                        source.setStruct(sourceColumn.isStruct());
23470                                                        source.setParent_id(String.valueOf(sourceColumn.getTable().getId()));
23471                                                        source.setParent_name(getTableName(sourceColumn.getTable()));
23472                                                        if (sourceItem instanceof TableColumnRelationshipElement) {
23473                                                                source.setParent_alias(
23474                                                                                ((TableColumnRelationshipElement) sourceItem).getTableAlias());
23475                                                        }
23476                                                        setSourceCoordinate(source, sourceItem, sourceColumn);
23477                                                        if (sourceItem.getTransforms() != null) {
23478                                                                for (Transform transform : sourceItem.getTransforms()) {
23479                                                                        source.addTransform(transform);
23480                                                                }
23481                                                        }
23482                                                        if(sourceColumn.isPseduo()) {
23483                                                                source.setSource("system");
23484                                                        }
23485                                                        if (sourceColumn.getCandidateParents() != null) {
23486                                                                for(Object item: sourceColumn.getCandidateParents()) {
23487                                                                        candidateTable candidateParent = new candidateTable();
23488                                                                        if(item instanceof Table) {
23489                                                                                candidateParent.setId(String.valueOf(((Table)item).getId()));
23490                                                                                candidateParent.setName(getTableName((Table)item));
23491                                                                                source.addCandidateParent(candidateParent);
23492                                                                        }
23493                                                                        else if(item instanceof ResultSet) {
23494                                                                                candidateParent.setId(String.valueOf(((ResultSet)item).getId()));
23495                                                                                candidateParent.setName(getResultSetName((ResultSet)item));
23496                                                                                source.addCandidateParent(candidateParent);
23497                                                                        }
23498                                                                }
23499                                                        }
23500                                                        append = true;
23501                                                        relationElement.addSource(source);
23502                                                }
23503                                        } else if (sourceElement instanceof Argument) {
23504                                                Argument sourceColumn = (Argument) sourceElement;
23505                                                sourceColumn source = new sourceColumn();
23506                                                source.setId(String.valueOf(sourceColumn.getId()));
23507                                                source.setColumn(sourceColumn.getName());
23508                                                source.setParent_id(String.valueOf(sourceColumn.getProcedure().getId()));
23509                                                source.setParent_name(sourceColumn.getProcedure().getName());
23510                                                if (sourceColumn.getStartPosition() != null && sourceColumn.getEndPosition() != null) {
23511                                                        source.setCoordinate(convertCoordinate(sourceColumn.getStartPosition()) + ","
23512                                                                        + convertCoordinate(sourceColumn.getEndPosition()));
23513                                                }
23514                                                append = true;
23515                                                relationElement.addSource(source);
23516                                        } else if (sourceElement instanceof TableRelationRows) {
23517                                                TableRelationRows sourceColumn = (TableRelationRows) sourceElement;
23518                                                sourceColumn source = new sourceColumn();
23519                                                source.setId(String.valueOf(sourceColumn.getId()));
23520                                                source.setColumn(sourceColumn.getName());
23521                                                source.setParent_id(String.valueOf(sourceColumn.getHolder().getId()));
23522                                                source.setParent_name(getTableName(sourceColumn.getHolder()));
23523                                                if (sourceColumn.getStartPosition() != null && sourceColumn.getEndPosition() != null) {
23524                                                        source.setCoordinate(convertCoordinate(sourceColumn.getStartPosition()) + ","
23525                                                                        + convertCoordinate(sourceColumn.getEndPosition()));
23526                                                }
23527                                                source.setSource("system");
23528                                                append = true;
23529                                                relationElement.addSource(source);
23530                                        } else if (sourceElement instanceof ResultSetRelationRows) {
23531                                                ResultSetRelationRows sourceColumn = (ResultSetRelationRows) sourceElement;
23532                                                sourceColumn source = new sourceColumn();
23533                                                source.setId(String.valueOf(sourceColumn.getId()));
23534                                                source.setColumn(sourceColumn.getName());
23535                                                source.setParent_id(String.valueOf(sourceColumn.getHolder().getId()));
23536                                                source.setParent_name(getResultSetName(sourceColumn.getHolder()));
23537                                                if (sourceColumn.getStartPosition() != null && sourceColumn.getEndPosition() != null) {
23538                                                        source.setCoordinate(convertCoordinate(sourceColumn.getStartPosition()) + ","
23539                                                                        + convertCoordinate(sourceColumn.getEndPosition()));
23540                                                }
23541                                                source.setSource("system");
23542                                                append = true;
23543                                                relationElement.addSource(source);
23544                                        } else if (sourceElement instanceof Constant) {
23545                                                Constant sourceColumn = (Constant) sourceElement;
23546                                                sourceColumn source = new sourceColumn();
23547                                                source.setId(String.valueOf(sourceColumn.getId()));
23548                                                source.setColumn(sourceColumn.getName());
23549                                                source.setColumn_type("constant");
23550                                                if (sourceColumn.getStartPosition() != null && sourceColumn.getEndPosition() != null) {
23551                                                        source.setCoordinate(convertCoordinate(sourceColumn.getStartPosition()) + ","
23552                                                                        + convertCoordinate(sourceColumn.getEndPosition()));
23553                                                }
23554                                                append = true;
23555                                                relationElement.addSource(source);
23556                                        } else if (sourceElement instanceof Table) {
23557                                                Table table = (Table) sourceElement;
23558                                                sourceColumn source = new sourceColumn();
23559                                                source.setSource_id(String.valueOf(table.getId()));
23560                                                source.setSource_name(getTableName(table));
23561                                                if (table.getStartPosition() != null && table.getEndPosition() != null) {
23562                                                        source.setCoordinate(convertCoordinate(table.getStartPosition()) + ","
23563                                                                        + convertCoordinate(table.getEndPosition()));
23564                                                }
23565                                                append = true;
23566                                                relationElement.addSource(source);
23567                                        }
23568
23569                                        if (relation instanceof ImpactRelationship) {
23570                                                ESqlClause clause = getSqlClause(sourceItem);
23571                                                if (clause != null
23572                                                                && (relationElement.getSources() != null && !relationElement.getSources().isEmpty())) {
23573                                                        relationElement.getSources().get(relationElement.getSources().size() - 1)
23574                                                                        .setClauseType(clause.name());
23575                                                }
23576                                        }
23577                                }
23578                                if (append)
23579                                        dataflow.getRelationships().add(relationElement);
23580                        }
23581                }
23582        }
23583
23584        private boolean containsStar(Collection<RelationshipElement<?>> elements) {
23585                if (elements == null || elements.size() == 0)
23586                        return false;
23587
23588                for (RelationshipElement<?> element : elements) {
23589                        if (element.getElement() instanceof ResultColumn) {
23590                                ResultColumn object = (ResultColumn) element.getElement();
23591                                if (object.isStarColumn() && object.isShowStar())
23592                                        return true;
23593                        } else if (element.getElement() instanceof TableColumn) {
23594                                TableColumn object = (TableColumn) element.getElement();
23595                                if (object.isStarColumn() && object.isShowStar())
23596                                        return true;
23597                        }
23598                }
23599                return false;
23600        }
23601
23602        private Map<String, Set<String>> appendTableStarColumns = new HashMap<String, Set<String>>();
23603
23604        // Tracks column names that have explicit (non-star) sources per target star column.
23605        // Used across relationships to prevent phantom star expansions.
23606        // Key: target star column ID, Value: set of column names with explicit sources.
23607        private Map<Long, StarLinkNameSet> explicitStarTargetColumns = new HashMap<Long, StarLinkNameSet>();
23608        /**
23609         * Remove from a SELECT-list star's expansion the columns its own EXCEPT / EXCLUDE clause takes
23610         * out -- BigQuery {@code SELECT * EXCEPT (a)}, Snowflake {@code SELECT * EXCLUDE (a)}.
23611         *
23612         * <p>Only the PROJECTION changes. The column stays a column of its table: naming it in
23613         * {@code EXCEPT} is proof that it exists there, and the table's own {@code *} still covers it.
23614         * What the clause proves is the narrower fact that this star does not project it.
23615         *
23616         * <p>Routed through {@link ResultColumn#unbindStarLinkColumn} rather than comparing names here,
23617         * because that method owns how a star-link key is derived from a {@link TObjectName}; a second
23618         * copy of that rule in this file would drift from the one analyzeSelectStmt already uses.
23619         *
23620         * @param starColumn the result column a star produced; anything else is left alone
23621         */
23622        private void unbindExceptedStarColumns(ResultColumn starColumn) {
23623                if (starColumn == null || !(starColumn.getColumnObject() instanceof TResultColumn)) {
23624                        return;
23625                }
23626                TObjectNameList exceptColumns =
23627                                ((TResultColumn) starColumn.getColumnObject()).getExceptColumnList();
23628                if (exceptColumns == null) {
23629                        return;
23630                }
23631                for (TObjectName exceptColumn : exceptColumns) {
23632                        starColumn.unbindStarLinkColumn(exceptColumn);
23633                }
23634        }
23635
23636        private void updateResultColumnStarLinks(dataflow dataflow, AbstractRelationship relation, int index) {
23637        try {
23638            if (option.getAnalyzeMode() == AnalyzeMode.crud) {
23639                return;
23640            }
23641
23642            ResultColumn targetColumn = (ResultColumn) relation.getTarget().getElement();
23643
23644            Collection<RelationshipElement<?>> sourceElements = (Collection<RelationshipElement<?>>) relation.getSources();
23645            if (sourceElements == null || sourceElements.size() == 0)
23646                return;
23647
23648            for (RelationshipElement<?> sourceItem : sourceElements) {
23649                Object sourceElement = sourceItem.getElement();
23650                if (sourceElement instanceof ResultColumn) {
23651                    ResultColumn source = (ResultColumn) sourceElement;
23652                    if (source.hasStarLinkColumn()) {
23653                        targetColumn.mergeStarLinksFrom(source);
23654                        if (!source.isShowStar()) {
23655                            targetColumn.setShowStar(false);
23656                            relation.setShowStarRelation(false);
23657                        }
23658                    } else if (!source.isStarColumn()) {
23659                        if (source.getColumnObject() instanceof TObjectName) {
23660                            if (source instanceof FunctionResultColumn) {
23661
23662                            } else {
23663                                targetColumn.bindStarLinkColumn((TObjectName) source.getColumnObject());
23664                            }
23665                        } else if (source.getColumnObject() instanceof TResultColumn) {
23666                            TResultColumn sourceColumn = (TResultColumn) source.getColumnObject();
23667                            if (sourceColumn.getAliasClause() != null) {
23668                                targetColumn.bindStarLinkColumn(sourceColumn.getAliasClause().getAliasName());
23669                            } else if (sourceColumn.getFieldAttr() != null) {
23670                                targetColumn.bindStarLinkColumn(sourceColumn.getFieldAttr());
23671                            } else {
23672                                TObjectName column = Column.nameOfColumn(source,
23673                                        sourceColumn.getExpr().getExpressionType() == EExpressionType.typecast_t
23674                                                ? sourceColumn.getExpr().getLeftOperand().toString()
23675                                                : sourceColumn.toString());
23676                                targetColumn.bindStarLinkColumn(column);
23677                            }
23678                        }
23679                    }
23680                } else if (sourceElement instanceof TableColumn && !targetColumn.isStruct()) {
23681                    TableColumn source = (TableColumn) sourceElement;
23682                    if (!source.isPseduo() && source.hasStarLinkColumn()) {
23683                                                targetColumn.mergeStarLinksFrom(source);
23684                                                if ((source.getTable().isCreateTable() || source.getTable().hasSQLEnv())
23685                                                                && !source.isShowStar()) {
23686                                                        targetColumn.setShowStar(false);
23687                                                        relation.setShowStarRelation(false);
23688                                                }
23689                    } else if (!source.isStarColumn()) {
23690                        targetColumn.bindStarLinkColumn(source.getColumnObject());
23691                    }
23692                }
23693            }
23694
23695            // Re-apply this star's own EXCEPT / EXCLUDE list to the expansion just merged in.
23696            //
23697            // analyzeSelectStmt already filters it (bind each learned column, then
23698            // unbindStarLinkColumn every EXCEPT name), but the merges above copy another column's
23699            // expansion in through the raw starLinkColumns map instead of through
23700            // bindStarLinkColumn, which puts the removed names back. A table's own "*" legitimately
23701            // covers every column of that table -- EXCEPT (x) is itself proof that x IS a column
23702            // there -- so the table side must not change; what must not happen is x re-entering the
23703            // projection of a SELECT-list star that provably does not project it.
23704            unbindExceptedStarColumns(targetColumn);
23705
23706            if (targetColumn.hasStarLinkColumn()) {
23707                table resultSetElement = null;
23708                for (table t : dataflow.getResultsets()) {
23709                    if (t.getId().equals(String.valueOf(targetColumn.getResultSet().getId()))) {
23710                        resultSetElement = t;
23711                        break;
23712                    }
23713                }
23714
23715                int starColumnCount = 0;
23716                for (column item : resultSetElement.getColumns()) {
23717                    if (item.standsForStar()) {
23718                        starColumnCount += 1;
23719                    }
23720                }
23721
23722                if (resultSetElement != null && starColumnCount <= 1) {
23723                    List<String> columns = targetColumn.getStarLinkColumnNames();
23724                    if (index == -1) {
23725                        for (int k = 0; k < columns.size(); k++) {
23726                            String columnName = columns.get(k);
23727                            String id = String.valueOf(targetColumn.getId()) + "_" + k;
23728                            if (appendTableStarColumns.containsKey(resultSetElement.getId()) && appendTableStarColumns.get(resultSetElement.getId()).contains(id)) {
23729                                continue;
23730                            }
23731                            column columnElement = new column();
23732                            columnElement.setId(id);
23733                            columnElement.setIdentifierKey(targetColumn.starLinkIdentifier(k));
23734                            columnElement.setSqlSpelling(targetColumn.starLinkSqlSpelling(k));
23735                            columnElement.setName(columnName);
23736                            if (targetColumn.isFunction()) {
23737                                columnElement.setIsFunction(String.valueOf(targetColumn.isFunction()));
23738                            }
23739                            if (targetColumn.getStartPosition() != null && targetColumn.getEndPosition() != null) {
23740                                columnElement.setCoordinate(convertCoordinate(targetColumn.getStartPosition()) + ","
23741                                        + convertCoordinate(targetColumn.getEndPosition()));
23742                            }
23743
23744                            if (targetColumn.getResultSet() != null && targetColumn.getResultSet().getColumns() != null) {
23745                                boolean find = false;
23746                                for (int i = 0; i < targetColumn.getResultSet().getColumns().size(); i++) {
23747                                    ResultColumn columnModel = targetColumn.getResultSet().getColumns().get(i);
23748                                    if (targetColumn.starLinkNamesColumn(k, columnModel)) {
23749                                        find = true;
23750                                        break;
23751                                    }
23752                                }
23753                                if (find) {
23754                                    continue;
23755                                }
23756                            }
23757
23758                            if (!resultSetElement.getColumns().contains(columnElement)) {
23759                                resultSetElement.getColumns().add(columnElement);
23760                                appendTableStarColumns.putIfAbsent(resultSetElement.getId(), new HashSet<String>());
23761                                appendTableStarColumns.get(resultSetElement.getId()).add(id);
23762                            }
23763                        }
23764                    } else {
23765                        int k = index;
23766                        String columnName = columns.get(k);
23767                        column columnElement = new column();
23768                        columnElement.setId(String.valueOf(targetColumn.getId()) + "_" + k);
23769                        columnElement.setIdentifierKey(targetColumn.starLinkIdentifier(k));
23770                        columnElement.setSqlSpelling(targetColumn.starLinkSqlSpelling(k));
23771                        columnElement.setName(columnName);
23772                        if (targetColumn.isFunction()) {
23773                            columnElement.setIsFunction(String.valueOf(targetColumn.isFunction()));
23774                        }
23775                        if (targetColumn.getStartPosition() != null && targetColumn.getEndPosition() != null) {
23776                            columnElement.setCoordinate(convertCoordinate(targetColumn.getStartPosition()) + ","
23777                                    + convertCoordinate(targetColumn.getEndPosition()));
23778                        }
23779
23780                        if (targetColumn.getResultSet() != null && targetColumn.getResultSet().getColumns() != null) {
23781                            boolean find = false;
23782                            for (int i = 0; i < targetColumn.getResultSet().getColumns().size(); i++) {
23783                                ResultColumn columnModel = targetColumn.getResultSet().getColumns().get(i);
23784                                if (targetColumn.starLinkNamesColumn(k, columnModel)) {
23785                                    find = true;
23786                                    break;
23787                                }
23788                            }
23789                            if (find) {
23790                                return;
23791                            }
23792                        }
23793
23794                        if (!resultSetElement.getColumns().contains(columnElement)) {
23795                            resultSetElement.getColumns().add(columnElement);
23796                        }
23797                    }
23798                }
23799            }
23800        } catch (Exception e) {
23801            logger.error("updateResultColumnStarLinks occurs unknown exceptions.", e);
23802        }
23803        }
23804
23805        private void updateTableColumnStarLinks(dataflow dataflow, AbstractRelationship relation) {
23806                TableColumn targetColumn = (TableColumn) relation.getTarget().getElement();             
23807                Collection<RelationshipElement<?>> sourceElements = (Collection<RelationshipElement<?>>) relation.getSources();
23808                if (sourceElements == null || sourceElements.size() == 0)
23809                        return;
23810
23811                TableColumn sourceStarColumn = null;
23812
23813                for (RelationshipElement<?> sourceItem: sourceElements) {
23814                        Object sourceElement = sourceItem.getElement();
23815                        if (sourceElement instanceof ResultColumn) {
23816                                ResultColumn source = (ResultColumn) sourceElement;
23817                                if(source.getResultSet() instanceof Function) {
23818                                        continue;
23819                                }
23820                                if (source.hasStarLinkColumn()) {
23821                                        targetColumn.mergeStarLinksFrom(source);
23822                                        if (!source.isShowStar()) {
23823                                                targetColumn.setShowStar(false);
23824                                                relation.setShowStarRelation(false);
23825                                        }
23826                                } else if (!source.isStarColumn()) {
23827                                        if (source.getColumnObject() instanceof TObjectName) {
23828                                                targetColumn.bindStarLinkColumn((TObjectName) source.getColumnObject());
23829                                        } else if (source.getColumnObject() instanceof TResultColumn) {
23830                                                if (((TResultColumn) source.getColumnObject()).getAliasClause() != null) {
23831                                                        TObjectName field = ((TResultColumn) source.getColumnObject()).getAliasClause()
23832                                                                        .getAliasName();
23833                                                        if (field != null) {
23834                                                                targetColumn.bindStarLinkColumn(field);
23835                                                        }
23836                                                } else {
23837                                                        TObjectName field = ((TResultColumn) source.getColumnObject()).getFieldAttr();
23838                                                        if (field != null) {
23839                                                                targetColumn.bindStarLinkColumn(field);
23840                                                        } else {
23841                                                                TObjectName column = Column.nameOfColumn(source,
23842                                                                                ((TResultColumn) source.getColumnObject()).getExpr()
23843                                                                                                .getExpressionType() == EExpressionType.typecast_t
23844                                                                                                ? ((TResultColumn) source.getColumnObject()).getExpr().getLeftOperand().toString()
23845                                                                                                : ((TResultColumn) source.getColumnObject()).toString());
23846                                                                targetColumn.bindStarLinkColumn(column);
23847                                                        }
23848                                                }
23849                                        }
23850                                }
23851                        } else if (sourceElement instanceof TableColumn) {
23852                                TableColumn source = (TableColumn) sourceElement;
23853                                if (source.hasStarLinkColumn()) {
23854                                        targetColumn.mergeStarLinksFrom(source);
23855                                        source.mergeStarLinksFrom(targetColumn);
23856
23857                                        sourceStarColumn = source;
23858
23859                                        if (source.getTable().isCreateTable() && !source.isShowStar()) {
23860                                                targetColumn.setShowStar(false);
23861                                                relation.setShowStarRelation(false);
23862                                        }
23863                                } else if (!source.isStarColumn()) {
23864                                        if (source.isStruct()) {
23865                                                targetColumn.bindStarLinkColumn(source.getColumnObject());
23866                                        }
23867                                        else {
23868                                                TObjectName objectName = Column.nameOfColumn(source, DlineageUtil.getColumnNameOnly(source.getName()));
23869                                                targetColumn.bindStarLinkColumn(objectName);
23870                                        }
23871                                }
23872                        }
23873                }
23874
23875                if (targetColumn.hasStarLinkColumn()) {
23876                        table tableElement = null;
23877                        if (dataflow.getTables() != null) {
23878                                for (table t : dataflow.getTables()) {
23879                                        if (t.getId().equals(String.valueOf(targetColumn.getTable().getId()))) {
23880                                                tableElement = t;
23881                                                break;
23882                                        }
23883                                }
23884                        }
23885                        if (tableElement == null && dataflow.getViews() != null) {
23886                                for (table t : dataflow.getViews()) {
23887                                        if (t.getId().equals(String.valueOf(targetColumn.getTable().getId()))) {
23888                                                tableElement = t;
23889                                                break;
23890                                        }
23891                                }
23892                        }
23893                        if (tableElement == null && dataflow.getVariables() != null) {
23894                                for (table t : dataflow.getVariables()) {
23895                                        if (t.getId().equals(String.valueOf(targetColumn.getTable().getId()))) {
23896                                                tableElement = t;
23897                                                break;
23898                                        }
23899                                }
23900                        }
23901
23902                        if (tableElement != null) {
23903                                List<String> columns = targetColumn.getStarLinkColumnNames();
23904                                for (int k = 0; k < columns.size(); k++) {
23905                                        String columnName = columns.get(k);
23906                                        if (containStarColumn(targetColumn.getTable().getColumns(), targetColumn, k)) {
23907                                                continue;
23908                                        }
23909                                        column columnElement = new column();
23910                                        columnElement.setId(String.valueOf(targetColumn.getId()) + "_" + k);
23911                                        columnElement.setIdentifierKey(targetColumn.starLinkIdentifier(k));
23912                                        columnElement.setSqlSpelling(targetColumn.starLinkSqlSpelling(k));
23913                                        columnElement.setName(columnName);
23914                                        if (targetColumn.getStartPosition() != null && targetColumn.getEndPosition() != null) {
23915                                                columnElement.setCoordinate(convertCoordinate(targetColumn.getStartPosition()) + ","
23916                                                                + convertCoordinate(targetColumn.getEndPosition()));
23917                                        }
23918                                        if (!tableElement.getColumns().contains(columnElement)) {
23919                                                tableElement.getColumns().add(columnElement);
23920                                        }
23921                                }
23922                        }
23923                }
23924
23925                if (sourceStarColumn != null) {
23926                        table tableElement = null;
23927                        if (dataflow.getTables() != null) {
23928                                for (table t : dataflow.getTables()) {
23929                                        if (t.getId().equals(String.valueOf(sourceStarColumn.getTable().getId()))) {
23930                                                tableElement = t;
23931                                                break;
23932                                        }
23933                                }
23934                        }
23935                        if (tableElement == null && dataflow.getViews() != null) {
23936                                for (table t : dataflow.getViews()) {
23937                                        if (t.getId().equals(String.valueOf(sourceStarColumn.getTable().getId()))) {
23938                                                tableElement = t;
23939                                                break;
23940                                        }
23941                                }
23942                        }
23943                        if (tableElement == null && dataflow.getVariables() != null) {
23944                                for (table t : dataflow.getVariables()) {
23945                                        if (t.getId().equals(String.valueOf(sourceStarColumn.getTable().getId()))) {
23946                                                tableElement = t;
23947                                                break;
23948                                        }
23949                                }
23950                        }
23951
23952                        if (tableElement != null) {
23953                                List<String> columns = sourceStarColumn.getStarLinkColumnNames();
23954                                for (int k = 0; k < columns.size(); k++) {
23955                                        String columnName = columns.get(k);
23956                                        if (containStarColumn(sourceStarColumn.getTable().getColumns(), sourceStarColumn, k)) {
23957                                                continue;
23958                                        }
23959                                        column columnElement = new column();
23960                                        columnElement.setId(sourceStarColumn.getId() + "_" + k);
23961                                        columnElement.setIdentifierKey(sourceStarColumn.starLinkIdentifier(k));
23962                                        columnElement.setSqlSpelling(sourceStarColumn.starLinkSqlSpelling(k));
23963                                        columnElement.setName(columnName);
23964                                        if (sourceStarColumn.getStartPosition() != null && sourceStarColumn.getEndPosition() != null) {
23965                                                columnElement.setCoordinate(convertCoordinate(sourceStarColumn.getStartPosition()) + ","
23966                                                                + convertCoordinate(sourceStarColumn.getEndPosition()));
23967                                        }
23968                                        if (!tableElement.getColumns().contains(columnElement)) {
23969                                                tableElement.getColumns().add(columnElement);
23970                                        }
23971                                }
23972                        }
23973                }
23974        }
23975
23976        private ESqlClause getSqlClause(RelationshipElement<?> relationshipElement) {
23977                if (relationshipElement instanceof TableColumnRelationshipElement) {
23978                        return ((TableColumnRelationshipElement) relationshipElement).getRelationLocation();
23979                } else if (relationshipElement instanceof ResultColumnRelationshipElement) {
23980                        return ((ResultColumnRelationshipElement) relationshipElement).getRelationLocation();
23981                }
23982                return null;
23983        }
23984
23985        /**
23986         * Emits a source coordinate, preferring the relationship element's own
23987         * reference site (where THIS relation referenced the column — e.g. the
23988         * ON/WHERE operand) over the shared column model's position. A created
23989         * (e.g. #temp) table reuses one TableColumn model for the definition and
23990         * every reference, so the model's position alone would report the
23991         * definition site for every operand.
23992         */
23993        private void setSourceCoordinate(sourceColumn source, RelationshipElement<?> sourceItem,
23994                        TableColumn sourceTableColumn) {
23995                Pair3<Long, Long, String> modelStart = sourceTableColumn.getStartPosition();
23996                Pair3<Long, Long, String> modelEnd = sourceTableColumn.getEndPosition();
23997                Pair3<Long, Long, String> start = modelStart;
23998                Pair3<Long, Long, String> end = modelEnd;
23999                if (sourceItem instanceof TableColumnRelationshipElement) {
24000                        TableColumnRelationshipElement element = (TableColumnRelationshipElement) sourceItem;
24001                        if (element.getReferenceStartPosition() != null && element.getReferenceEndPosition() != null) {
24002                                start = element.getReferenceStartPosition();
24003                                end = element.getReferenceEndPosition();
24004                                if (modelStart != null && modelEnd != null) {
24005                                        // Keep the definition span for the merge pass's dedup steps.
24006                                        source.setDedupCoordinate(convertCoordinate(modelStart) + "," + convertCoordinate(modelEnd));
24007                                }
24008                        }
24009                }
24010                if (start != null && end != null) {
24011                        source.setCoordinate(convertCoordinate(start) + "," + convertCoordinate(end));
24012                }
24013        }
24014
24015        /**
24016         * ResultColumn flavor of {@link #setSourceCoordinate}: a derived table's /
24017         * CTE's result column model carries its select-list definition span, so the
24018         * relationship element's reference site (the ON/WHERE operand) wins when it
24019         * was captured.
24020         */
24021        private void setSourceCoordinate(sourceColumn source, RelationshipElement<?> sourceItem,
24022                        ResultColumn sourceResultColumn) {
24023                Pair3<Long, Long, String> modelStart = sourceResultColumn.getStartPosition();
24024                Pair3<Long, Long, String> modelEnd = sourceResultColumn.getEndPosition();
24025                Pair3<Long, Long, String> start = modelStart;
24026                Pair3<Long, Long, String> end = modelEnd;
24027                if (sourceItem instanceof ResultColumnRelationshipElement) {
24028                        ResultColumnRelationshipElement element = (ResultColumnRelationshipElement) sourceItem;
24029                        if (element.getReferenceStartPosition() != null && element.getReferenceEndPosition() != null) {
24030                                start = element.getReferenceStartPosition();
24031                                end = element.getReferenceEndPosition();
24032                                if (modelStart != null && modelEnd != null) {
24033                                        // Keep the definition span for the merge pass's dedup steps.
24034                                        source.setDedupCoordinate(convertCoordinate(modelStart) + "," + convertCoordinate(modelEnd));
24035                                }
24036                        }
24037                }
24038                if (start != null && end != null) {
24039                        source.setCoordinate(convertCoordinate(start) + "," + convertCoordinate(end));
24040                }
24041        }
24042
24043        private void appendStarRelation(dataflow dataflow, AbstractRelationship relation, int index) {
24044                if(option.getAnalyzeMode() == AnalyzeMode.crud) {
24045                        return;
24046                }
24047                
24048                Object targetElement = relation.getTarget().getElement();
24049
24050                relationship relationElement = new relationship();
24051                relationElement.setType(relation.getRelationshipType().name());
24052                if (relation.getEffectType() != null) {
24053                        relationElement.setEffectType(relation.getEffectType().name());
24054                }
24055                relationElement.setSqlHash(relation.getSqlHash());
24056                relationElement.setSqlComment(relation.getSqlComment());
24057
24058                if (relation.getProcedureId() != null) {
24059                        relationElement.setProcedureId(String.valueOf(relation.getProcedureId()));
24060                }
24061                relationElement.setId(String.valueOf(relation.getId()) + "_" + index);
24062                if (relation.getProcess() != null) {
24063                        relationElement.setProcessId(String.valueOf(relation.getProcess().getId()));
24064                        if (relation.getProcess().getGspObject() != null) {
24065                                relationElement.setProcessType(relation.getProcess().getGspObject().sqlstatementtype.name());
24066                        }
24067                }
24068                if (relation instanceof DataFlowRelationship) {
24069                        relationElement.setSqlHash(((DataFlowRelationship) relation).getSqlHash());
24070                        relationElement.setSqlComment(((DataFlowRelationship) relation).getSqlComment());
24071                
24072                        if (relation.getProcedureId() != null) {
24073                                relationElement.setProcedureId(String.valueOf(relation.getProcedureId()));
24074                        }
24075                }
24076                String targetName = "";
24077                Column targetLinkColumn = null;
24078
24079                if (targetElement instanceof ResultColumn) {
24080                        ResultColumn targetColumn = (ResultColumn) targetElement;
24081                        targetLinkColumn = targetColumn;
24082
24083                        targetName = targetColumn.starLinkName(index);
24084
24085                        
24086                        targetColumn target = new targetColumn();
24087                        target.setId(String.valueOf(targetColumn.getId()) + "_" + index);
24088                        if(targetColumn.getResultSet()!=null && targetColumn.getResultSet().getColumns()!=null) {
24089                                ResultColumn matched = getResultSetColumnLookup(targetColumn.getResultSet().getColumns()).get(targetColumn, index);
24090                                if (matched != null) {
24091                                        target.setId(String.valueOf(matched.getId()));
24092                                }
24093                        }
24094                        target.setColumn(targetName);
24095                        target.setStruct(targetColumn.isStruct());
24096                        target.setParent_id(String.valueOf(targetColumn.getResultSet().getId()));
24097                        target.setParent_name(getResultSetName(targetColumn.getResultSet()));
24098                        if (targetColumn.getStartPosition() != null && targetColumn.getEndPosition() != null) {
24099                                target.setCoordinate(convertCoordinate(targetColumn.getStartPosition()) + ","
24100                                                + convertCoordinate(targetColumn.getEndPosition()));
24101                        }
24102                        relationElement.setTarget(target);
24103                } else if (targetElement instanceof TableColumn) {
24104                        TableColumn targetColumn = (TableColumn) targetElement;
24105                        targetLinkColumn = targetColumn;
24106
24107                        targetName = targetColumn.starLinkName(index);
24108
24109                        TableColumn tableColumn = getTableColumnLookup(targetColumn.getTable().getColumns()).get(targetColumn, index);
24110
24111                        targetColumn target = new targetColumn();
24112                        if (tableColumn == null) {
24113                                target.setId(targetColumn.getId() + "_" + index);
24114                        } else {
24115                                target.setId(String.valueOf(tableColumn.getId()));
24116                        }
24117                        target.setStruct(targetColumn.isStruct());
24118                        target.setColumn(targetName);
24119                        target.setParent_id(String.valueOf(targetColumn.getTable().getId()));
24120                        target.setParent_name(targetColumn.getTable().getName());
24121                        if (relation.getTarget() instanceof TableColumnRelationshipElement) {
24122                                target.setParent_alias(((TableColumnRelationshipElement) relation.getTarget()).getTableAlias());
24123                        }
24124                        if (targetColumn.getStartPosition() != null && targetColumn.getEndPosition() != null) {
24125                                target.setCoordinate(convertCoordinate(targetColumn.getStartPosition()) + ","
24126                                                + convertCoordinate(targetColumn.getEndPosition()));
24127                        }
24128                        relationElement.setTarget(target);
24129                } else {
24130                        return;
24131                }
24132
24133                Collection<RelationshipElement<?>> sourceElements = (Collection<RelationshipElement<?>>) relation.getSources();
24134                if (sourceElements.size() == 0) {
24135                        return;
24136                }
24137
24138                StarLinkMatcher targetLink = targetLinkColumn.starLinkMatcher(index);
24139                if (targetLink == null) {
24140                        return;
24141                }
24142
24143                // Record-fill expansion (#695): when the star being expanded is a
24144                // record VARIABLE's own * (holder type decided at analysis, not
24145                // inferred here), every field is filled by the SAME producer, so the
24146                // name filters below — written for SELECT-* expansion, where source
24147                // and target columns pair by name — must not drop it.
24148                // Record-fill keep-all applies to a SINGLE-source fill only: one opaque
24149                // producer genuinely feeds every field. A multi-source relation fanned
24150                // across the fields would multiply unproven field-level claims.
24151                boolean recordFillTarget = targetElement instanceof TableColumn
24152                                && ((TableColumn) targetElement).getTable() instanceof Variable
24153                                && relation.getSources() != null && relation.getSources().size() == 1;
24154
24155                // Track explicit (non-star) source columns per target star column to prevent
24156                // phantom star expansions. Skip for UNION targets where branches contribute independently.
24157                long targetColumnId = -1;
24158                boolean isUnionTarget = false;
24159                if (targetElement instanceof ResultColumn) {
24160                        ResultColumn tc = (ResultColumn) targetElement;
24161                        targetColumnId = tc.getId();
24162                        isUnionTarget = (tc.getResultSet() instanceof SelectSetResultSet);
24163                } else if (targetElement instanceof TableColumn) {
24164                        targetColumnId = ((TableColumn) targetElement).getId();
24165                }
24166
24167                if (!isUnionTarget && targetColumnId != -1) {
24168                        // Check if any source in this relationship is a non-star source matching the target
24169                        // column name, AND whose parent does NOT also contribute a star source.
24170                        // This distinguishes:
24171                        //   - "b.col_a" (definitive: b only provides explicit cols, not *) => suppress star expansion
24172                        //   - "aTab.id" (not definitive: aTab also provides *) => don't suppress
24173                        boolean hasDefinitiveExplicitSource = hasDefinitiveNonStarSource(sourceElements, targetLink);
24174                        if (hasDefinitiveExplicitSource) {
24175                                if (!explicitStarTargetColumns.containsKey(targetColumnId)) {
24176                                        explicitStarTargetColumns.put(targetColumnId, new StarLinkNameSet());
24177                                }
24178                                explicitStarTargetColumns.get(targetColumnId).add(targetLink);
24179                        }
24180                }
24181
24182                // Column names that have explicit (non-star) sources for this target, across all relationships
24183                StarLinkNameSet targetExplicitNames = isUnionTarget ? null : explicitStarTargetColumns.get(targetColumnId);
24184
24185                for (RelationshipElement<?> sourceItem: sourceElements) {
24186                        Object sourceElement = sourceItem.getElement();
24187                        if (sourceElement instanceof ResultColumn) {
24188                                ResultColumn sourceColumn = (ResultColumn) sourceElement;
24189                                if (sourceColumn.hasStarLinkColumn()) {
24190                                        List<String> linkColumnNames = sourceColumn.getStarLinkColumnNames();
24191                                        int linkColumnNameSize = linkColumnNames.size();
24192                                        for (int k = 0; k < linkColumnNameSize; k++) {
24193                                                String sourceName = linkColumnNames.get(k);
24194                                                if (relation.getRelationshipType() == RelationshipType.fdd) {
24195                                                        if (!targetLink.matches(sourceColumn, k))
24196                                                                continue;
24197                                                }
24198                                                // Skip star-expanded source when an explicit (non-star) source provides
24199                                                // the same column, either in this relationship or a prior one.
24200                                                if (targetExplicitNames != null && targetExplicitNames.contains(sourceColumn, k)) {
24201                                                        continue;
24202                                                }
24203                                                sourceColumn source = new sourceColumn();                                               
24204                                                
24205                                                boolean find = false;
24206                                                if(sourceColumn.getResultSet()!=null && sourceColumn.getResultSet().getColumns()!=null) {
24207                                                        ResultColumn matched = getResultSetColumnLookup(sourceColumn.getResultSet().getColumns()).get(sourceColumn, k);
24208                                                        if (matched != null) {
24209                                                                source.setId(String.valueOf(matched.getId()));
24210                                                                find = true;
24211                                                        }
24212                                                }
24213                                                if(!find) {
24214                                                        source.setId(String.valueOf(sourceColumn.getId()) + "_" + k);
24215                                                }
24216                                                source.setColumn(sourceName);
24217                                                source.setParent_id(String.valueOf(sourceColumn.getResultSet().getId()));
24218                                                source.setParent_name(getResultSetName(sourceColumn.getResultSet()));
24219                                                if (sourceColumn.getStartPosition() != null && sourceColumn.getEndPosition() != null) {
24220                                                        source.setCoordinate(convertCoordinate(sourceColumn.getStartPosition()) + ","
24221                                                                        + convertCoordinate(sourceColumn.getEndPosition()));
24222                                                }
24223                                                if (sourceItem.getTransforms() != null) {
24224                                                        for (Transform transform : sourceItem.getTransforms()) {
24225                                                                source.addTransform(transform);
24226                                                        }
24227                                                }
24228                                                relationElement.addSource(source);
24229                                        }
24230                                        if(relationElement.getSources().isEmpty()
24231                                                && !(targetExplicitNames != null && targetExplicitNames.contains(targetLink))
24232                                                && sourceColumn.getResultSet()!=null && sourceColumn.getResultSet().getColumns()!=null) {
24233                                                for (ResultColumn columnModel : getResultSetColumnLookup(sourceColumn.getResultSet().getColumns()).values()) {
24234                                                        if (targetLink.matchesColumn(columnModel)) {
24235                                                                sourceColumn source = new sourceColumn();
24236                                                                source.setId(String.valueOf(columnModel.getId()));
24237                                                                source.setColumn(columnModel.getName());
24238                                                                source.setParent_id(String.valueOf(sourceColumn.getResultSet().getId()));
24239                                                                source.setParent_name(getResultSetName(sourceColumn.getResultSet()));
24240                                                                if (sourceColumn.getStartPosition() != null && sourceColumn.getEndPosition() != null) {
24241                                                                        source.setCoordinate(convertCoordinate(sourceColumn.getStartPosition()) + ","
24242                                                                                        + convertCoordinate(sourceColumn.getEndPosition()));
24243                                                                }
24244                                                                if (sourceItem.getTransforms() != null) {
24245                                                                        for (Transform transform : sourceItem.getTransforms()) {
24246                                                                                source.addTransform(transform);
24247                                                                        }
24248                                                                }
24249                                                                relationElement.addSource(source);
24250                                                                break;
24251                                                        }
24252                                                }
24253                                        }
24254                                        // No source link matched: the target's column is inferred to come through this star, unless the
24255                                        // star holds another identity's column under the same name (grouped by identity, a different
24256                                        // column, which this star does not also hold by inference).
24257                                        if (relationElement.getSources().isEmpty()
24258                                                && !(targetExplicitNames != null && targetExplicitNames.contains(targetLink))
24259                                                && !targetLink.isDisplacedIn(sourceColumn)) {
24260                                                TObjectName sourceStarLinkColumn = targetLinkColumn.starLinkNameNode(index);
24261                                                boolean newBinding = sourceColumn.bindStarLinkColumn(sourceStarLinkColumn);
24262                                                sourceColumn source = new sourceColumn();
24263                                                String sourceName = LegacyDlineageWireNamesV1.columnName(sourceStarLinkColumn);
24264                                                if (!newBinding) {
24265                                                        source.setId(String.valueOf(sourceColumn.getId()) + "_"
24266                                                                        + sourceColumn.indexOfStarLinkColumn(sourceStarLinkColumn));
24267                                                } else {
24268                                                        source.setId(String.valueOf(sourceColumn.getId()) + "_" + linkColumnNameSize);
24269                                                }
24270                                                source.setColumn(sourceName);
24271                                                source.setParent_id(String.valueOf(sourceColumn.getResultSet().getId()));
24272                                                source.setParent_name(getResultSetName(sourceColumn.getResultSet()));
24273                                                if (sourceColumn.getStartPosition() != null && sourceColumn.getEndPosition() != null) {
24274                                                        source.setCoordinate(convertCoordinate(sourceColumn.getStartPosition()) + ","
24275                                                                        + convertCoordinate(sourceColumn.getEndPosition()));
24276                                                }
24277                                                if (sourceItem.getTransforms() != null) {
24278                                                        for (Transform transform : sourceItem.getTransforms()) {
24279                                                                source.addTransform(transform);
24280                                                        }
24281                                                }
24282                                                relationElement.addSource(source);
24283
24284                                                if (newBinding) {
24285                                                        table resultSetElement = null;
24286                                                        for (table t : dataflow.getResultsets()) {
24287                                                                if (t.getId().equals(String.valueOf(sourceColumn.getResultSet().getId()))) {
24288                                                                        resultSetElement = t;
24289                                                                        break;
24290                                                                }
24291                                                        }
24292                                                        if (resultSetElement != null) {
24293                                                                column columnElement = new column();
24294                                                                columnElement.setId(String.valueOf(sourceColumn.getId()) + "_" + linkColumnNameSize);
24295                                                                columnElement.setIdentifierKey(sourceColumn.starLinkIdentifier(linkColumnNameSize));
24296                                                                columnElement.setSqlSpelling(sourceColumn.starLinkSqlSpelling(linkColumnNameSize));
24297                                                                columnElement.setName(sourceName);
24298                                                                if (sourceColumn.isFunction()) {
24299                                                                        columnElement.setIsFunction(String.valueOf(sourceColumn.isFunction()));
24300                                                                }
24301                                                                if (sourceColumn.getStartPosition() != null && sourceColumn.getEndPosition() != null) {
24302                                                                        columnElement.setCoordinate(convertCoordinate(sourceColumn.getStartPosition()) + ","
24303                                                                                        + convertCoordinate(sourceColumn.getEndPosition()));
24304                                                                }
24305                                                                resultSetElement.getColumns().add(columnElement);
24306                                                        }
24307                                                }
24308                                        }
24309                                } else {
24310                                        sourceColumn source = new sourceColumn();
24311                                        source.setId(String.valueOf(sourceColumn.getId()));
24312                                        source.setColumn(sourceColumn.getName());
24313                                        source.setStruct(sourceColumn.isStruct());
24314                                        source.setParent_id(String.valueOf(sourceColumn.getResultSet().getId()));
24315                                        source.setParent_name(getResultSetName(sourceColumn.getResultSet()));
24316                                        if (sourceColumn.getStartPosition() != null && sourceColumn.getEndPosition() != null) {
24317                                                source.setCoordinate(convertCoordinate(sourceColumn.getStartPosition()) + ","
24318                                                                + convertCoordinate(sourceColumn.getEndPosition()));
24319                                        }
24320                                        if (relation.getRelationshipType() == RelationshipType.fdd) {
24321                                                // Model display-name compares (can be constants/expressions) — see
24322                                                // the P0d.3c2 reclassification note above.
24323                                                if (!targetLink.matchesColumn(sourceColumn)) {
24324                                                        if (!sourceColumn.isStarColumn()) {
24325                                                                // Record-fill (#695): keep a producer only when it
24326                                                                // is OPAQUE — a single-column result (function
24327                                                                // value, collection element) genuinely feeds every
24328                                                                // field. A source from a multi-column result shape
24329                                                                // pairs its own fields by name; fanning it onto
24330                                                                // fields of OTHER shapes asserts dependencies the
24331                                                                // SQL never made.
24332                                                                if (!recordFillTarget) {
24333                                                                        continue;
24334                                                                }
24335                                                                boolean opaqueProducer = sourceColumn.getResultSet() == null
24336                                                                                || sourceColumn.getResultSet().getColumns() == null
24337                                                                                || sourceColumn.getResultSet().getColumns().size() <= 1;
24338                                                                if (!opaqueProducer) {
24339                                                                        continue;
24340                                                                }
24341                                                        }
24342                                                        else {
24343                                                                boolean flag = false;
24344                                                                for (ResultColumn candidate : getResultSetColumnLookup(sourceColumn.getResultSet().getColumns()).values()) {
24345                                                                        if (targetLink.matchesColumn(candidate)) {
24346                                                                                flag = true;
24347                                                                                break;
24348                                                                        }
24349                                                                }
24350                                                                if(flag) {
24351                                                                        continue;
24352                                                                }
24353                                                        }
24354                                                }
24355                                        }
24356                                        if (sourceItem.getTransforms() != null) {
24357                                                for (Transform transform : sourceItem.getTransforms()) {
24358                                                        source.addTransform(transform);
24359                                                }
24360                                        }
24361                                        relationElement.addSource(source);
24362                                }
24363                        } else if (sourceElement instanceof TableColumn) {
24364                                TableColumn sourceColumn = (TableColumn) sourceElement;
24365                                if (!sourceColumn.isPseduo() && sourceColumn.hasStarLinkColumn()) {
24366                                        List<String> linkColumnNames = sourceColumn.getStarLinkColumnNames();
24367                                        int linkColumnNameSize = linkColumnNames.size();
24368                                        for (int k = 0; k < linkColumnNameSize; k++) {
24369                                                String sourceName = linkColumnNames.get(k);
24370                                                if (relation.getRelationshipType() == RelationshipType.fdd) {
24371                                                        if (!targetLink.matches(sourceColumn, k))
24372                                                                continue;
24373                                                }
24374
24375                                                TableColumn tableColumn = getTableColumnLookup(sourceColumn.getTable().getColumns()).get(sourceColumn, k);
24376
24377                                                sourceColumn source = new sourceColumn();
24378                                                if (tableColumn == null) {
24379                                                        source.setId(sourceColumn.getId() + "_" + k);
24380                                                } else {
24381                                                        source.setId(String.valueOf(tableColumn.getId()));
24382                                                }
24383                                                source.setColumn(sourceName);
24384                                                source.setStruct(sourceColumn.isStruct());
24385                                                if (containStarColumn(sourceElements, sourceColumn, k)) {
24386                                                        continue;
24387                                                }
24388                                                // Cross-relationship check: skip if explicit source was found in prior relationship
24389                                                if (targetExplicitNames != null && targetExplicitNames.contains(sourceColumn, k)) {
24390                                                        continue;
24391                                                }
24392                                                if (sourceColumn.getTable().getColumns().size() > 1) {
24393                                                        for (int y = 0; y < sourceColumn.getTable().getColumns().size(); y++) {
24394                                                                if (SQLUtil.sameName(option.getVendor(), ESQLDataObjectType.dotColumn,
24395                                                                                sourceColumn.getTable().getColumns().get(y).getName(),
24396                                                                                rawColumnSpelling(sourceColumn.getStarLinkColumnName(k)))) {
24397                                                                        source.setId(String.valueOf(sourceColumn.getTable().getColumns().get(y).getId()));
24398                                                                        break;
24399                                                                }
24400                                                        }
24401                                                }
24402                                                source.setParent_id(String.valueOf(sourceColumn.getTable().getId()));
24403                                                source.setParent_name(getTableName(sourceColumn.getTable()));
24404                                                if (sourceItem instanceof TableColumnRelationshipElement) {
24405                                                        source.setParent_alias(
24406                                                                        ((TableColumnRelationshipElement) sourceItem).getTableAlias());
24407                                                }
24408                                                if (sourceColumn.getStartPosition() != null && sourceColumn.getEndPosition() != null) {
24409                                                        source.setCoordinate(convertCoordinate(sourceColumn.getStartPosition()) + ","
24410                                                                        + convertCoordinate(sourceColumn.getEndPosition()));
24411                                                }
24412                                                if (sourceItem.getTransforms() != null) {
24413                                                        for (Transform transform : sourceItem.getTransforms()) {
24414                                                                source.addTransform(transform);
24415                                                        }
24416                                                }
24417                                                relationElement.addSource(source);
24418                                        }
24419                                } else {
24420                                        sourceColumn source = new sourceColumn();
24421                                        source.setId(String.valueOf(sourceColumn.getId()));
24422                                        source.setColumn(sourceColumn.getName());
24423                                        source.setStruct(sourceColumn.isStruct());
24424                                        source.setParent_id(String.valueOf(sourceColumn.getTable().getId()));
24425                                        source.setParent_name(getTableName(sourceColumn.getTable()));
24426                                        if (sourceItem instanceof TableColumnRelationshipElement) {
24427                                                source.setParent_alias(((TableColumnRelationshipElement) sourceItem).getTableAlias());
24428                                        }
24429                                        if (sourceColumn.getStartPosition() != null && sourceColumn.getEndPosition() != null) {
24430                                                source.setCoordinate(convertCoordinate(sourceColumn.getStartPosition()) + ","
24431                                                                + convertCoordinate(sourceColumn.getEndPosition()));
24432                                        }
24433                                        if (relation.getRelationshipType() == RelationshipType.fdd) {
24434                                                // Model display-name compare — see the P0d.3c2 reclassification note.
24435                                                if (!targetLink.matchesColumn(sourceColumn)
24436                                                                && !sourceColumn.isStarColumn()) {
24437                                                        // Record-fill (#695): same keep-only-if-opaque rule as
24438                                                        // the resultset-source branch above.
24439                                                        if (!recordFillTarget) {
24440                                                                continue;
24441                                                        }
24442                                                        boolean opaqueProducer = sourceColumn.getTable() == null
24443                                                                        || sourceColumn.getTable().getColumns() == null
24444                                                                        || sourceColumn.getTable().getColumns().size() <= 1;
24445                                                        if (!opaqueProducer) {
24446                                                                continue;
24447                                                        }
24448                                                }
24449                                        }
24450                                        if (sourceItem.getTransforms() != null) {
24451                                                for (Transform transform : sourceItem.getTransforms()) {
24452                                                        source.addTransform(transform);
24453                                                }
24454                                        }
24455                                        relationElement.addSource(source);
24456                                }
24457                        }
24458                }
24459
24460                if (relationElement.getTarget() != null && relationElement.getSources() != null
24461                                && !relationElement.getSources().isEmpty()) {
24462                        dataflow.getRelationships().add(relationElement);
24463                }
24464        }
24465
24466        /**
24467         * The star links that share {@code targetName}'s legacy normal name, compared without case as
24468         * the legacy fold made them: the links a name met before normal names were collision-free.
24469         * Empty when the analysis has no contested names, since every link then already carries it.
24470         */
24471        private List<String> legacyStarLinks(List<String> links, String targetName) {
24472                ModelBindingManager manager = ModelBindingManager.get();
24473                if (manager == null || !manager.hasContestedNormalNames() || targetName == null || links == null) {
24474                        return Collections.emptyList();
24475                }
24476                String target = legacyColumnKey(targetName);
24477                if (target == null) {
24478                        return Collections.emptyList();
24479                }
24480                List<String> matched = new ArrayList<String>();
24481                for (String link : links) {
24482                        if (target.equals(legacyColumnKey(link))) {
24483                                matched.add(link);
24484                        }
24485                }
24486                return matched;
24487        }
24488
24489        /** Set when the analysis's only unit is scanned from its parse's tokens; see analyzeSqlScript. */
24490        private boolean collisionScanPending;
24491
24492        /** The rules names are told apart under: the analysis's own, for the analysis's vendor. */
24493        private IdentifierService collisionService() {
24494                IdentifierService service = ModelBindingManager.getGlobalIdentifierService();
24495                return service != null && service.getProfile().getVendor() == option.getVendor() ? service
24496                                : IdentifierService.defaultServiceFor(option.getVendor());
24497        }
24498
24499        private void publishContestedNormalNames(Set<String> contested) {
24500                modelManager.setContestedNormalNames(option.getVendor(), contested);
24501        }
24502
24503        /** A legacy normal name without case: the equality the star matching used before (HEAD's equalsIgnoreCase). */
24504        private String legacyColumnKey(String name) {
24505                String legacy = SQLUtil.legacyNormalSegment(option.getVendor(), ESQLDataObjectType.dotColumn, name);
24506                return legacy == null ? null : legacy.toUpperCase(Locale.ROOT);
24507        }
24508
24509        /**
24510         * Whether a reference is the wildcard. The parse says so ({@link DlineageUtil#isWildcard}); a
24511         * synthesized reference spelled {@code *} counts too, while a delimited {@code "*"} is a column.
24512         */
24513        private static boolean isWildcardReference(TObjectName column) {
24514                return DlineageUtil.isStarName(column);
24515        }
24516
24517        /** Identifier comparison must use the AST spelling, not a display-normalized name. */
24518        private String rawColumnSpelling(TObjectName column) {
24519                if (column == null) return null;
24520                String name = column.getColumnNameOnly();
24521                return name == null || name.trim().isEmpty() ? column.toString().trim() : name.trim();
24522        }
24523
24524        /**
24525         * The label a column reference is published under (output version 1): the endpoint labels of star links and the
24526         * display a star-link lookup falls back to ({@link Column#starLinkIndexOf(Column, String)}). Never a key.
24527         */
24528        private String getColumnName(TObjectName column) {
24529                return LegacyDlineageWireNamesV1.columnName(column);
24530        }
24531
24532        /**
24533         * The struct field path a column reference reads, {@code customer.address.city}, as the one entry
24534         * {@link TObjectName#getFieldPathRead()} finds it (STRUCT tree plan, section 4). Null when the reference reads a
24535         * whole column, and in a dialect without STRUCT field reads.
24536         */
24537        private String getStructFieldFullName(TObjectName column) {
24538                if (column == null || !(nestedProfile().buildsDeclaredTree()
24539                                || (nestedProfile().publishesPropertyNames() && nestedProfile().readsAttributePaths()))) return null;
24540                gudusoft.gsqlparser.common.structured.StructuredColumnPath read = column.getFieldPathRead();
24541                return read == null ? null : read.toDisplayString();
24542        }
24543
24544        /**
24545         * The column whose field a column reference reads, {@code customer} in {@code customer.address.city}, as
24546         * {@link TObjectName#getFieldPathBaseToken()} finds it. Null when the reference reads a whole column.
24547         */
24548        private String getStructFieldBaseName(TObjectName column) {
24549                TSourceToken base = column == null ? null : column.getFieldPathBaseToken();
24550                return base == null ? null : base.toString();
24551        }
24552
24553        /** Whether the dialect reads STRUCT fields by path: BigQuery, the Trino family and Redshift ({@code SUPER}). */
24554        /** The nested-access profile of the analysis's vendor: every vendor axis of nested access is read from it. */
24555        private gudusoft.gsqlparser.nodes.structured.NestedAccessProfile nestedProfile() {
24556                return gudusoft.gsqlparser.nodes.structured.NestedAccessProfile.forVendor(option.getVendor());
24557        }
24558
24559        private String getColumnNameOnly(String column) {
24560                if (column == null) {
24561                        return null;
24562                }
24563                return DlineageUtil.getColumnNameOnly(column);
24564        }
24565
24566        private void appendRecordSetRelation(dataflow dataflow, Relationship[] relations) {
24567                for (int i = 0; i < relations.length; i++) {
24568                        AbstractRelationship relation = (AbstractRelationship) relations[i];
24569                        relationship relationElement = new relationship();
24570                        relationElement.setType(relation.getRelationshipType().name());
24571                        if (relation.getFunction() != null) {
24572                                relationElement.setFunction(relation.getFunction());
24573                        }
24574                        if (relation.getEffectType() != null) {
24575                                relationElement.setEffectType(relation.getEffectType().name());
24576                        }
24577                        relationElement.setSqlHash(relation.getSqlHash());
24578                        relationElement.setSqlComment(relation.getSqlComment());
24579
24580                        if (relation.getProcedureId() != null) {
24581                                relationElement.setProcedureId(String.valueOf(relation.getProcedureId()));
24582                        }
24583                        relationElement.setId(String.valueOf(relation.getId()));
24584
24585                        if (relation instanceof RecordSetRelationship) {
24586                                RecordSetRelationship recordCountRelation = (RecordSetRelationship) relation;
24587
24588                                Object targetElement = recordCountRelation.getTarget().getElement();
24589                                if (targetElement instanceof ResultColumn) {
24590                                        ResultColumn targetColumn = (ResultColumn) targetElement;
24591                                        targetColumn target = new targetColumn();
24592                                        target.setId(String.valueOf(targetColumn.getId()));
24593                                        target.setColumn(targetColumn.getName());
24594                                        target.setStruct(targetColumn.isStruct());
24595                                        target.setFunction(recordCountRelation.getAggregateFunction());
24596                                        target.setParent_id(String.valueOf(targetColumn.getResultSet().getId()));
24597                                        target.setParent_name(getResultSetName(targetColumn.getResultSet()));
24598                                        if (targetColumn.getStartPosition() != null && targetColumn.getEndPosition() != null) {
24599                                                target.setCoordinate(convertCoordinate(targetColumn.getStartPosition()) + ","
24600                                                                + convertCoordinate(targetColumn.getEndPosition()));
24601                                        }
24602                                        relationElement.setTarget(target);
24603                                } else if (targetElement instanceof TableColumn) {
24604                                        TableColumn targetColumn = (TableColumn) targetElement;
24605                                        targetColumn target = new targetColumn();
24606                                        target.setId(String.valueOf(targetColumn.getId()));
24607                                        target.setColumn(targetColumn.getName());
24608                                        target.setStruct(targetColumn.isStruct());
24609                                        target.setFunction(recordCountRelation.getAggregateFunction());
24610                                        target.setParent_id(String.valueOf(targetColumn.getTable().getId()));
24611                                        target.setParent_name(getTableName(targetColumn.getTable()));
24612                                        if (recordCountRelation.getTarget() instanceof TableColumnRelationshipElement) {
24613                                                target.setParent_alias(
24614                                                                ((TableColumnRelationshipElement) recordCountRelation.getTarget()).getTableAlias());
24615                                        }
24616                                        if (targetColumn.getStartPosition() != null && targetColumn.getEndPosition() != null) {
24617                                                target.setCoordinate(convertCoordinate(targetColumn.getStartPosition()) + ","
24618                                                                + convertCoordinate(targetColumn.getEndPosition()));
24619                                        }
24620                                        relationElement.setTarget(target);
24621                                } else if (targetElement instanceof ResultSetRelationRows) {
24622                                        ResultSetRelationRows targetColumn = (ResultSetRelationRows) targetElement;
24623                                        targetColumn target = new targetColumn();
24624                                        target.setId(String.valueOf(targetColumn.getId()));
24625                                        target.setColumn(targetColumn.getName());
24626                                        target.setParent_id(String.valueOf(targetColumn.getHolder().getId()));
24627                                        target.setParent_name(getResultSetName(targetColumn.getHolder()));
24628                                        if (targetColumn.getStartPosition() != null && targetColumn.getEndPosition() != null) {
24629                                                target.setCoordinate(convertCoordinate(targetColumn.getStartPosition()) + ","
24630                                                                + convertCoordinate(targetColumn.getEndPosition()));
24631                                        }
24632                                        target.setSource("system");
24633                                        relationElement.setTarget(target);
24634                                } else {
24635                                        continue;
24636                                }
24637
24638                                Collection<RelationshipElement<?>> sourceElements = (Collection<RelationshipElement<?>>)recordCountRelation.getSources();
24639                                if (sourceElements.size() == 0) {
24640                                        continue;
24641                                }
24642
24643                                boolean append = false;
24644                                for (RelationshipElement<?> sourceItem: sourceElements) {
24645                                        Object sourceElement = sourceItem.getElement();
24646                                        if (sourceElement instanceof Table) {
24647                                                Table table = (Table) sourceElement;
24648                                                sourceColumn source = new sourceColumn();
24649                                                source.setSource_id(String.valueOf(table.getId()));
24650                                                source.setSource_name(getTableName(table));
24651                                                if (table.getStartPosition() != null && table.getEndPosition() != null) {
24652                                                        source.setCoordinate(convertCoordinate(table.getStartPosition()) + ","
24653                                                                        + convertCoordinate(table.getEndPosition()));
24654                                                }
24655                                                append = true;
24656                                                relationElement.addSource(source);
24657                                        } else if (sourceElement instanceof QueryTable) {
24658                                                QueryTable table = (QueryTable) sourceElement;
24659                                                sourceColumn source = new sourceColumn();
24660                                                source.setSource_id(String.valueOf(table.getId()));
24661                                                source.setSource_name(getResultSetName(table));
24662                                                if (table.getStartPosition() != null && table.getEndPosition() != null) {
24663                                                        source.setCoordinate(convertCoordinate(table.getStartPosition()) + ","
24664                                                                        + convertCoordinate(table.getEndPosition()));
24665                                                }
24666                                                append = true;
24667                                                relationElement.addSource(source);
24668                                        } else if (sourceElement instanceof TableRelationRows) {
24669                                                TableRelationRows relationRows = (TableRelationRows) sourceElement;
24670                                                sourceColumn source = new sourceColumn();
24671                                                source.setId(String.valueOf(relationRows.getId()));
24672                                                source.setColumn(relationRows.getName());
24673                                                source.setParent_id(String.valueOf(relationRows.getHolder().getId()));
24674                                                source.setParent_name(getTableName(relationRows.getHolder()));
24675                                                if (relationRows.getStartPosition() != null && relationRows.getEndPosition() != null) {
24676                                                        source.setCoordinate(convertCoordinate(relationRows.getStartPosition()) + ","
24677                                                                        + convertCoordinate(relationRows.getEndPosition()));
24678                                                }
24679                                                source.setSource("system");
24680                                                append = true;
24681                                                relationElement.addSource(source);
24682                                        } else if (sourceElement instanceof ResultSetRelationRows) {
24683                                                ResultSetRelationRows relationRows = (ResultSetRelationRows) sourceElement;
24684                                                sourceColumn source = new sourceColumn();
24685                                                source.setId(String.valueOf(relationRows.getId()));
24686                                                source.setColumn(relationRows.getName());
24687                                                source.setParent_id(String.valueOf(relationRows.getHolder().getId()));
24688                                                source.setParent_name(getResultSetName(relationRows.getHolder()));
24689                                                if (relationRows.getStartPosition() != null && relationRows.getEndPosition() != null) {
24690                                                        source.setCoordinate(convertCoordinate(relationRows.getStartPosition()) + ","
24691                                                                        + convertCoordinate(relationRows.getEndPosition()));
24692                                                }
24693                                                source.setSource("system");
24694                                                append = true;
24695                                                relationElement.addSource(source);
24696                                        } else if (sourceElement instanceof TableColumn) {
24697                                                TableColumn sourceColumn = (TableColumn) sourceElement;
24698                                                sourceColumn source = new sourceColumn();
24699                                                source.setId(String.valueOf(sourceColumn.getId()));
24700                                                source.setColumn(sourceColumn.getName());
24701                                                source.setStruct(sourceColumn.isStruct());
24702                                                source.setParent_id(String.valueOf(sourceColumn.getTable().getId()));
24703                                                source.setParent_name(getTableName(sourceColumn.getTable()));
24704                                                if (sourceItem instanceof TableColumnRelationshipElement) {
24705                                                        source.setParent_alias(
24706                                                                        ((TableColumnRelationshipElement) sourceItem).getTableAlias());
24707                                                }
24708                                                setSourceCoordinate(source, sourceItem, sourceColumn);
24709                                                append = true;
24710                                                relationElement.addSource(source);
24711                                        }
24712                                        if (sourceElement instanceof ResultColumn) {
24713                                                ResultColumn sourceColumn = (ResultColumn) sourceElement;
24714                                                sourceColumn source = new sourceColumn();
24715                                                source.setId(String.valueOf(sourceColumn.getId()));
24716                                                source.setColumn(sourceColumn.getName());
24717                                                source.setStruct(sourceColumn.isStruct());
24718                                                source.setParent_id(String.valueOf(sourceColumn.getResultSet().getId()));
24719                                                source.setParent_name(getResultSetName(sourceColumn.getResultSet()));
24720                                                setSourceCoordinate(source, sourceItem, sourceColumn);
24721                                                append = true;
24722                                                relationElement.addSource(source);
24723                                        }
24724                                }
24725
24726                                if (append)
24727                                        dataflow.getRelationships().add(relationElement);
24728                        }
24729                }
24730        }
24731
24732        private void appendCallRelation(dataflow dataflow, Relationship[] relations) {
24733                for (int i = 0; i < relations.length; i++) {
24734                        AbstractRelationship relation = (AbstractRelationship) relations[i];
24735                        relationship relationElement = new relationship();
24736                        relationElement.setType(relation.getRelationshipType().name());
24737                        if (relation.getFunction() != null) {
24738                                relationElement.setFunction(relation.getFunction());
24739                        }
24740                        if (relation.getEffectType() != null) {
24741                                relationElement.setEffectType(relation.getEffectType().name());
24742                        }
24743                        relationElement.setSqlHash(relation.getSqlHash());
24744                        relationElement.setSqlComment(relation.getSqlComment());
24745                        
24746                        if (relation.getProcedureId() != null) {
24747                                relationElement.setProcedureId(String.valueOf(relation.getProcedureId()));
24748                        }
24749                        relationElement.setId(String.valueOf(relation.getId()));
24750
24751                        if (relation instanceof CallRelationship) {
24752                                CallRelationship callRelation = (CallRelationship) relation;
24753                                
24754                                if (callRelation.getCallObject() != null) {
24755                                        relationElement.setCallStmt(callRelation.getCallObject().toString());
24756                                        if (callRelation.getStartPosition() != null && callRelation.getEndPosition() != null) {
24757                                                relationElement.setCallCoordinate(convertCoordinate(callRelation.getStartPosition()) + ","
24758                                                                + convertCoordinate(callRelation.getEndPosition()));
24759                                        }
24760                                }
24761                                
24762                                if (Boolean.TRUE.equals(callRelation.getBuiltIn())) {
24763                                        relationElement.setBuiltIn(true);
24764                                }
24765                                Object targetElement = callRelation.getTarget().getElement();
24766                                if (targetElement instanceof Procedure) {
24767                                        Procedure procedure = (Procedure) targetElement;
24768                                        targetColumn target = new targetColumn();
24769                                        target.setId(String.valueOf(procedure.getId()));
24770                                        target.setName(getProcedureName(procedure));
24771                                        if (procedure.getStartPosition() != null && procedure.getEndPosition() != null) {
24772                                                target.setCoordinate(convertCoordinate(procedure.getStartPosition()) + ","
24773                                                                + convertCoordinate(procedure.getEndPosition()));
24774                                        }
24775                                        String clazz = procedure.getProcedureObject().getClass().getSimpleName().toLowerCase(Locale.ROOT);
24776                                        if (clazz.indexOf("function") != -1) {
24777                                                target.setType("function");
24778                                        } else if (clazz.indexOf("trigger") != -1) {
24779                                                target.setType("trigger");
24780                                        } else if (clazz.indexOf("macro") != -1) {
24781                                                target.setType("macro");
24782                                        } else {
24783                                                target.setType("procedure");
24784                                        }
24785                                        relationElement.setCaller(target);
24786                                } else if (targetElement instanceof Process) {
24787                                        Process process = (Process) targetElement;
24788                                        targetColumn target = new targetColumn();
24789                                        target.setId(String.valueOf(process.getId()));
24790                                        target.setName(getProcessName(process));
24791                                        target.setType("process");
24792                                        if (process.getStartPosition() != null && process.getEndPosition() != null) {
24793                                                target.setCoordinate(convertCoordinate(process.getStartPosition()) + ","
24794                                                                + convertCoordinate(process.getEndPosition()));
24795                                        }
24796                                        relationElement.setCaller(target);
24797                                } else {
24798                                        continue;
24799                                }
24800
24801                                Collection<RelationshipElement<?>> sourceElements = (Collection<RelationshipElement<?>>)callRelation.getSources();
24802                                if (sourceElements.size() == 0) {
24803                                        continue;
24804                                }
24805
24806                                boolean append = false;
24807                                for (RelationshipElement<?> sourceItem: sourceElements) {
24808                                        Object sourceElement = sourceItem.getElement();
24809                                        if (sourceElement instanceof Procedure) {
24810                                                Procedure procedure = (Procedure) sourceElement;
24811                                                sourceColumn source = new sourceColumn();
24812                                                source.setId(String.valueOf(procedure.getId()));
24813                                                source.setName(getProcedureName(procedure));
24814                                                if (procedure.getStartPosition() != null && procedure.getEndPosition() != null) {
24815                                                        source.setCoordinate(convertCoordinate(procedure.getStartPosition()) + ","
24816                                                                        + convertCoordinate(procedure.getEndPosition()));
24817                                                }
24818                                                String clazz = procedure.getProcedureObject().getClass().getSimpleName().toLowerCase(Locale.ROOT);
24819                                                if (clazz.indexOf("function") != -1) {
24820                                                        source.setType("function");
24821                                                } else if (clazz.indexOf("trigger") != -1) {
24822                                                        source.setType("trigger");
24823                                                } else if (clazz.indexOf("macro") != -1) {
24824                                                        source.setType("macro");
24825                                                } else {
24826                                                        source.setType("procedure");
24827                                                }
24828                                                append = true;
24829                                                relationElement.getCallees().add(source);
24830                                        } else if (sourceElement instanceof Function) {
24831                                                Function function = (Function) sourceElement;
24832                                                sourceColumn source = new sourceColumn();
24833                                                source.setId(String.valueOf(function.getId()));
24834                                                source.setName(function.getFunctionObject() instanceof TObjectName
24835                                                                ? function.getFunctionName() : getFunctionName(function.getFunctionObject()));
24836                                                if (function.getStartPosition() != null && function.getEndPosition() != null) {
24837                                                        source.setCoordinate(convertCoordinate(function.getStartPosition()) + ","
24838                                                                        + convertCoordinate(function.getEndPosition()));
24839                                                }
24840                                                source.setType("function");
24841                                                append = true;
24842                                                relationElement.getCallees().add(source);
24843                                        }
24844                                }
24845
24846                                if (append)
24847                                        dataflow.getRelationships().add(relationElement);
24848                        }
24849                }
24850        }
24851
24852        private void appendResultSets(dataflow dataflow) {
24853                Set<ResultSet> resultSets = modelManager.getResultSets(); 
24854                for (ResultSet resultSet: resultSets) {
24855                        appendResultSet(dataflow, resultSet);
24856                }
24857        }
24858
24859        private void appendResultSet(dataflow dataflow, ResultSet resultSetModel) {
24860                if (!appendResultSets.contains(resultSetModel)) {
24861                        appendResultSets.add(resultSetModel);
24862                } else {
24863                        return;
24864                }
24865
24866                table resultSetElement = new table();
24867                resultSetElement.setId(String.valueOf(resultSetModel.getId()));
24868                resultSetElement.setServer(resultSetModel.getServer());
24869                if (!SQLUtil.isEmpty(resultSetModel.getDatabase())) {
24870                        resultSetElement.setDatabase(resultSetModel.getDatabase());
24871                }
24872                if (!SQLUtil.isEmpty(resultSetModel.getSchema())) {
24873                        resultSetElement.setSchema(resultSetModel.getSchema());
24874                }
24875                resultSetElement.setName(getResultSetName(resultSetModel));
24876                resultSetElement.setType(getResultSetType(resultSetModel));
24877                if (resultSetElement.isFunction()) {
24878                        // the routine a call names, from its name as written, before normalized output renames it
24879                        resultSetElement.setRoutineKey(DataflowUtility.calledRoutineKey(resultSetElement.getName()));
24880                }
24881                // if ((ignoreRecordSet || simpleOutput) && resultSetModel.isTarget()) {
24882                resultSetElement.setIsTarget(String.valueOf(resultSetModel.isTarget()));
24883                // }
24884                resultSetElement.setIsDetermined(String.valueOf(resultSetModel.isDetermined()));
24885                if (resultSetModel.getProcedureId() != null) {
24886                        resultSetElement.setProcedureId(resultSetModel.getProcedureId());
24887                }
24888                if (resultSetModel.getStartPosition() != null && resultSetModel.getEndPosition() != null) {
24889                        resultSetElement.setCoordinate(convertCoordinate(resultSetModel.getStartPosition()) + ","
24890                                        + convertCoordinate(resultSetModel.getEndPosition()));
24891                }
24892                dataflow.getResultsets().add(resultSetElement);
24893
24894                // each column followed by the STRUCT field nodes below it (Mantis 4836)
24895                List<ResultColumn> columns = resultSetModel.getColumnsWithFieldPaths();
24896
24897                // Columns that publish the same name are qualified by their table.
24898                Map<Object, Integer> publishedCounts = new HashMap<Object, Integer>();
24899                for (ResultColumn column : columns) {
24900                        Object publishedKey = publishedColumnKey(column);
24901                        publishedCounts.put(publishedKey, publishedCounts.containsKey(publishedKey) ? publishedCounts.get(publishedKey) + 1 : 1);
24902                        // if (column.hasStarLinkColumn()) {
24903                        // List<String> starLinkColumns = column.getStarLinkColumnNames();
24904                        // for (int k = 0; k < starLinkColumns.size(); k++) {
24905                        // columnName = starLinkColumns.get(k);
24906                        // if (!columnCounts.containsKey(columnName)) {
24907                        // columnCounts.put(columnName, 0);
24908                        // }
24909                        // columnCounts.put(columnName, columnCounts.get(columnName) + 1);
24910                        // }
24911                        // }
24912                }
24913
24914                for (int j = 0; j < columns.size(); j++) {
24915                        ResultColumn columnModel = columns.get(j);
24916                        if (columnModel.hasStarLinkColumn()) {
24917                                // List<String> starLinkColumns =
24918                                // columnModel.getStarLinkColumnNames();
24919                                // for (int k = 0; k < starLinkColumns.size(); k++) {
24920                                // column columnElement = new column();
24921                                // columnElement.setId( String.valueOf(columnModel.getId()) +
24922                                // "_" + k);
24923                                // String columnName = starLinkColumns.get(k);
24924                                // columnElement.setName(columnName);
24925                                // if(columnModel.isFunction()){
24926                                // columnElement.setIsFunction(String.valueOf(columnModel.isFunction()));
24927                                // }
24928                                // if (columnModel.getStartPosition() != null &&
24929                                // columnModel.getEndPosition() != null) {
24930                                // columnElement.setCoordinate(
24931                                // columnModel.getStartPosition() + "," +
24932                                // columnModel.getEndPosition());
24933                                // }
24934                                // String identifier = columnName;
24935                                // if(columnCounts.containsKey(identifier) &&
24936                                // columnCounts.get(identifier)>1){
24937                                // TObjectName column =
24938                                // columnModel.getStarLinkColumns().get(columnName).iterator().next();
24939                                // if(!SQLUtil.isEmpty(getQualifiedTable(column))){
24940                                // columnElement.setQualifiedTable(getQualifiedTable(column));
24941                                // }
24942                                // }
24943                                // resultSetElement.getColumns().add(columnElement);
24944                                // }
24945                                if (columnModel.isShowStar()) {
24946                                        column columnElement = new column();
24947                                        columnElement.setId(String.valueOf(columnModel.getId()));
24948                                        columnElement.setName(columnModel.getName());
24949                                        DlineageUtil.publishStarProvenance(columnElement, columnModel);
24950                                        columnElement.setIdentifierKey(columnModel.getIdentifierKey());
24951                                        if (columnModel.isFunction()) {
24952                                                columnElement.setIsFunction(String.valueOf(columnModel.isFunction()));
24953                                        }
24954                                        if (columnModel.getStartPosition() != null && columnModel.getEndPosition() != null) {
24955                                                columnElement.setCoordinate(convertCoordinate(columnModel.getStartPosition()) + ","
24956                                                                + convertCoordinate(columnModel.getEndPosition()));
24957                                        }
24958
24959                                        if (publishedCounts.get(publishedColumnKey(columnModel)) > 1) {
24960                                                String qualifiedTable = getQualifiedTable(columnModel);
24961                                                if (!SQLUtil.isEmpty(qualifiedTable)) {
24962                                                        columnElement.setQualifiedTable(qualifiedTable);
24963                                                }
24964                                        }
24965                                        resultSetElement.getColumns().add(columnElement);
24966                                }
24967                        } else {
24968                                column columnElement = new column();
24969                                columnElement.setId(String.valueOf(columnModel.getId()));
24970                                columnElement.setName(columnModel.getName());
24971                                DlineageUtil.publishStarProvenance(columnElement, columnModel);
24972                                columnElement.setIdentifierKey(columnModel.getIdentifierKey());
24973                                if (columnModel.isFunction()) {
24974                                        columnElement.setIsFunction(String.valueOf(columnModel.isFunction()));
24975                                }
24976                                if (columnModel.getStartPosition() != null && columnModel.getEndPosition() != null) {
24977                                        columnElement.setCoordinate(convertCoordinate(columnModel.getStartPosition()) + ","
24978                                                        + convertCoordinate(columnModel.getEndPosition()));
24979                                }
24980
24981                                if (publishedCounts.get(publishedColumnKey(columnModel)) > 1) {
24982                                        String qualifiedTable = getQualifiedTable(columnModel);
24983                                        if (!SQLUtil.isEmpty(qualifiedTable)) {
24984                                                columnElement.setQualifiedTable(qualifiedTable);
24985                                        }
24986                                }
24987                                resultSetElement.getColumns().add(columnElement);
24988                        }
24989                }
24990
24991                ResultSetRelationRows relationRows = resultSetModel.getRelationRows();
24992                if (relationRows.hasRelation()) {
24993                        column relationRowsElement = new column();
24994                        relationRowsElement.setId(String.valueOf(relationRows.getId()));
24995                        relationRowsElement.setName(relationRows.getName());
24996                        if (relationRows.getStartPosition() != null && relationRows.getEndPosition() != null) {
24997                                relationRowsElement.setCoordinate(convertCoordinate(relationRows.getStartPosition()) + ","
24998                                                + convertCoordinate(relationRows.getEndPosition()));
24999                        }
25000                        relationRowsElement.setSource("system");
25001                        resultSetElement.getColumns().add(relationRowsElement);
25002                }
25003        }
25004
25005        private String getQualifiedTable(ResultColumn columnModel) {
25006                if (columnModel.getColumnObject() instanceof TObjectName) {
25007                        return getQualifiedTable((TObjectName) columnModel.getColumnObject());
25008                }
25009                if (columnModel.getColumnObject() instanceof TResultColumn) {
25010                        TObjectName field = ((TResultColumn) columnModel.getColumnObject()).getFieldAttr();
25011                        if (field != null) {
25012                                return getQualifiedTable(field);
25013                        }
25014                }
25015                return null;
25016        }
25017
25018        private String getQualifiedTable(TObjectName column) {
25019                if (column == null)
25020                        return null;
25021                String[] splits = column.toString().split("\\.");
25022                if (splits.length > 1) {
25023                        return splits[splits.length - 2];
25024                }
25025                return null;
25026        }
25027
25028        /**
25029         * Get the qualified prefix (schema.table) from a column name for 3-part names.
25030         * For example, for "sch.pk_constv2.c_cdsl", returns "sch.pk_constv2".
25031         * Returns null if the column doesn't have both schema and table parts.
25032         */
25033        private String getQualifiedPrefixFromColumn(TObjectName column) {
25034                if (column == null) return null;
25035
25036                // STRUCT tree: customer.address.city read as a field path, or that several relations could own (rule R3), is a
25037                // field read, not a column of a schema customer with a table address
25038                if (nestedProfile().buildsDeclaredTree()
25039                                && (isUndecidedFieldRead(column) || column.getFieldPathRead() != null)) {
25040                        return null;
25041                }
25042
25043                // Check if both schema and table tokens are present (3-part name)
25044                String schemaStr = column.getSchemaString();
25045                String tableStr = column.getTableString();
25046
25047                if (schemaStr != null && !schemaStr.isEmpty() &&
25048                        tableStr != null && !tableStr.isEmpty()) {
25049                        return schemaStr + "." + tableStr;
25050                }
25051
25052                // A field path has no qualifier to split off its text: the parts of payload.address.city before its last one
25053                // are a column and a field, not a schema payload with a table address (Mantis 4822, STRUCT tree plan).
25054                if (DlineageUtil.isFieldPath(column) && nestedProfile().buildsDeclaredTree()) {
25055                        return null;
25056                }
25057
25058                // Fallback: parse from toString() for complex cases
25059                String[] splits = column.toString().split("\\.");
25060                if (splits.length >= 3) {
25061                        // Return all parts except the last one (column name)
25062                        StringBuilder prefix = new StringBuilder();
25063                        for (int i = 0; i < splits.length - 1; i++) {
25064                                if (i > 0) prefix.append(".");
25065                                prefix.append(splits[i]);
25066                        }
25067                        return prefix.toString();
25068                }
25069
25070                return null;
25071        }
25072
25073        private String getResultSetType(ResultSet resultSetModel) {
25074                if (resultSetModel instanceof QueryTable) {
25075                        QueryTable table = (QueryTable) resultSetModel;
25076                        if (table.getTableObject().getCTE() != null) {
25077                                return "with_cte";
25078                        }
25079                }
25080
25081                if (resultSetModel instanceof SelectSetResultSet) {
25082                        ESetOperatorType type = ((SelectSetResultSet) resultSetModel).getSetOperatorType();
25083                        return "select_" + type.name();
25084                }
25085
25086                if (resultSetModel instanceof SelectResultSet) {
25087                        if (((SelectResultSet) resultSetModel).getSelectStmt().getParentStmt() instanceof TInsertSqlStatement) {
25088                                return "insert-select";
25089                        }
25090                        if (((SelectResultSet) resultSetModel).getSelectStmt().getParentStmt() instanceof TUpdateSqlStatement) {
25091                                return "update-select";
25092                        }
25093                }
25094
25095                if (resultSetModel.getGspObject() instanceof TMergeUpdateClause) {
25096                        return "merge-update";
25097                }
25098
25099                if (resultSetModel.getGspObject() instanceof TOutputClause) {
25100                        return ResultSetType.output.name();
25101                }
25102
25103                if (resultSetModel.getGspObject() instanceof TMergeInsertClause) {
25104                        return "merge-insert";
25105                }
25106
25107                if (resultSetModel.getGspObject() instanceof TUpdateSqlStatement) {
25108                        return "update-set";
25109                }
25110
25111                if (resultSetModel.getGspObject() instanceof TFunctionCall && ((TFunctionCall)resultSetModel.getGspObject()).getFunctionType() == EFunctionType.array_t) {
25112                        return ResultSetType.array.name();
25113                }
25114
25115                if (resultSetModel.getGspObject() instanceof TFunctionCall && ((TFunctionCall)resultSetModel.getGspObject()).getFunctionType() == EFunctionType.struct_t) {
25116                        return ResultSetType.struct.name();
25117                }
25118
25119                if (resultSetModel.getGspObject() instanceof TFunctionCall || resultSetModel instanceof Function) {
25120                        return ResultSetType.function.name();
25121                }
25122
25123                if (resultSetModel.getGspObject() instanceof TAliasClause) {
25124                        return ResultSetType.alias.name();
25125                }
25126
25127                if (resultSetModel.getGspObject() instanceof TCursorDeclStmt) {
25128                        return ResultSetType.cursor.name();
25129                }
25130
25131                if (resultSetModel instanceof PivotedTable) {
25132                        if (((PivotedTable) resultSetModel).isUnpivoted()) {
25133                                return ResultSetType.unpivot_table.name();
25134                        }
25135                        return ResultSetType.pivot_table.name();
25136                }
25137
25138                return "select_list";
25139        }
25140
25141        private String getTableName(Table tableModel) {
25142                if (modelManager.DISPLAY_NAME.containsKey(tableModel.getId())) {
25143                        return modelManager.DISPLAY_NAME.get(tableModel.getId());
25144                }
25145
25146                String tableName;
25147                if (tableModel.getFullName() != null && tableModel.getFullName().trim().length() > 0) {
25148                        return tableModel.getFullName();
25149                }
25150                if (tableModel.getAlias() != null && tableModel.getAlias().trim().length() > 0) {
25151                        tableName = getResultSetWithId("RESULT_OF_" + tableModel.getAlias());
25152
25153                } else {
25154                        tableName = getResultSetDisplayId("RS");
25155                }
25156                modelManager.DISPLAY_NAME.put(tableModel.getId(), tableName);
25157                return tableName;
25158        }
25159
25160        private String getProcedureName(Procedure procedureModel) {
25161                if (modelManager.DISPLAY_NAME.containsKey(procedureModel.getId())) {
25162                        return modelManager.DISPLAY_NAME.get(procedureModel.getId());
25163                }
25164
25165                String procedureName = procedureModel.getFullName();
25166
25167                modelManager.DISPLAY_NAME.put(procedureModel.getId(), procedureName);
25168                return procedureName;
25169        }
25170
25171        private String getProcessName(Process processModel) {
25172                if (modelManager.DISPLAY_NAME.containsKey(processModel.getId())) {
25173                        return modelManager.DISPLAY_NAME.get(processModel.getId());
25174                } else {
25175                        if (processModel.getCustomType() != null) {
25176                                String name = processModel.getCustomType();
25177                                modelManager.DISPLAY_NAME.put(processModel.getId(), name);
25178                                return name;
25179                        }
25180                        String name = processModel.getType();
25181                        String procedureName = getProcedureParentName(processModel.getGspObject());
25182                        if (procedureName != null) {
25183                                name = getResultSetDisplayId(procedureName + " " + name);
25184                        } else {
25185                                name = getResultSetDisplayId("Query " + name);
25186                        }
25187                        modelManager.DISPLAY_NAME.put(processModel.getId(), name);
25188                        return name;
25189                }
25190        }
25191
25192        private String getDisplayIdByType(String type) {
25193                if (!modelManager.DISPLAY_ID.containsKey(type)) {
25194                        modelManager.DISPLAY_ID.put(type, option.getStartId() + 1);
25195                        return type + "-" + (option.getStartId() + 1);
25196                } else {
25197                        long id = modelManager.DISPLAY_ID.get(type);
25198                        modelManager.DISPLAY_ID.put(type, id + 1);
25199                        return type + "-" + (id + 1);
25200                }
25201        }
25202        
25203        private String getDisplayIdByTypeFromZero(String type) {
25204                if (!modelManager.DISPLAY_ID.containsKey(type)) {
25205                        modelManager.DISPLAY_ID.put(type, option.getStartId());
25206                        if(option.getStartId() == 0) {
25207                                return type;
25208                        }
25209                        return type + "-" + (option.getStartId() + 1);
25210                } else {
25211                        long id = modelManager.DISPLAY_ID.get(type);
25212                        modelManager.DISPLAY_ID.put(type, id + 1);
25213                        return type + "-" + (id + 1);
25214                }
25215        }
25216
25217        private String getConstantName(Table tableModel) {
25218                if (modelManager.DISPLAY_NAME.containsKey(tableModel.getId())) {
25219                        return modelManager.DISPLAY_NAME.get(tableModel.getId());
25220                } else {
25221                        String name = getDisplayIdByType("SQL_CONSTANTS");
25222                        modelManager.DISPLAY_NAME.put(tableModel.getId(), name);
25223                        return name;
25224                }
25225        }
25226        
25227        private String getTempTableName(TTable table) {
25228                if (modelManager.DISPLAY_NAME.containsKey((long)System.identityHashCode(table))) {
25229                        return modelManager.DISPLAY_NAME.get((long)System.identityHashCode(table));
25230                } else {
25231                        String name = getDisplayIdByTypeFromZero(table.getName());
25232                        modelManager.DISPLAY_NAME.put((long)System.identityHashCode(table), name);
25233                        return name;
25234                }
25235        }
25236
25237        private String getResultSetName(ResultSet resultSetModel) {
25238
25239                if (modelManager.DISPLAY_NAME.containsKey(resultSetModel.getId())) {
25240                        return modelManager.DISPLAY_NAME.get(resultSetModel.getId());
25241                }
25242                
25243                if (resultSetModel.getGspObject() instanceof TFunctionCall && ((TFunctionCall)resultSetModel.getGspObject()).getFunctionType() == EFunctionType.array_t) {
25244                        String name = getResultSetDisplayId("ARRAY");
25245                        modelManager.DISPLAY_NAME.put(resultSetModel.getId(), name);
25246                        if(option.containsResultSetType(ResultSetType.array)) {
25247                                resultSetModel.setTarget(true);
25248                        }
25249                        return name;
25250                }
25251                
25252                if (resultSetModel.getGspObject() instanceof TFunctionCall && ((TFunctionCall)resultSetModel.getGspObject()).getFunctionType() == EFunctionType.struct_t) {
25253                        String name = getResultSetDisplayId("STRUCT");
25254                        modelManager.DISPLAY_NAME.put(resultSetModel.getId(), name);
25255                        if(option.containsResultSetType(ResultSetType.struct)) {
25256                                resultSetModel.setTarget(true);
25257                        }
25258                        return name;
25259                }
25260
25261                if (resultSetModel instanceof QueryTable) {
25262                        QueryTable table = (QueryTable) resultSetModel;
25263                        if (table.getAlias() != null && table.getAlias().trim().length() > 0) {
25264                                String name = getResultSetWithId("RESULT_OF_" + table.getAlias().trim());
25265                                if (table.getTableObject().getCTE() != null) {
25266                                        name = getResultSetWithId("RESULT_OF_" + table.getTableObject().getCTE().getTableName().toString()
25267                                                        + "_" + table.getAlias().trim());
25268                                }
25269                                modelManager.DISPLAY_NAME.put(resultSetModel.getId(), name);
25270                                if(option.containsResultSetType(ResultSetType.result_of)) {
25271                                        resultSetModel.setTarget(true);
25272                                }
25273                                return name;
25274                        } else if (table.getTableObject().getCTE() != null) {
25275                                String name = getResultSetWithId("CTE-" + table.getTableObject().getCTE().getTableName().toString());
25276                                modelManager.DISPLAY_NAME.put(table.getId(), name);
25277                                if(option.containsResultSetType(ResultSetType.cte)) {
25278                                        resultSetModel.setTarget(true);
25279                                }
25280                                return name;
25281                        }
25282                }
25283
25284                if (resultSetModel instanceof SelectResultSet) {
25285                        if (((SelectResultSet) resultSetModel).getSelectStmt().getParentStmt() instanceof TInsertSqlStatement) {
25286                                String name = getResultSetDisplayId("INSERT-SELECT");
25287                                modelManager.DISPLAY_NAME.put(resultSetModel.getId(), name);
25288                                if(option.containsResultSetType(ResultSetType.insert_select)) {
25289                                        resultSetModel.setTarget(true);
25290                                }
25291                                return name;
25292                        }
25293
25294                        if (((SelectResultSet) resultSetModel).getSelectStmt().getParentStmt() instanceof TUpdateSqlStatement) {
25295                                String name = getResultSetDisplayId("UPDATE-SELECT");
25296                                modelManager.DISPLAY_NAME.put(resultSetModel.getId(), name);
25297                                if(option.containsResultSetType(ResultSetType.update_select)) {
25298                                        resultSetModel.setTarget(true);
25299                                }
25300                                return name;
25301                        }
25302                }
25303
25304                if (resultSetModel instanceof SelectSetResultSet) {
25305                        ESetOperatorType type = ((SelectSetResultSet) resultSetModel).getSetOperatorType();
25306                        String name = getResultSetDisplayId("RESULT_OF_" + type.name().toUpperCase(Locale.ROOT));
25307                        modelManager.DISPLAY_NAME.put(resultSetModel.getId(), name);
25308                        if(option.containsResultSetType(ResultSetType.result_of)) {
25309                                resultSetModel.setTarget(true);
25310                        }
25311                        return name;
25312                }
25313
25314                if (resultSetModel.getGspObject() instanceof TMergeUpdateClause) {
25315                        String name = getResultSetDisplayId("MERGE-UPDATE");
25316                        modelManager.DISPLAY_NAME.put(resultSetModel.getId(), name);
25317                        if(option.containsResultSetType(ResultSetType.merge_update)) {
25318                                resultSetModel.setTarget(true);
25319                        }
25320                        return name;
25321                }
25322
25323                if (resultSetModel.getGspObject() instanceof TOutputClause) {
25324                        String name = getResultSetDisplayId("OUTPUT");
25325                        modelManager.DISPLAY_NAME.put(resultSetModel.getId(), name);
25326                        if(option.containsResultSetType(ResultSetType.output)) {
25327                                resultSetModel.setTarget(true);
25328                        }
25329                        return name;
25330                }
25331
25332                if (resultSetModel.getGspObject() instanceof TMergeInsertClause) {
25333                        String name = getResultSetDisplayId("MERGE-INSERT");
25334                        modelManager.DISPLAY_NAME.put(resultSetModel.getId(), name);
25335                        if(option.containsResultSetType(ResultSetType.merge_insert)) {
25336                                resultSetModel.setTarget(true);
25337                        }
25338                        return name;
25339                }
25340
25341                if (resultSetModel.getGspObject() instanceof TUpdateSqlStatement) {
25342                        String name = getResultSetDisplayId("UPDATE-SET");
25343                        modelManager.DISPLAY_NAME.put(resultSetModel.getId(), name);
25344                        if(option.containsResultSetType(ResultSetType.update_set)) {
25345                                resultSetModel.setTarget(true);
25346                        }
25347                        return name;
25348                }
25349
25350                if (resultSetModel.getGspObject() instanceof TCaseExpression) {
25351                        String name = ((Function) resultSetModel).getFunctionName();
25352                        modelManager.DISPLAY_NAME.put(resultSetModel.getId(), name);
25353                        if (option.containsResultSetType(ResultSetType.case_when) || option.containsResultSetType(ResultSetType.function)) {
25354                                resultSetModel.setTarget(true);
25355                        }
25356                        return name;
25357                }
25358                
25359                if (resultSetModel.getGspObject() instanceof TFunctionCall || resultSetModel instanceof Function) {
25360                        String name = ((Function) resultSetModel).getFunctionName();
25361                        modelManager.DISPLAY_NAME.put(resultSetModel.getId(), name);
25362                        if(option.containsResultSetType(ResultSetType.function)) {
25363                                resultSetModel.setTarget(true);
25364                        }
25365                        return name;
25366                }
25367
25368                if (resultSetModel instanceof PivotedTable) {
25369                        String name = getResultSetDisplayId("PIVOT-TABLE");
25370                        if (((PivotedTable) resultSetModel).isUnpivoted()) {
25371                                name = getResultSetDisplayId("UNPIVOT-TABLE");
25372                                if(option.containsResultSetType(ResultSetType.unpivot_table)) {
25373                                        resultSetModel.setTarget(true);
25374                                }
25375                        }
25376                        else {
25377                                if(option.containsResultSetType(ResultSetType.pivot_table)) {
25378                                        resultSetModel.setTarget(true);
25379                                }
25380                        }
25381                        modelManager.DISPLAY_NAME.put(resultSetModel.getId(), name);
25382                        return name;
25383                }
25384
25385                if (resultSetModel instanceof Alias) {
25386                        String name = getResultSetDisplayId("ALIAS");
25387                        modelManager.DISPLAY_NAME.put(resultSetModel.getId(), name);
25388                        if(option.containsResultSetType(ResultSetType.alias)) {
25389                                resultSetModel.setTarget(true);
25390                        }
25391                        return name;
25392                }
25393
25394                String name = getResultSetDisplayId("RS");
25395                modelManager.DISPLAY_NAME.put(resultSetModel.getId(), name);
25396                if(option.containsResultSetType(ResultSetType.select_list)) {
25397                        resultSetModel.setTarget(true);
25398                }
25399                return name;
25400        }
25401
25402        private String getResultSetWithId(String type) {
25403                // A display label (RESULT_OF_<alias>, CTE-<name>): its published form counts the ids of one label family.
25404                type = LegacyDlineageWireNamesV1.tableName(type);
25405                if (!modelManager.DISPLAY_ID.containsKey(type)) {
25406                        modelManager.DISPLAY_ID.put(type, option.getStartId() + 1);
25407                        return type + "-" + (option.getStartId() + 1);
25408                } else {
25409                        long id = modelManager.DISPLAY_ID.get(type);
25410                        modelManager.DISPLAY_ID.put(type, id + 1);
25411                        return type + "-" + (id + 1);
25412                }
25413        }
25414
25415        private String getResultSetDisplayId(String type) {
25416                if (!modelManager.DISPLAY_ID.containsKey(type)) {
25417                        modelManager.DISPLAY_ID.put(type, option.getStartId() + 1);
25418                        return type + "-" + (option.getStartId() + 1);
25419                } else {
25420                        long id = modelManager.DISPLAY_ID.get(type);
25421                        modelManager.DISPLAY_ID.put(type, id + 1);
25422                        return type + "-" + (id + 1);
25423                }
25424        }
25425
25426        private void appendViews(dataflow dataflow) {
25427                List<TCustomSqlStatement> views = modelManager.getViews();
25428                for (int i = 0; i < views.size(); i++) {
25429                        Table viewModel = (Table) modelManager.getViewModel(views.get(i));
25430                        if (!tableIds.contains(viewModel.getId())) {
25431                                appendViewModel(dataflow, viewModel);
25432                                tableIds.add(viewModel.getId());
25433                        }
25434                }
25435
25436                List<TTable> tables = modelManager.getBaseTables();
25437                for (int i = 0; i < tables.size(); i++) {
25438                        Object model = modelManager.getModel(tables.get(i));
25439                        if (model instanceof Table) {
25440                                Table tableModel = (Table) model;
25441                                if (tableModel.isView()) {
25442                                        if (!tableIds.contains(tableModel.getId())) {
25443                                                appendViewModel(dataflow, tableModel);
25444                                                tableIds.add(tableModel.getId());
25445                                        }
25446                                }
25447                        }
25448                }
25449
25450                List<Table> tableNames = modelManager.getTablesByName();
25451                for (int i = 0; i < tableNames.size(); i++) {
25452                        Table tableModel = tableNames.get(i);
25453                        if (tableModel.isView()) {
25454                                if (!tableIds.contains(tableModel.getId())) {
25455                                        appendViewModel(dataflow, tableModel);
25456                                        tableIds.add(tableModel.getId());
25457                                }
25458                        }
25459                }
25460        }
25461
25462        private void appendViewModel(dataflow dataflow, Table viewModel) {
25463                table viewElement = new table();
25464                viewElement.setId(String.valueOf(viewModel.getId()));
25465                if (!SQLUtil.isEmpty(viewModel.getDatabase())) {
25466                        viewElement.setDatabase(viewModel.getDatabase());
25467                }
25468                if (!SQLUtil.isEmpty(viewModel.getSchema())) {
25469                        viewElement.setSchema(viewModel.getSchema());
25470                }
25471                viewElement.setServer(viewModel.getServer());
25472                viewElement.setName(viewModel.getName());
25473                viewElement.setIdentifierKey(viewModel.getIdentifierKey());
25474                viewElement.setMergeIdentity(viewModel.getCanonicalIdentity());
25475                viewElement.setType("view");
25476                // Propagate the view sub type (e.g. temp_table for CREATE TEMPORARY VIEW)
25477                // so consumers can tell a temporary view apart from a regular one. Mantis 4538.
25478                if (viewModel.getSubType() != null) {
25479                        viewElement.setSubType(viewModel.getSubType().name());
25480                }
25481                viewElement.setStarStmt(viewModel.getStarStmt());
25482
25483                if(viewModel.isFromDDL()){
25484                        viewElement.setFromDDL(String.valueOf(viewModel.isFromDDL()));
25485                }
25486
25487        if(option.isTraceTablePosition()){
25488            for (Pair<Pair3<Long, Long, String>, Pair3<Long, Long, String>> position:viewModel.getPositions()){
25489                viewElement.setCoordinate(convertCoordinate(position.first)+","+convertCoordinate(position.second));
25490            }
25491        }
25492        else {
25493            viewElement.setCoordinate(convertCoordinate(viewModel.getStartPosition()) + ","
25494                    + convertCoordinate(viewModel.getEndPosition()));
25495        }
25496
25497                if (viewModel.getProcesses() != null) {
25498                        List<String> processIds = new ArrayList<String>();
25499                        for (Process process : viewModel.getProcesses()) {
25500                                processIds.add(String.valueOf(process.getId()));
25501                        }
25502                        viewElement.setProcessIds(processIds);
25503                }
25504                dataflow.getViews().add(viewElement);
25505
25506                List<TableColumn> columns = viewModel.getColumns();
25507
25508                if (containStarColumn(columns)) {
25509                        for (TableColumn column : columns) {
25510                                if (column.isStarColumn()) {
25511                                        for (TableColumn starElement : columns) {
25512                                                if (starElement == column) {
25513                                                        continue;
25514                                                }
25515                                                TObjectName columnObject = starElement.getColumnObject();
25516                                                column.bindStarLinkColumn(columnObject);
25517                                        }
25518                                        if (viewModel.isCreateTable()) {
25519                                                column.setShowStar(false);
25520                                        }
25521                                }
25522                        }
25523                }
25524
25525                // each column followed by the STRUCT field path nodes below it, as for a table (Mantis 4834)
25526                List<TableColumn> publishedColumns = viewModel.getColumnsWithFieldPaths();
25527                for (int j = 0; j < publishedColumns.size(); j++) {
25528                        TableColumn columnModel = (TableColumn) publishedColumns.get(j);
25529                        if (!columnModel.isPseduo() && columnModel.hasStarLinkColumn()) {
25530                                List<String> starLinkColumnList = columnModel.getStarLinkColumnNames();
25531                                for (int k = 0; k < starLinkColumnList.size(); k++) {
25532                                        column columnElement = new column();
25533                                        columnElement.setId(String.valueOf(columnModel.getId()) + "_" + k);
25534                                        columnElement.setIdentifierKey(columnModel.starLinkIdentifier(k));
25535                                        columnElement.setSqlSpelling(columnModel.starLinkSqlSpelling(k));
25536                                        String columnName = starLinkColumnList.get(k);
25537                                        if (containStarColumn(columns, columnModel, k)) {
25538                                                continue;
25539                                        }
25540                                        columnElement.setName(columnName);
25541                                        if (columnModel.getStartPosition() != null && columnModel.getEndPosition() != null) {
25542                                                columnElement.setCoordinate(convertCoordinate(columnModel.getStartPosition()) + ","
25543                                                                + convertCoordinate(columnModel.getEndPosition()));
25544                                        }
25545                                        viewElement.getColumns().add(columnElement);
25546                                }
25547
25548                                if (columnModel.isShowStar()) {
25549                                        column columnElement = new column();
25550                                        columnElement.setId(String.valueOf(columnModel.getId()));
25551                                        columnElement.setName(columnModel.getName());
25552                                        DlineageUtil.publishStarProvenance(columnElement, columnModel);
25553                                        columnElement.setIdentifierKey(columnModel.getIdentifierKey());
25554                                        columnElement.setFieldPathKey(fieldPathMergeKey(columnModel));
25555                                        if (columnModel.getStartPosition() != null && columnModel.getEndPosition() != null) {
25556                                                columnElement.setCoordinate(convertCoordinate(columnModel.getStartPosition()) + ","
25557                                                                + convertCoordinate(columnModel.getEndPosition()));
25558                                        }
25559                                        viewElement.getColumns().add(columnElement);
25560                                }
25561
25562                        } else {
25563                                column columnElement = new column();
25564                                columnElement.setId(String.valueOf(columnModel.getId()));
25565                                columnElement.setName(columnModel.getName());
25566                                DlineageUtil.publishStarProvenance(columnElement, columnModel);
25567                                columnElement.setIdentifierKey(columnModel.getIdentifierKey());
25568                                columnElement.setFieldPathKey(fieldPathMergeKey(columnModel));
25569                                if (columnModel.getStartPosition() != null && columnModel.getEndPosition() != null) {
25570                                        columnElement.setCoordinate(convertCoordinate(columnModel.getStartPosition()) + ","
25571                                                        + convertCoordinate(columnModel.getEndPosition()));
25572                                }
25573                                if(columnModel.isPseduo()) {
25574                                        columnElement.setSource("system");
25575                                }
25576                                viewElement.getColumns().add(columnElement);
25577                        }
25578                }
25579
25580                TableRelationRows relationRows = viewModel.getRelationRows();
25581                if (relationRows.hasRelation()) {
25582                        column relationRowsElement = new column();
25583                        relationRowsElement.setId(String.valueOf(relationRows.getId()));
25584                        relationRowsElement.setName(relationRows.getName());
25585                        if (relationRows.getStartPosition() != null && relationRows.getEndPosition() != null) {
25586                                relationRowsElement.setCoordinate(convertCoordinate(relationRows.getStartPosition()) + ","
25587                                                + convertCoordinate(relationRows.getEndPosition()));
25588                        }
25589                        relationRowsElement.setSource("system");
25590                        viewElement.getColumns().add(relationRowsElement);
25591                }
25592        }
25593
25594        private void appendStreamModel(dataflow dataflow, Table streamModel) {
25595                table streamElement = new table();
25596                streamElement.setId(String.valueOf(streamModel.getId()));
25597                if (!SQLUtil.isEmpty(streamModel.getDatabase())) {
25598                        streamElement.setDatabase(streamModel.getDatabase());
25599                }
25600                if (!SQLUtil.isEmpty(streamModel.getSchema())) {
25601                        streamElement.setSchema(streamModel.getSchema());
25602                }
25603                streamElement.setServer(streamModel.getServer());
25604                streamElement.setName(streamModel.getName());
25605                streamElement.setType("stream");
25606                if (streamModel.getFileType() != null) {
25607                        streamElement.setFileType(SQLUtil.trimColumnStringQuote(streamModel.getFileType()));
25608                }
25609
25610                if (streamModel.getStartPosition() != null && streamModel.getEndPosition() != null) {
25611                        streamElement.setCoordinate(convertCoordinate(streamModel.getStartPosition()) + ","
25612                                        + convertCoordinate(streamModel.getEndPosition()));
25613                }
25614
25615                if (streamModel.getProcesses() != null) {
25616                        List<String> processIds = new ArrayList<String>();
25617                        for (Process process : streamModel.getProcesses()) {
25618                                processIds.add(String.valueOf(process.getId()));
25619                        }
25620                        streamElement.setProcessIds(processIds);
25621                }
25622                dataflow.getStreams().add(streamElement);
25623
25624                List<TableColumn> columns = streamModel.getColumns();
25625
25626                for (int j = 0; j < columns.size(); j++) {
25627                        TableColumn columnModel = (TableColumn) columns.get(j);
25628                        column columnElement = new column();
25629                        columnElement.setId(String.valueOf(columnModel.getId()));
25630                        columnElement.setName(SQLUtil.trimColumnStringQuote(columnModel.getName()));
25631                        DlineageUtil.publishStarProvenance(columnElement, columnModel);
25632                        columnElement.setCoordinate(convertCoordinate(columnModel.getStartPosition()) + ","
25633                                        + convertCoordinate(columnModel.getEndPosition()));
25634                        streamElement.getColumns().add(columnElement);
25635                }
25636
25637                TableRelationRows relationRows = streamModel.getRelationRows();
25638                if (relationRows.hasRelation()) {
25639                        column relationRowsElement = new column();
25640                        relationRowsElement.setId(String.valueOf(relationRows.getId()));
25641                        relationRowsElement.setName(relationRows.getName());
25642                        if (relationRows.getStartPosition() != null && relationRows.getEndPosition() != null) {
25643                                relationRowsElement.setCoordinate(convertCoordinate(relationRows.getStartPosition()) + ","
25644                                                + convertCoordinate(relationRows.getEndPosition()));
25645                        }
25646                        relationRowsElement.setSource("system");
25647                        streamElement.getColumns().add(relationRowsElement);
25648                }
25649        }
25650
25651        private void appendStageModel(dataflow dataflow, Table stageModel) {
25652                table stageElement = new table();
25653                stageElement.setId(String.valueOf(stageModel.getId()));
25654                if (!SQLUtil.isEmpty(stageModel.getDatabase())) {
25655                        stageElement.setDatabase(stageModel.getDatabase());
25656                }
25657                if (!SQLUtil.isEmpty(stageModel.getSchema())) {
25658                        stageElement.setSchema(stageModel.getSchema());
25659                }
25660                stageElement.setServer(stageModel.getServer());
25661                stageElement.setName(stageModel.getName());
25662                stageElement.setType("stage");
25663                stageElement.setLocation(stageModel.getLocation());
25664                if (stageModel.getFileType() != null) {
25665                        stageElement.setFileType(SQLUtil.trimColumnStringQuote(stageModel.getFileType()));
25666                }
25667
25668                if (stageModel.getStartPosition() != null && stageModel.getEndPosition() != null) {
25669                        stageElement.setCoordinate(convertCoordinate(stageModel.getStartPosition()) + ","
25670                                        + convertCoordinate(stageModel.getEndPosition()));
25671                }
25672
25673                if (stageModel.getProcesses() != null) {
25674                        List<String> processIds = new ArrayList<String>();
25675                        for (Process process : stageModel.getProcesses()) {
25676                                processIds.add(String.valueOf(process.getId()));
25677                        }
25678                        stageElement.setProcessIds(processIds);
25679                }
25680                dataflow.getStages().add(stageElement);
25681
25682                List<TableColumn> columns = stageModel.getColumns();
25683
25684                for (int j = 0; j < columns.size(); j++) {
25685                        TableColumn columnModel = (TableColumn) columns.get(j);
25686                        column columnElement = new column();
25687                        columnElement.setId(String.valueOf(columnModel.getId()));
25688                        columnElement.setName(SQLUtil.trimColumnStringQuote(columnModel.getName()));
25689                        DlineageUtil.publishStarProvenance(columnElement, columnModel);
25690                        columnElement.setCoordinate(convertCoordinate(columnModel.getStartPosition()) + ","
25691                                        + convertCoordinate(columnModel.getEndPosition()));
25692                        stageElement.getColumns().add(columnElement);
25693                }
25694
25695                TableRelationRows relationRows = stageModel.getRelationRows();
25696                if (relationRows.hasRelation()) {
25697                        column relationRowsElement = new column();
25698                        relationRowsElement.setId(String.valueOf(relationRows.getId()));
25699                        relationRowsElement.setName(relationRows.getName());
25700                        if (relationRows.getStartPosition() != null && relationRows.getEndPosition() != null) {
25701                                relationRowsElement.setCoordinate(convertCoordinate(relationRows.getStartPosition()) + ","
25702                                                + convertCoordinate(relationRows.getEndPosition()));
25703                        }
25704                        relationRowsElement.setSource("system");
25705                        stageElement.getColumns().add(relationRowsElement);
25706                }
25707        }
25708
25709        private void appendSequenceModel(dataflow dataflow, Table sequenceModel) {
25710                table sequenceElement = new table();
25711                sequenceElement.setId(String.valueOf(sequenceModel.getId()));
25712                if (!SQLUtil.isEmpty(sequenceModel.getDatabase())) {
25713                        sequenceElement.setDatabase(sequenceModel.getDatabase());
25714                }
25715                if (!SQLUtil.isEmpty(sequenceModel.getSchema())) {
25716                        sequenceElement.setSchema(sequenceModel.getSchema());
25717                }
25718                sequenceElement.setServer(sequenceModel.getServer());
25719                sequenceElement.setName(sequenceModel.getName());
25720                sequenceElement.setType("sequence");
25721                sequenceElement.setLocation(sequenceModel.getLocation());
25722                if (sequenceModel.getFileType() != null) {
25723                        sequenceElement.setFileType(SQLUtil.trimColumnStringQuote(sequenceModel.getFileType()));
25724                }
25725
25726                if (sequenceModel.getStartPosition() != null && sequenceModel.getEndPosition() != null) {
25727                        sequenceElement.setCoordinate(convertCoordinate(sequenceModel.getStartPosition()) + ","
25728                                        + convertCoordinate(sequenceModel.getEndPosition()));
25729                }
25730
25731                if (sequenceModel.getProcesses() != null) {
25732                        List<String> processIds = new ArrayList<String>();
25733                        for (Process process : sequenceModel.getProcesses()) {
25734                                processIds.add(String.valueOf(process.getId()));
25735                        }
25736                        sequenceElement.setProcessIds(processIds);
25737                }
25738                dataflow.getSequences().add(sequenceElement);
25739
25740                List<TableColumn> columns = sequenceModel.getColumns();
25741
25742                for (int j = 0; j < columns.size(); j++) {
25743                        TableColumn columnModel = (TableColumn) columns.get(j);
25744                        column columnElement = new column();
25745                        columnElement.setId(String.valueOf(columnModel.getId()));
25746                        columnElement.setName(SQLUtil.trimColumnStringQuote(columnModel.getName()));
25747                        DlineageUtil.publishStarProvenance(columnElement, columnModel);
25748                        columnElement.setCoordinate(convertCoordinate(columnModel.getStartPosition()) + ","
25749                                        + convertCoordinate(columnModel.getEndPosition()));
25750                        sequenceElement.getColumns().add(columnElement);
25751                }
25752
25753                TableRelationRows relationRows = sequenceModel.getRelationRows();
25754                if (relationRows.hasRelation()) {
25755                        column relationRowsElement = new column();
25756                        relationRowsElement.setId(String.valueOf(relationRows.getId()));
25757                        relationRowsElement.setName(relationRows.getName());
25758                        if (relationRows.getStartPosition() != null && relationRows.getEndPosition() != null) {
25759                                relationRowsElement.setCoordinate(convertCoordinate(relationRows.getStartPosition()) + ","
25760                                                + convertCoordinate(relationRows.getEndPosition()));
25761                        }
25762                        relationRowsElement.setSource("system");
25763                        sequenceElement.getColumns().add(relationRowsElement);
25764                }
25765        }
25766
25767        private void appendDataSourceModel(dataflow dataflow, Table datasourceModel) {
25768                table datasourceElement = new table();
25769                datasourceElement.setId(String.valueOf(datasourceModel.getId()));
25770                if (!SQLUtil.isEmpty(datasourceModel.getDatabase())) {
25771                        datasourceElement.setDatabase(datasourceModel.getDatabase());
25772                }
25773                if (!SQLUtil.isEmpty(datasourceModel.getSchema())) {
25774                        datasourceElement.setSchema(datasourceModel.getSchema());
25775                }
25776                datasourceElement.setServer(datasourceModel.getServer());
25777                datasourceElement.setName(datasourceModel.getName());
25778                datasourceElement.setType("datasource");
25779                datasourceElement.setLocation(datasourceModel.getLocation());
25780                if (datasourceModel.getFileType() != null) {
25781                        datasourceElement.setFileType(SQLUtil.trimColumnStringQuote(datasourceModel.getFileType()));
25782                }
25783
25784                if (datasourceModel.getStartPosition() != null && datasourceModel.getEndPosition() != null) {
25785                        datasourceElement.setCoordinate(convertCoordinate(datasourceModel.getStartPosition()) + ","
25786                                        + convertCoordinate(datasourceModel.getEndPosition()));
25787                }
25788
25789                if (datasourceModel.getProcesses() != null) {
25790                        List<String> processIds = new ArrayList<String>();
25791                        for (Process process : datasourceModel.getProcesses()) {
25792                                processIds.add(String.valueOf(process.getId()));
25793                        }
25794                        datasourceElement.setProcessIds(processIds);
25795                }
25796                dataflow.getDatasources().add(datasourceElement);
25797
25798                List<TableColumn> columns = datasourceModel.getColumns();
25799
25800                for (int j = 0; j < columns.size(); j++) {
25801                        TableColumn columnModel = (TableColumn) columns.get(j);
25802                        column columnElement = new column();
25803                        columnElement.setId(String.valueOf(columnModel.getId()));
25804                        columnElement.setName(SQLUtil.trimColumnStringQuote(columnModel.getName()));
25805                        DlineageUtil.publishStarProvenance(columnElement, columnModel);
25806                        columnElement.setCoordinate(convertCoordinate(columnModel.getStartPosition()) + ","
25807                                        + convertCoordinate(columnModel.getEndPosition()));
25808                        datasourceElement.getColumns().add(columnElement);
25809                }
25810
25811                TableRelationRows relationRows = datasourceModel.getRelationRows();
25812                if (relationRows.hasRelation()) {
25813                        column relationRowsElement = new column();
25814                        relationRowsElement.setId(String.valueOf(relationRows.getId()));
25815                        relationRowsElement.setName(relationRows.getName());
25816                        if (relationRows.getStartPosition() != null && relationRows.getEndPosition() != null) {
25817                                relationRowsElement.setCoordinate(convertCoordinate(relationRows.getStartPosition()) + ","
25818                                                + convertCoordinate(relationRows.getEndPosition()));
25819                        }
25820                        relationRowsElement.setSource("system");
25821                        datasourceElement.getColumns().add(relationRowsElement);
25822                }
25823        }
25824
25825        private void appendDatabaseModel(dataflow dataflow, Table databaseModel) {
25826                table databaseElement = new table();
25827                databaseElement.setId(String.valueOf(databaseModel.getId()));
25828                if (!SQLUtil.isEmpty(databaseModel.getDatabase())) {
25829                        databaseElement.setDatabase(databaseModel.getDatabase());
25830                }
25831                if (!SQLUtil.isEmpty(databaseModel.getSchema())) {
25832                        databaseElement.setSchema(databaseModel.getSchema());
25833                }
25834                databaseElement.setServer(databaseModel.getServer());
25835                databaseElement.setName(databaseModel.getName());
25836                databaseElement.setType("database");
25837                if (databaseModel.getFileType() != null) {
25838                        databaseElement.setFileType(SQLUtil.trimColumnStringQuote(databaseModel.getFileType()));
25839                }
25840
25841                if (databaseModel.getStartPosition() != null && databaseModel.getEndPosition() != null) {
25842                        databaseElement.setCoordinate(convertCoordinate(databaseModel.getStartPosition()) + ","
25843                                        + convertCoordinate(databaseModel.getEndPosition()));
25844                }
25845
25846                if (databaseModel.getProcesses() != null) {
25847                        List<String> processIds = new ArrayList<String>();
25848                        for (Process process : databaseModel.getProcesses()) {
25849                                processIds.add(String.valueOf(process.getId()));
25850                        }
25851                        databaseElement.setProcessIds(processIds);
25852                }
25853                dataflow.getDatabases().add(databaseElement);
25854
25855                List<TableColumn> columns = databaseModel.getColumns();
25856
25857                for (int j = 0; j < columns.size(); j++) {
25858                        TableColumn columnModel = (TableColumn) columns.get(j);
25859                        column columnElement = new column();
25860                        columnElement.setId(String.valueOf(columnModel.getId()));
25861                        columnElement.setName(SQLUtil.trimColumnStringQuote(columnModel.getName()));
25862                        DlineageUtil.publishStarProvenance(columnElement, columnModel);
25863                        columnElement.setCoordinate(convertCoordinate(columnModel.getStartPosition()) + ","
25864                                        + convertCoordinate(columnModel.getEndPosition()));
25865                        databaseElement.getColumns().add(columnElement);
25866                }
25867
25868                TableRelationRows relationRows = databaseModel.getRelationRows();
25869                if (relationRows.hasRelation()) {
25870                        column relationRowsElement = new column();
25871                        relationRowsElement.setId(String.valueOf(relationRows.getId()));
25872                        relationRowsElement.setName(relationRows.getName());
25873                        if (relationRows.getStartPosition() != null && relationRows.getEndPosition() != null) {
25874                                relationRowsElement.setCoordinate(convertCoordinate(relationRows.getStartPosition()) + ","
25875                                                + convertCoordinate(relationRows.getEndPosition()));
25876                        }
25877                        relationRowsElement.setSource("system");
25878                        databaseElement.getColumns().add(relationRowsElement);
25879                }
25880        }
25881
25882        private void appendSchemaModel(dataflow dataflow, Table schemaModel) {
25883                table schemaElement = new table();
25884                schemaElement.setId(String.valueOf(schemaModel.getId()));
25885                if (!SQLUtil.isEmpty(schemaModel.getDatabase())) {
25886                        schemaElement.setDatabase(schemaModel.getDatabase());
25887                }
25888                if (!SQLUtil.isEmpty(schemaModel.getSchema())) {
25889                        schemaElement.setSchema(schemaModel.getSchema());
25890                }
25891                schemaElement.setServer(schemaModel.getServer());
25892                schemaElement.setName(schemaModel.getName());
25893                schemaElement.setType("schema");
25894                if (schemaModel.getFileType() != null) {
25895                        schemaElement.setFileType(SQLUtil.trimColumnStringQuote(schemaModel.getFileType()));
25896                }
25897
25898                if (schemaModel.getStartPosition() != null && schemaModel.getEndPosition() != null) {
25899                        schemaElement.setCoordinate(convertCoordinate(schemaModel.getStartPosition()) + ","
25900                                        + convertCoordinate(schemaModel.getEndPosition()));
25901                }
25902
25903                if (schemaModel.getProcesses() != null) {
25904                        List<String> processIds = new ArrayList<String>();
25905                        for (Process process : schemaModel.getProcesses()) {
25906                                processIds.add(String.valueOf(process.getId()));
25907                        }
25908                        schemaElement.setProcessIds(processIds);
25909                }
25910                dataflow.getSchemas().add(schemaElement);
25911
25912                List<TableColumn> columns = schemaModel.getColumns();
25913
25914                for (int j = 0; j < columns.size(); j++) {
25915                        TableColumn columnModel = (TableColumn) columns.get(j);
25916                        column columnElement = new column();
25917                        columnElement.setId(String.valueOf(columnModel.getId()));
25918                        columnElement.setName(SQLUtil.trimColumnStringQuote(columnModel.getName()));
25919                        DlineageUtil.publishStarProvenance(columnElement, columnModel);
25920                        columnElement.setCoordinate(convertCoordinate(columnModel.getStartPosition()) + ","
25921                                        + convertCoordinate(columnModel.getEndPosition()));
25922                        schemaElement.getColumns().add(columnElement);
25923                }
25924
25925                TableRelationRows relationRows = schemaModel.getRelationRows();
25926                if (relationRows.hasRelation()) {
25927                        column relationRowsElement = new column();
25928                        relationRowsElement.setId(String.valueOf(relationRows.getId()));
25929                        relationRowsElement.setName(relationRows.getName());
25930                        if (relationRows.getStartPosition() != null && relationRows.getEndPosition() != null) {
25931                                relationRowsElement.setCoordinate(convertCoordinate(relationRows.getStartPosition()) + ","
25932                                                + convertCoordinate(relationRows.getEndPosition()));
25933                        }
25934                        relationRowsElement.setSource("system");
25935                        schemaElement.getColumns().add(relationRowsElement);
25936                }
25937        }
25938
25939        /**
25940         * Plan section 4.7: the identity an output merge keys a STRUCT field path node by, its declared column (by the
25941         * column's identifier key, else its legacy display bucket) and its path step by step; null for any other column.
25942         */
25943        private static Object fieldPathMergeKey(TableColumn column) {
25944                if (column == null || !column.isFieldPathNode() || column.getDeclaredColumn() == null) {
25945                        return null;
25946                }
25947                TableColumn declared = column.getDeclaredColumn();
25948                Object declaredKey = declared.getIdentifierKey() != null ? declared.getIdentifierKey()
25949                                : LegacyDlineageWireNamesV1.columnBucket(declared.getName());
25950                return java.util.Arrays.asList("field path", declaredKey, FieldNodeKey.of(column).getSteps());
25951        }
25952
25953        private void appendPathModel(dataflow dataflow, Table pathModel) {
25954                table pathElement = new table();
25955                pathElement.setId(String.valueOf(pathModel.getId()));
25956                if (!SQLUtil.isEmpty(pathModel.getDatabase())) {
25957                        pathElement.setDatabase(pathModel.getDatabase());
25958                }
25959                if (!SQLUtil.isEmpty(pathModel.getSchema())) {
25960                        pathElement.setSchema(pathModel.getSchema());
25961                }
25962                pathElement.setServer(pathModel.getServer());
25963                pathElement.setName(pathModel.getName());
25964                pathElement.setType("path");
25965                if (pathModel.getFileFormat() != null) {
25966                        pathElement.setFileFormat(SQLUtil.trimColumnStringQuote(pathModel.getFileFormat()));
25967                }
25968
25969                if (pathModel.getStartPosition() != null && pathModel.getEndPosition() != null) {
25970                        pathElement.setCoordinate(convertCoordinate(pathModel.getStartPosition()) + ","
25971                                        + convertCoordinate(pathModel.getEndPosition()));
25972                }
25973
25974                if (pathModel.getProcesses() != null) {
25975                        List<String> processIds = new ArrayList<String>();
25976                        for (Process process : pathModel.getProcesses()) {
25977                                processIds.add(String.valueOf(process.getId()));
25978                        }
25979                        pathElement.setProcessIds(processIds);
25980                }
25981
25982                pathElement.setUri(pathModel.getPathUri() != null ? pathModel.getPathUri() : pathModel.getName());
25983                if (pathModel.getDataSourceName() != null) {
25984                        pathElement.setDataSourceName(pathModel.getDataSourceName());
25985                }
25986                // a file path a dynamic statement assembled at runtime (Mantis 4962); ordinary paths carry no endpoint attributes
25987                if (pathModel.isDynamicTemplate()) {
25988                        pathElement.setEndpointKind(EndpointKind.DYNAMIC_TEMPLATE.name());
25989                        pathElement.setTemplatedParts(pathModel.getTemplatedParts());
25990                }
25991
25992                dataflow.getPaths().add(pathElement);
25993
25994                // the files' columns with the fields an external table's declared STRUCT types read from them (Mantis 4858)
25995                List<TableColumn> columns = pathModel.getColumnsWithFieldPaths();
25996
25997                for (int j = 0; j < columns.size(); j++) {
25998                        TableColumn columnModel = (TableColumn) columns.get(j);
25999                        column columnElement = new column();
26000                        columnElement.setId(String.valueOf(columnModel.getId()));
26001                        columnElement.setName(SQLUtil.trimColumnStringQuote(columnModel.getName()));
26002                        columnElement.setFieldPathKey(fieldPathMergeKey(columnModel));
26003                        DlineageUtil.publishStarProvenance(columnElement, columnModel);
26004                        columnElement.setCoordinate(convertCoordinate(columnModel.getStartPosition()) + ","
26005                                        + convertCoordinate(columnModel.getEndPosition()));
26006                        pathElement.getColumns().add(columnElement);
26007                }
26008
26009                TableRelationRows relationRows = pathModel.getRelationRows();
26010                if (relationRows.hasRelation()) {
26011                        column relationRowsElement = new column();
26012                        relationRowsElement.setId(String.valueOf(relationRows.getId()));
26013                        relationRowsElement.setName(relationRows.getName());
26014                        if (relationRows.getStartPosition() != null && relationRows.getEndPosition() != null) {
26015                                relationRowsElement.setCoordinate(convertCoordinate(relationRows.getStartPosition()) + ","
26016                                                + convertCoordinate(relationRows.getEndPosition()));
26017                        }
26018                        relationRowsElement.setSource("system");
26019                        pathElement.getColumns().add(relationRowsElement);
26020                }
26021        }
26022
26023        private void appendVariableModel(dataflow dataflow, Table variableModel) {
26024                table variableElement = new table();
26025                variableElement.setId(String.valueOf(variableModel.getId()));
26026                if (!SQLUtil.isEmpty(variableModel.getDatabase())) {
26027                        variableElement.setDatabase(variableModel.getDatabase());
26028                }
26029                if (!SQLUtil.isEmpty(variableModel.getSchema())) {
26030                        variableElement.setSchema(variableModel.getSchema());
26031                }
26032                variableElement.setServer(variableModel.getServer());
26033                variableElement.setName(variableModel.getName());
26034                variableElement.setType("variable");
26035                variableElement.setParent(variableModel.getParent());
26036                if (variableModel.getProcedureId() != null) {
26037                        variableElement.setProcedureId(variableModel.getProcedureId());
26038                }
26039                variableElement.setIsTarget(String.valueOf(variableModel.isTarget()));
26040                if (variableModel.getSubType() != null) {
26041                        variableElement.setSubType(variableModel.getSubType().name());
26042                }
26043
26044                if (variableModel.getStartPosition() != null && variableModel.getEndPosition() != null) {
26045                        variableElement.setCoordinate(convertCoordinate(variableModel.getStartPosition()) + ","
26046                                        + convertCoordinate(variableModel.getEndPosition()));
26047                }
26048                dataflow.getVariables().add(variableElement);
26049
26050                List<TableColumn> columns = variableModel.getColumns();
26051
26052                if (containStarColumn(columns)) {
26053                        for (TableColumn column : columns) {
26054                                if (column.isStarColumn()) {
26055                                        for (TableColumn starElement : columns) {
26056                                                if (starElement == column) {
26057                                                        continue;
26058                                                }
26059                                                TObjectName columnObject = starElement.getColumnObject();
26060                                                column.bindStarLinkColumn(columnObject);
26061                                        }
26062//                                      column.setShowStar(false);
26063                                }
26064                        }
26065                }
26066
26067                for (int j = 0; j < columns.size(); j++) {
26068                        TableColumn columnModel = columns.get(j);
26069                        if (columnModel.hasStarLinkColumn()) {
26070                                List<String> starLinkColumnList = columnModel.getStarLinkColumnNames();
26071                                for (int k = 0; k < starLinkColumnList.size(); k++) {
26072                                        column columnElement = new column();
26073                                        columnElement.setId(columnModel.getId() + "_" + k);
26074                                        columnElement.setIdentifierKey(columnModel.starLinkIdentifier(k));
26075                                        columnElement.setSqlSpelling(columnModel.starLinkSqlSpelling(k));
26076                                        String columnName = starLinkColumnList.get(k);
26077                                        if (containStarColumn(columns, columnModel, k)) {
26078                                                continue;
26079                                        }
26080                                        columnElement.setName(columnName);
26081                                        if (columnModel.getStartPosition() != null && columnModel.getEndPosition() != null) {
26082                                                columnElement.setCoordinate(convertCoordinate(columnModel.getStartPosition()) + ","
26083                                                                + convertCoordinate(columnModel.getEndPosition()));
26084                                        }
26085                                        variableElement.getColumns().add(columnElement);
26086                                }
26087
26088                                if (columnModel.isShowStar()) {
26089                                        column columnElement = new column();
26090                                        columnElement.setId(String.valueOf(columnModel.getId()));
26091                                        columnElement.setName(columnModel.getName());
26092                                        DlineageUtil.publishStarProvenance(columnElement, columnModel);
26093                                        columnElement.setIdentifierKey(columnModel.getIdentifierKey());
26094                                        if (columnModel.getStartPosition() != null && columnModel.getEndPosition() != null) {
26095                                                columnElement.setCoordinate(convertCoordinate(columnModel.getStartPosition()) + ","
26096                                                                + convertCoordinate(columnModel.getEndPosition()));
26097                                        }
26098                                        variableElement.getColumns().add(columnElement);
26099                                }
26100
26101                        } else {
26102                                column columnElement = new column();
26103                                columnElement.setId(String.valueOf(columnModel.getId()));
26104                                columnElement.setName(columnModel.getName());
26105                                DlineageUtil.publishStarProvenance(columnElement, columnModel);
26106                                columnElement.setIdentifierKey(columnModel.getIdentifierKey());
26107                                if (columnModel.getStartPosition() != null && columnModel.getEndPosition() != null) {
26108                                        columnElement.setCoordinate(convertCoordinate(columnModel.getStartPosition()) + ","
26109                                                        + convertCoordinate(columnModel.getEndPosition()));
26110                                }
26111                                variableElement.getColumns().add(columnElement);
26112                        }
26113                }
26114
26115                TableRelationRows relationRows = variableModel.getRelationRows();
26116                if (relationRows.hasRelation()) {
26117                        column relationRowsElement = new column();
26118                        relationRowsElement.setId(String.valueOf(relationRows.getId()));
26119                        relationRowsElement.setName(relationRows.getName());
26120                        if (relationRows.getStartPosition() != null && relationRows.getEndPosition() != null) {
26121                                relationRowsElement.setCoordinate(convertCoordinate(relationRows.getStartPosition()) + ","
26122                                                + convertCoordinate(relationRows.getEndPosition()));
26123                        }
26124                        relationRowsElement.setSource("system");
26125                        variableElement.getColumns().add(relationRowsElement);
26126                }
26127        }
26128
26129        private void appendCursorModel(dataflow dataflow, Table cursorModel) {
26130                table cursorElement = new table();
26131                cursorElement.setId(String.valueOf(cursorModel.getId()));
26132                if (!SQLUtil.isEmpty(cursorModel.getDatabase())) {
26133                        cursorElement.setDatabase(cursorModel.getDatabase());
26134                }
26135                if (!SQLUtil.isEmpty(cursorModel.getSchema())) {
26136                        cursorElement.setSchema(cursorModel.getSchema());
26137                }
26138                cursorElement.setServer(cursorModel.getServer());
26139                cursorElement.setName(cursorModel.getName());
26140                cursorElement.setType("variable");
26141                if (cursorElement.getSubType() != null) {
26142                        cursorElement.setSubType(cursorModel.getSubType().name());
26143                }
26144
26145                if (cursorModel.getStartPosition() != null && cursorModel.getEndPosition() != null) {
26146                        cursorElement.setCoordinate(convertCoordinate(cursorModel.getStartPosition()) + ","
26147                                        + convertCoordinate(cursorModel.getEndPosition()));
26148                }
26149                dataflow.getVariables().add(cursorElement);
26150
26151                List<TableColumn> columns = cursorModel.getColumns();
26152
26153                if (containStarColumn(columns)) {
26154                        for (TableColumn column : columns) {
26155                                if (column.isStarColumn()) {
26156                                        for (TableColumn starElement : columns) {
26157                                                if (starElement == column) {
26158                                                        continue;
26159                                                }
26160                                                TObjectName columnObject = starElement.getColumnObject();
26161                                                column.bindStarLinkColumn(columnObject);
26162                                        }
26163                                        column.setShowStar(false);
26164                                }
26165                        }
26166                }
26167
26168                for (int j = 0; j < columns.size(); j++) {
26169                        TableColumn columnModel = (TableColumn) columns.get(j);
26170                        if (columnModel.hasStarLinkColumn()) {
26171                                List<String> starLinkColumnList = columnModel.getStarLinkColumnNames();
26172                                for (int k = 0; k < starLinkColumnList.size(); k++) {
26173                                        column columnElement = new column();
26174                                        columnElement.setId(String.valueOf(columnModel.getId()) + "_" + k);
26175                                        columnElement.setIdentifierKey(columnModel.starLinkIdentifier(k));
26176                                        columnElement.setSqlSpelling(columnModel.starLinkSqlSpelling(k));
26177                                        String columnName = starLinkColumnList.get(k);
26178                                        if (containStarColumn(columns, columnModel, k)) {
26179                                                continue;
26180                                        }
26181                                        columnElement.setName(columnName);
26182                                        if (columnModel.getStartPosition() != null && columnModel.getEndPosition() != null) {
26183                                                columnElement.setCoordinate(convertCoordinate(columnModel.getStartPosition()) + ","
26184                                                                + convertCoordinate(columnModel.getEndPosition()));
26185                                        }
26186                                        cursorElement.getColumns().add(columnElement);
26187                                }
26188
26189                                if (columnModel.isShowStar()) {
26190                                        column columnElement = new column();
26191                                        columnElement.setId(String.valueOf(columnModel.getId()));
26192                                        columnElement.setName(columnModel.getName());
26193                                        DlineageUtil.publishStarProvenance(columnElement, columnModel);
26194                                        columnElement.setIdentifierKey(columnModel.getIdentifierKey());
26195                                        if (columnModel.getStartPosition() != null && columnModel.getEndPosition() != null) {
26196                                                columnElement.setCoordinate(convertCoordinate(columnModel.getStartPosition()) + ","
26197                                                                + convertCoordinate(columnModel.getEndPosition()));
26198                                        }
26199                                        cursorElement.getColumns().add(columnElement);
26200                                }
26201
26202                        } else {
26203                                column columnElement = new column();
26204                                columnElement.setId(String.valueOf(columnModel.getId()));
26205                                columnElement.setName(columnModel.getName());
26206                                DlineageUtil.publishStarProvenance(columnElement, columnModel);
26207                                columnElement.setIdentifierKey(columnModel.getIdentifierKey());
26208                                if (columnModel.getStartPosition() != null && columnModel.getEndPosition() != null) {
26209                                        columnElement.setCoordinate(convertCoordinate(columnModel.getStartPosition()) + ","
26210                                                        + convertCoordinate(columnModel.getEndPosition()));
26211                                }
26212                                cursorElement.getColumns().add(columnElement);
26213                        }
26214                }
26215
26216                TableRelationRows relationRows = cursorModel.getRelationRows();
26217                if (relationRows.hasRelation()) {
26218                        column relationRowsElement = new column();
26219                        relationRowsElement.setId(String.valueOf(relationRows.getId()));
26220                        relationRowsElement.setName(relationRows.getName());
26221                        if (relationRows.getStartPosition() != null && relationRows.getEndPosition() != null) {
26222                                relationRowsElement.setCoordinate(convertCoordinate(relationRows.getStartPosition()) + ","
26223                                                + convertCoordinate(relationRows.getEndPosition()));
26224                        }
26225                        relationRowsElement.setSource("system");
26226                        cursorElement.getColumns().add(relationRowsElement);
26227                }
26228        }
26229
26230        private void appendErrors(dataflow dataflow) {
26231                List<ErrorInfo> errorInfos = this.getErrorMessages();
26232                if (metadataErrors != null) {
26233                        for (int i = 0; i < metadataErrors.size(); i++) {
26234                                errorInfos.add(i, metadataErrors.get(i));
26235                        }
26236                }
26237
26238                for (int i = 0; i < errorInfos.size(); ++i) {
26239                        ErrorInfo errorInfo = errorInfos.get(i);
26240                        error error = new error();
26241                        if (!SQLUtil.isEmpty(errorInfo.getErrorMessage())) {
26242                                error.setErrorMessage(errorInfo.getErrorMessage());
26243                        }
26244                        if (!SQLUtil.isEmpty(errorInfo.getErrorType())) {
26245                                error.setErrorType(errorInfo.getErrorType());
26246                        }
26247                        if (errorInfo.getStartPosition() != null && errorInfo.getEndPosition() != null) {
26248                                error.setCoordinate(convertCoordinate(errorInfo.getStartPosition()) + ","
26249                                                + convertCoordinate(errorInfo.getEndPosition()));
26250                        }
26251                        if (!SQLUtil.isEmpty(errorInfo.getFileName())) {
26252                                error.setFile(errorInfo.getFileName());
26253                        }
26254                        if (errorInfo.getOriginStartPosition() != null && errorInfo.getOriginEndPosition() != null) {
26255                                error.setOriginCoordinate(errorInfo.getOriginStartPosition() + "," + errorInfo.getOriginEndPosition());
26256                        }
26257                        dataflow.getErrors().add(error);
26258                }
26259        }
26260
26261        private void appendOraclePackages(dataflow dataflow) {
26262                List<OraclePackage> packages = this.modelManager.getOraclePackageModels();
26263
26264                for (int i = 0; i < packages.size(); ++i) {
26265                        OraclePackage model = packages.get(i);
26266                        oraclePackage oraclePackage = new oraclePackage();
26267                        oraclePackage.setId(String.valueOf(model.getId()));
26268                        if (!SQLUtil.isEmpty(model.getDatabase())) {
26269                                oraclePackage.setDatabase(model.getDatabase());
26270                        }
26271                        if (!SQLUtil.isEmpty(model.getSchema())) {
26272                                oraclePackage.setSchema(model.getSchema());
26273                        }
26274                        oraclePackage.setServer(model.getServer());
26275                        oraclePackage.setName(model.getName());
26276                        if (model.getType() != null) {
26277                                oraclePackage.setType(model.getType().name().replace("sst", ""));
26278                        }
26279                        if (model.getStartPosition() != null && model.getEndPosition() != null) {
26280                                oraclePackage.setCoordinate(
26281                                                convertCoordinate(model.getStartPosition()) + "," + convertCoordinate(model.getEndPosition()));
26282                        }
26283
26284                        oraclePackage.setComment(packageCommentOf(model));
26285
26286                        dataflow.getPackages().add(oraclePackage);
26287
26288                        List<Argument> arguments = model.getArguments();
26289
26290                        for (int j = 0; j < arguments.size(); ++j) {
26291                                Argument argumentModel = (Argument) arguments.get(j);
26292                                argument argumentElement = new argument();
26293                                argumentElement.setId(String.valueOf(argumentModel.getId()));
26294                                argumentElement.setName(argumentModel.getName());
26295                                if (argumentModel.getStartPosition() != null && argumentModel.getEndPosition() != null) {
26296                                        argumentElement.setCoordinate(convertCoordinate(argumentModel.getStartPosition()) + ","
26297                                                        + convertCoordinate(argumentModel.getEndPosition()));
26298                                }
26299
26300                                argumentElement.setDatatype(argumentModel.getDataType().getDataTypeName());
26301                                argumentElement.setInout(argumentModel.getMode().name());
26302                                oraclePackage.getArguments().add(argumentElement);
26303                        }
26304
26305                        gudusoft.gsqlparser.TSourceToken packageBodyKeyword = packageBodyKeywordOf(model);
26306                        gudusoft.gsqlparser.TSourceToken memberBound = memberBoundOf(model, packageBodyKeyword);
26307                        for (int j = 0; j < model.getProcedures().size(); j++) {
26308                                Procedure procedureModel = model.getProcedures().get(j);
26309                                procedure procedure = new procedure();
26310                                procedure.setId(String.valueOf(procedureModel.getId()));
26311                                if (!SQLUtil.isEmpty(procedureModel.getDatabase())) {
26312                                        procedure.setDatabase(procedureModel.getDatabase());
26313                                }
26314                                if (!SQLUtil.isEmpty(procedureModel.getSchema())) {
26315                                        procedure.setSchema(procedureModel.getSchema());
26316                                }
26317                                procedure.setServer(procedureModel.getServer());
26318                                procedure.setName(procedureModel.getName());
26319                                // taken from the name as written, before normalized output renames it (DataflowUtility.routineKey)
26320                                procedure.setRoutineKey(DataflowUtility.routineKey(procedure.getName()));
26321                                if (procedureModel.getType() != null) {
26322                                        procedure.setType(procedureModel.getType().name().replace("sst", ""));
26323                                }
26324                                if (procedureModel.getStartPosition() != null && procedureModel.getEndPosition() != null) {
26325                                        procedure.setCoordinate(convertCoordinate(procedureModel.getStartPosition()) + ","
26326                                                        + convertCoordinate(procedureModel.getEndPosition()));
26327                                }
26328
26329                                procedure.setComment(memberCommentOf(procedureModel.getProcedureObject(), memberBound));
26330
26331                                oraclePackage.getProcedures().add(procedure);
26332
26333                                List<Argument> procedureArguments = procedureModel.getArguments();
26334
26335                                for (int k = 0; k < procedureArguments.size(); ++k) {
26336                                        Argument argumentModel = (Argument) procedureArguments.get(k);
26337                                        argument argumentElement = new argument();
26338                                        argumentElement.setId(String.valueOf(argumentModel.getId()));
26339                                        argumentElement.setName(argumentModel.getName());
26340                                        if (argumentModel.getStartPosition() != null && argumentModel.getEndPosition() != null) {
26341                                                argumentElement.setCoordinate(convertCoordinate(argumentModel.getStartPosition()) + ","
26342                                                                + convertCoordinate(argumentModel.getEndPosition()));
26343                                        }
26344
26345                                        argumentElement.setDatatype(argumentModel.getDataType().getDataTypeName());
26346                                        argumentElement.setInout(argumentModel.getMode().name());
26347                                        procedure.getArguments().add(argumentElement);
26348                                }
26349                        }
26350                }
26351        }
26352
26353        /**
26354         * The comment the author wrote directly above {@code node}, or {@code null} when the caller
26355         * did not ask for comments. Bounded at a blank line so a section header stays with its
26356         * section. See {@code Option.setShowComment(boolean)}.
26357         */
26358        private String commentOf(gudusoft.gsqlparser.nodes.TParseTreeNode node) {
26359                if (node == null || !option.isShowComment()) {
26360                        return null;
26361                }
26362                try {
26363                        String comment = node.getCommentBeforeNode(true);
26364                        return SQLUtil.isEmpty(comment) ? null : comment;
26365                } catch (Exception e) {
26366                        return null;
26367                }
26368        }
26369
26370        /**
26371         * The package's header comment: the block that sits under the package declaration, after
26372         * {@code AS} / {@code IS}.
26373         *
26374         * <p>Read forward from that keyword rather than backward from the first procedure, because a
26375         * package may declare variables, types or cursors before any procedure, and a backward walk
26376         * stops at the first of those and loses the header.
26377         */
26378        private String packageCommentOf(gudusoft.gsqlparser.dlineage.dataflow.model.OraclePackage model) {
26379                if (model == null || !option.isShowComment()) {
26380                        return null;
26381                }
26382                String own = commentOf(model.getProcedureObject());
26383                if (!SQLUtil.isEmpty(own)) {
26384                        return own;
26385                }
26386                gudusoft.gsqlparser.TSourceToken bodyKeyword = packageBodyKeywordOf(model);
26387                if (bodyKeyword == null) {
26388                        return null;
26389                }
26390                try {
26391                        return gudusoft.gsqlparser.nodes.TParseTreeNode.commentBlockAfter(bodyKeyword);
26392                } catch (Exception e) {
26393                        return null;
26394                }
26395        }
26396
26397        /**
26398         * The {@code AS} / {@code IS} that opens a package body, or {@code null}.
26399         *
26400         * <p>It is where the header comment starts, and it is also the boundary for the members: a
26401         * comment above the first procedure but below this keyword describes the package when nothing
26402         * separates it from the keyword, so the procedure's own scan must not run past it.
26403         */
26404        private gudusoft.gsqlparser.TSourceToken packageBodyKeywordOf(
26405                        gudusoft.gsqlparser.dlineage.dataflow.model.OraclePackage model) {
26406                if (model == null) {
26407                        return null;
26408                }
26409                gudusoft.gsqlparser.nodes.TParseTreeNode packageNode = model.getProcedureObject();
26410                if (packageNode == null || packageNode.getStartToken() == null) {
26411                        return null;
26412                }
26413                try {
26414                        gudusoft.gsqlparser.TSourceToken current = packageNode.getStartToken();
26415                        gudusoft.gsqlparser.TSourceToken stop = packageNode.getEndToken();
26416                        while (current != null) {
26417                                String text = current.toString();
26418                                if ("AS".equalsIgnoreCase(text) || "IS".equalsIgnoreCase(text)) {
26419                                        return current;
26420                                }
26421                                if (current == stop) {
26422                                        break;
26423                                }
26424                                current = current.getNextTokenInChain();
26425                        }
26426                } catch (Exception e) {
26427                        return null;
26428                }
26429                return null;
26430        }
26431
26432        /**
26433         * The comment above a package member, minus the one the package already claimed.
26434         *
26435         * <p>The walk stops at the body keyword so it cannot wander into the package declaration, and
26436         * a block identical to the package's own comment is dropped: one comment, one owner. A member
26437         * that simply follows a header the package took from ELSEWHERE (a block written above
26438         * {@code CREATE PACKAGE BODY}) keeps its own.
26439         */
26440        /**
26441         * How far back a package member may look for its comment.
26442         *
26443         * <p>Past the body keyword is the package declaration, never a member's. And when the package
26444         * took its header from the block right after that keyword, the member may not reach that block
26445         * either — so the bound becomes the block's last token. If the package's comment came from
26446         * ABOVE its declaration instead, the block after the keyword was never claimed and belongs to
26447         * whichever member follows it. Deciding this from the tokens rather than by comparing text
26448         * keeps two objects that carry identical comments independent.
26449         */
26450        private gudusoft.gsqlparser.TSourceToken memberBoundOf(
26451                        gudusoft.gsqlparser.dlineage.dataflow.model.OraclePackage model,
26452                        gudusoft.gsqlparser.TSourceToken bodyKeyword) {
26453                if (bodyKeyword == null) {
26454                        return null;
26455                }
26456                if (!SQLUtil.isEmpty(commentOf(model.getProcedureObject()))) {
26457                        return bodyKeyword;   // the header came from above the declaration
26458                }
26459                gudusoft.gsqlparser.TSourceToken headerEnd =
26460                                gudusoft.gsqlparser.nodes.TParseTreeNode.commentBlockEndAfter(bodyKeyword);
26461                return headerEnd == null ? bodyKeyword : headerEnd;
26462        }
26463
26464        private String memberCommentOf(gudusoft.gsqlparser.nodes.TParseTreeNode node,
26465                        gudusoft.gsqlparser.TSourceToken memberBound) {
26466                if (node == null || !option.isShowComment()) {
26467                        return null;
26468                }
26469                try {
26470                        String comment = node.getCommentBeforeNode(true, memberBound);
26471                        return SQLUtil.isEmpty(comment) ? null : comment;
26472                } catch (Exception e) {
26473                        return null;
26474                }
26475        }
26476
26477        private void appendProcedures(dataflow dataflow) {
26478                List<Procedure> procedures = this.modelManager.getProcedureModels();
26479
26480                for (int i = 0; i < procedures.size(); ++i) {
26481                        Procedure model = procedures.get(i);
26482                        if (model.getParentPackage() != null) {
26483                                continue;
26484                        }
26485                        procedure procedure = new procedure();
26486                        procedure.setId(String.valueOf(model.getId()));
26487                        if (!SQLUtil.isEmpty(model.getDatabase())) {
26488                                procedure.setDatabase(model.getDatabase());
26489                        }
26490                        if (!SQLUtil.isEmpty(model.getSchema())) {
26491                                procedure.setSchema(model.getSchema());
26492                        }
26493                        procedure.setServer(model.getServer());
26494                        procedure.setName(model.getName());
26495                        // taken from the name as written, before normalized output renames it (DataflowUtility.routineKey)
26496                        procedure.setRoutineKey(DataflowUtility.routineKey(procedure.getName()));
26497                        if (model.getType() != null) {
26498                                procedure.setType(model.getType().name().replace("sst", ""));
26499                        }
26500                        if (model.getStartPosition() != null && model.getEndPosition() != null) {
26501                                procedure.setCoordinate(
26502                                                convertCoordinate(model.getStartPosition()) + "," + convertCoordinate(model.getEndPosition()));
26503                        }
26504
26505                        procedure.setComment(commentOf(model.getProcedureObject()));
26506
26507                        dataflow.getProcedures().add(procedure);
26508
26509                        List<Argument> arguments = model.getArguments();
26510
26511                        for (int j = 0; j < arguments.size(); ++j) {
26512                                Argument argumentModel = (Argument) arguments.get(j);
26513                                argument argumentElement = new argument();
26514                                argumentElement.setId(String.valueOf(argumentModel.getId()));
26515                                argumentElement.setName(argumentModel.getName());
26516                                if (argumentModel.getStartPosition() != null && argumentModel.getEndPosition() != null) {
26517                                        argumentElement.setCoordinate(convertCoordinate(argumentModel.getStartPosition()) + ","
26518                                                        + convertCoordinate(argumentModel.getEndPosition()));
26519                                }
26520
26521                                argumentElement.setDatatype(argumentModel.getDataType().getDataTypeName());
26522                                argumentElement.setInout(argumentModel.getMode().name());
26523                                procedure.getArguments().add(argumentElement);
26524                        }
26525                }
26526        }
26527
26528        private void appendProcesses(dataflow dataflow) {
26529                List<Process> processes = this.modelManager.getProcessModels();
26530
26531                for (int i = 0; i < processes.size(); ++i) {
26532                        Process model = processes.get(i);
26533                        process process = new process();
26534                        process.setId(String.valueOf(model.getId()));
26535                        if (!SQLUtil.isEmpty(model.getDatabase())) {
26536                                process.setDatabase(model.getDatabase());
26537                        }
26538                        if (!SQLUtil.isEmpty(model.getSchema())) {
26539                                process.setSchema(model.getSchema());
26540                        }
26541                        process.setServer(model.getServer());
26542                        process.setName(getProcessName(model));
26543                        if (!SQLUtil.isEmpty(model.getProcedureName())) {
26544                                process.setProcedureName(model.getProcedureName());
26545                        }
26546                        if (model.getProcedureId() != null) {
26547                                process.setProcedureId(String.valueOf(model.getProcedureId()));
26548                        }
26549                        if (!SQLUtil.isEmpty(model.getQueryHashId())) {
26550                                process.setQueryHashId(model.getQueryHashId());
26551                        }
26552                        if (model.getGspObject() != null) {
26553                                process.setType(model.getGspObject().sqlstatementtype.name());
26554                        }
26555                        if (model.getStartPosition() != null && model.getEndPosition() != null) {
26556                                process.setCoordinate(
26557                                                convertCoordinate(model.getStartPosition()) + "," + convertCoordinate(model.getEndPosition()));
26558                        }
26559                        if (model.getTransforms() != null && !model.getTransforms().isEmpty()) {
26560                                for (Transform transformItem : model.getTransforms()) {
26561                                        process.addTransform(transformItem);
26562                                }
26563                        }
26564                        dataflow.getProcesses().add(process);
26565                }
26566        }
26567
26568        private void appendTables(dataflow dataflow) {
26569                List<TTable> tables = modelManager.getBaseTables();
26570                Map<Object, table> tableMap = new HashMap<Object, table>();
26571                Set<Long> tableModelIds = new HashSet<Long>();
26572                for (int i = 0; i < tables.size(); i++) {
26573                        Object model = modelManager.getModel(tables.get(i));
26574                        if (model instanceof Table) {
26575                                Table tableModel = (Table) model;
26576                                if(tableModelIds.contains(tableModel.getId())) {
26577                                        continue;
26578                                }
26579                                else {
26580                                        tableModelIds.add(tableModel.getId());
26581                                }
26582                                if (tableModel.isView()) {
26583                                        continue;
26584                                }
26585                                if (tableModel.isStage()) {
26586                                        appendStageModel(dataflow, tableModel);
26587                                        continue;
26588                                }
26589                                if (tableModel.isSequence()) {
26590                                        appendSequenceModel(dataflow, tableModel);
26591                                        continue;
26592                                }
26593                                if (tableModel.isDataSource()) {
26594                                        appendDataSourceModel(dataflow, tableModel);
26595                                        continue;
26596                                }
26597                                if (tableModel.isDatabase()) {
26598                                        appendDatabaseModel(dataflow, tableModel);
26599                                        continue;
26600                                }
26601                                if (tableModel.isSchema()) {
26602                                        appendSchemaModel(dataflow, tableModel);
26603                                        continue;
26604                                }
26605                                if (tableModel.isStream()) {
26606                                        appendStreamModel(dataflow, tableModel);
26607                                        continue;
26608                                }
26609                                if (tableModel.isPath()) {
26610                                        appendPathModel(dataflow, tableModel);
26611                                        continue;
26612                                }
26613                                if (tableModel.isVariable() && !tableModel.isCursor()) {
26614                                        appendVariableModel(dataflow, tableModel);
26615                                        continue;
26616                                }
26617                                if (tableModel.isCursor()) {
26618                                        appendCursorModel(dataflow, tableModel);
26619                                        continue;
26620                                }
26621                                if (tableModel.isConstant()) {
26622                                        appendConstantModel(dataflow, tableModel);
26623                                        continue;
26624                                }
26625                                if (!tableIds.contains(tableModel.getId())) {
26626                                        appendTableModel(dataflow, tableModel, tableMap);
26627                                        tableIds.add(tableModel.getId());
26628                                }
26629                        } else if (model instanceof QueryTable) {
26630                                QueryTable queryTable = (QueryTable) model;
26631                                if (!tableIds.contains(queryTable.getId())) {
26632                                        appendResultSet(dataflow, queryTable);
26633                                        tableIds.add(queryTable.getId());
26634                                }
26635                        }
26636                }
26637
26638                List<Table> tableNames = modelManager.getTablesByName();
26639                tableNames.addAll(modelManager.getDropTables());
26640                
26641                for (int i = 0; i < tableNames.size(); i++) {
26642                        Table tableModel = tableNames.get(i);
26643                        if(tableModelIds.contains(tableModel.getId())) {
26644                                continue;
26645                        }
26646                        else {
26647                                tableModelIds.add(tableModel.getId());
26648                        }
26649                        if (tableModel.isView()) {
26650                                continue;
26651                        }
26652                        if (tableModel.isDatabase()) {
26653                                appendDatabaseModel(dataflow, tableModel);
26654                                continue;
26655                        }
26656                        if (tableModel.isSchema()) {
26657                                appendSchemaModel(dataflow, tableModel);
26658                                continue;
26659                        }
26660                        if (tableModel.isStage()) {
26661                                appendStageModel(dataflow, tableModel);
26662                                continue;
26663                        }
26664                        if (tableModel.isSequence()) {
26665                                appendSequenceModel(dataflow, tableModel);
26666                                continue;
26667                        }
26668                        if (tableModel.isDataSource()) {
26669                                appendDataSourceModel(dataflow, tableModel);
26670                                continue;
26671                        }
26672                        if (tableModel.isStream()) {
26673                                appendStreamModel(dataflow, tableModel);
26674                                continue;
26675                        }
26676                        if (tableModel.isPath()) {
26677                                appendPathModel(dataflow, tableModel);
26678                                continue;
26679                        }
26680                        if (tableModel.isVariable()) {
26681                                appendVariableModel(dataflow, tableModel);
26682                                continue;
26683                        }
26684                        if (tableModel.isCursor()) {
26685                                appendCursorModel(dataflow, tableModel);
26686                                continue;
26687                        }
26688                        if (tableModel.isConstant()) {
26689                                appendConstantModel(dataflow, tableModel);
26690                                continue;
26691                        }
26692                        if (!tableIds.contains(tableModel.getId())) {
26693                                appendTableModel(dataflow, tableModel, tableMap);
26694                                tableIds.add(tableModel.getId());
26695                        }
26696                }
26697        }
26698
26699        private void appendConstantModel(dataflow dataflow, Table tableModel) {
26700                table constantElement = new table();
26701                constantElement.setId(String.valueOf(tableModel.getId()));
26702                if (!SQLUtil.isEmpty(tableModel.getDatabase())) {
26703                        constantElement.setDatabase(tableModel.getDatabase());
26704                }
26705                if (!SQLUtil.isEmpty(tableModel.getSchema())) {
26706                        constantElement.setSchema(tableModel.getSchema());
26707                }
26708                constantElement.setServer(tableModel.getServer());
26709                constantElement.setName(getConstantName(tableModel));
26710                constantElement.setType("constantTable");
26711
26712                if (tableModel.getStartPosition() != null && tableModel.getEndPosition() != null) {
26713                        constantElement.setCoordinate(convertCoordinate(tableModel.getStartPosition()) + ","
26714                                        + convertCoordinate(tableModel.getEndPosition()));
26715                }
26716                dataflow.getTables().add(constantElement);
26717
26718                List<TableColumn> columns = tableModel.getColumns();
26719                for (int j = 0; j < columns.size(); j++) {
26720                        TableColumn columnModel = (TableColumn) columns.get(j);
26721                        column columnElement = new column();
26722                        columnElement.setId(String.valueOf(columnModel.getId()));
26723                        columnElement.setName(columnModel.getName());
26724                        DlineageUtil.publishStarProvenance(columnElement, columnModel);
26725                        columnElement.setIdentifierKey(columnModel.getIdentifierKey());
26726                        if (columnModel.getStartPosition() != null && columnModel.getEndPosition() != null) {
26727                                columnElement.setCoordinate(convertCoordinate(columnModel.getStartPosition()) + ","
26728                                                + convertCoordinate(columnModel.getEndPosition()));
26729                        }
26730                        constantElement.getColumns().add(columnElement);
26731                }
26732        }
26733
26734        /**
26735         * Copies the authoritative endpoint classification from the internal model onto the
26736         * XML element, emitting each field ONLY when it carries information (kind/intro not
26737         * UNKNOWN, createdInSql only when true, normalizedName only when it differs from the
26738         * raw name). This keeps existing XML byte-identical for endpoints that aren't
26739         * classified, honouring the strictly-additive contract (§9 of
26740         * docs/tmp/dlineage-authoritative-endpoint-classification.md).
26741         */
26742        private void appendEndpointClassification(table tableElement, Table tableModel) {
26743                if (ModelBindingManager.isScopedTemporary(tableModel.getTableObject())) tableElement.setScopedTemporary(true);
26744                if (tableModel.isArrayRelation()) {
26745                        tableElement.setArrayRelation(true);
26746                }
26747                boolean created = tableModel.isCreatedInSql();
26748                EndpointKind kind = tableModel.getEndpointKind();
26749                // Emit endpointKind only when it carries signal: a non-catalog special kind
26750                // (temp / global-temp / table-variable / tempdb) always, or CATALOG_OBJECT only
26751                // when the object is created here. A plainly-referenced catalog table is the
26752                // default case and carries no attribute, so existing golden XML for ordinary
26753                // table references stays byte-identical.
26754                if (kind != null && kind != EndpointKind.UNKNOWN
26755                                && (created || kind != EndpointKind.CATALOG_OBJECT)) {
26756                        tableElement.setEndpointKind(kind.name());
26757                }
26758                EndpointIntroduction intro = tableModel.getEndpointIntroduction();
26759                if (intro != null && intro != EndpointIntroduction.UNKNOWN) {
26760                        tableElement.setEndpointIntroduction(intro.name());
26761                }
26762                if (created) {
26763                        tableElement.setCreatedInSql("true");
26764                }
26765                // Which name segments a dynamic fold assembled at runtime. Present only on endpoints
26766                // the analyzer marked inside a fold context, so ordinary output is untouched.
26767                if (tableModel.getTemplatedParts() != null) {
26768                        tableElement.setTemplatedParts(tableModel.getTemplatedParts());
26769                }
26770                // Emit normalizedName only when the raw name actually carries delimiters
26771                // (brackets / quotes / backticks) — that is when the normalized form is useful
26772                // and not trivially derivable. Avoids case-only churn on every plain name.
26773                // A templated endpoint always gets it: consumers match the delimiter-free skeleton
26774                // against later-resolved concrete names, and an unbracketed concatenation
26775                // ('RECON_' + @X + '_t') carries no delimiter to trigger the test above.
26776                String rawName = tableModel.getName();
26777                if (rawName != null && (hasIdentifierDelimiter(rawName) || tableModel.isDynamicTemplate())) {
26778                        String normalized = tableModel.getNormalizedName();
26779                        if (normalized != null && !normalized.equals(rawName)) {
26780                                tableElement.setNormalizedName(normalized);
26781                        }
26782                }
26783        }
26784
26785        private static boolean hasIdentifierDelimiter(String name) {
26786                return name.indexOf('[') != -1 || name.indexOf(']') != -1
26787                                || name.indexOf('`') != -1 || name.indexOf('"') != -1;
26788        }
26789
26790        private void appendTableModel(dataflow dataflow, Table tableModel, Map<Object, table> tableMap) {
26791                if(tableModel.getSubType() == SubType.unnest) {}
26792                table tableElement = new table();
26793                tableElement.setIdentifierKey(tableModel.getIdentifierKey());
26794                tableElement.setMergeIdentity(tableModel.getCanonicalIdentity());
26795                tableElement.setId(String.valueOf(tableModel.getId()));
26796                // A one-part name that is ambiguous across two or more schemas (with no object
26797                // in the default schema) must NOT be bound to a fabricated default-schema
26798                // object: surface it unqualified and expose the candidate set instead.
26799                boolean ambiguousUnqualified = tableModel.isAmbiguousUnqualifiedTable()
26800                                && tableModel.getCandidateTables() != null
26801                                && !tableModel.getCandidateTables().isEmpty();
26802                if (!ambiguousUnqualified) {
26803                        if (!SQLUtil.isEmpty(tableModel.getDatabase())) {
26804                                tableElement.setDatabase(tableModel.getDatabase());
26805                        }
26806                        if (!SQLUtil.isEmpty(tableModel.getSchema())) {
26807                                tableElement.setSchema(tableModel.getSchema());
26808                        }
26809                }
26810                tableElement.setServer(tableModel.getServer());
26811                if (ambiguousUnqualified) {
26812                        tableElement.setName(DlineageUtil.getSimpleTableName(tableModel.getName()));
26813                        tableElement.setCandidateTables(new ArrayList<String>(tableModel.getCandidateTables()));
26814                } else {
26815                        tableElement.setName(tableModel.getName());
26816                }
26817                tableElement.setDisplayName(tableModel.getDisplayName());
26818                tableElement.setStarStmt(tableModel.getStarStmt());
26819                if(tableModel.isFromDDL()) {
26820                        tableElement.setFromDDL(String.valueOf(tableModel.isFromDDL()));
26821                }
26822                // Authoritative endpoint classification (additive). Emit only non-default
26823                // values so existing XML stays byte-identical for unclassified endpoints.
26824                // See docs/tmp/dlineage-authoritative-endpoint-classification.md.
26825                appendEndpointClassification(tableElement, tableModel);
26826
26827                if (tableModel.isPseudo()) {
26828                        tableElement.setType("pseudoTable");
26829                } else {
26830                        tableElement.setType("table");
26831                }
26832
26833                if (tableModel.getSubType() != null) {
26834                        if (tableModel.getSubType() == SubType.unnest) {
26835                                tableElement.setType(SubType.unnest.name());
26836                        }
26837                        else {
26838                                tableElement.setSubType(tableModel.getSubType().name());
26839                        }
26840                }
26841                if (tableModel.getParent() != null) {
26842                        tableElement.setParent(tableModel.getParent());
26843                }
26844                if (tableModel.getAlias() != null && tableModel.getAlias().trim().length() > 0) {
26845                        tableElement.setAlias(tableModel.getAlias());
26846                }
26847                if (tableModel.getStartPosition() != null && tableModel.getEndPosition() != null) {
26848            if(option.isTraceTablePosition()){
26849                for (Pair<Pair3<Long, Long, String>, Pair3<Long, Long, String>> position:tableModel.getPositions()){
26850                    tableElement.appendCoordinate(convertCoordinate(position.first)+","+convertCoordinate(position.second));
26851                }
26852            }
26853            else {
26854                tableElement.setCoordinate(convertCoordinate(tableModel.getStartPosition()) + ","
26855                    + convertCoordinate(tableModel.getEndPosition()));
26856            }
26857                }
26858                if (tableModel.getProcesses() != null) {
26859                        List<String> processIds = new ArrayList<String>();
26860                        for (Process process : tableModel.getProcesses()) {
26861                                processIds.add(String.valueOf(process.getId()));
26862                        }
26863                        tableElement.setProcessIds(processIds);
26864                }
26865
26866                table oldTableElement = null;
26867                // The elements that share column metadata: by the BigQuery key, else by the bucket of the qualified published
26868                // name. Not by mergeIdentity: it is not qualified alike for one table (a table a DDL creates carries employee,
26869                // a reference to it default.employee), and keying by it keeps one table's elements from sharing their column
26870                // types. Two tables the analysis keeps apart publish names of their own (NormalNameCollisions), so their
26871                // buckets differ (TableMetadataIdentityTest).
26872                Object tableFullName = tableElement.getIdentifierKey() != null ? tableElement.getIdentifierKey()
26873                                : LegacyDlineageWireNamesV1.tableBucket(DlineageUtil.getQualifiedTableName(tableElement));
26874                if (tableElement.isArrayRelation()) {
26875                        // a LATERAL VIEW's relation shares nothing with a table of its name (Mantis 4890)
26876                        tableFullName = Arrays.asList("array relation", tableModel.getId());
26877                }
26878                
26879                if(tableMap.containsKey(tableFullName)) {
26880                        oldTableElement = tableMap.get(tableFullName);
26881                }
26882                else {
26883                        tableMap.put(tableFullName, tableElement);
26884                }
26885                
26886                if (tableModel.getSubType() == SubType.unnest) {
26887                        dataflow.getResultsets().add(tableElement);
26888                } else {
26889                        dataflow.getTables().add(tableElement);
26890                }
26891
26892                List<TableColumn> columns = tableModel.getColumns();
26893
26894                if (containStarColumn(columns)) {
26895                        for (TableColumn column : columns) {
26896                                if (column.isStarColumn()) {
26897                                        for (TableColumn starElement : columns) {
26898                                                if (starElement == column) {
26899                                                        continue;
26900                                                }
26901                                                if (starElement.isNotBindStarLinkColumn()) {
26902                                                        continue;
26903                                                }
26904                                                TObjectName columnObject = starElement.getColumnObject();
26905                                                column.bindStarLinkColumn(columnObject);
26906                                        }
26907                                        if (tableModel.isCreateTable() && column.isExpandStar()) {
26908                                                column.setShowStar(false);
26909                                        }
26910                                }
26911                        }
26912                }
26913
26914                // each column followed by the STRUCT field path nodes below it
26915                List<TableColumn> publishedColumns = tableModel.getColumnsWithFieldPaths();
26916                for (int j = 0; j < publishedColumns.size(); j++) {
26917                        TableColumn columnModel = publishedColumns.get(j);
26918
26919                        if(oldTableElement != null){
26920                                if(!CollectionUtil.isEmpty(oldTableElement.getColumns())){
26921                                        for(column oldColumnElement: oldTableElement.getColumns()){
26922                                                if(SQLUtil.sameName(option.getVendor(), ESQLDataObjectType.dotColumn, oldColumnElement.getName(), columnModel.getName())){
26923                                                        if(columnModel.getDataType() == null && oldColumnElement.getDataType() != null){
26924                                                                columnModel.setDataType(oldColumnElement.getDataType());
26925                                                        }
26926                                                        if(columnModel.getPrimaryKey() == null && oldColumnElement.isPrimaryKey() != null){
26927                                                                columnModel.setPrimaryKey(oldColumnElement.isPrimaryKey());
26928                                                        }
26929                                                        if(columnModel.getIndexKey() == null && oldColumnElement.isIndexKey() != null){
26930                                                                columnModel.setIndexKey(oldColumnElement.isIndexKey());
26931                                                        }
26932                                                        if(columnModel.getUnqiueKey() == null && oldColumnElement.isUnqiueKey() != null){
26933                                                                columnModel.setUnqiueKey(oldColumnElement.isUnqiueKey());
26934                                                        }
26935                                                        if(columnModel.getForeignKey() == null && oldColumnElement.isForeignKey() != null){
26936                                                                columnModel.setForeignKey(oldColumnElement.isForeignKey());
26937                                                        }
26938
26939                                                        if(oldColumnElement.getDataType() == null && columnModel.getDataType() != null){
26940                                                                oldColumnElement.setDataType(columnModel.getDataType());
26941                                                        }
26942                                                        if(oldColumnElement.isPrimaryKey() == null && columnModel.getPrimaryKey() != null){
26943                                                                oldColumnElement.setPrimaryKey(columnModel.getPrimaryKey());
26944                                                        }
26945                                                        if(oldColumnElement.isIndexKey() == null && columnModel.getIndexKey() != null){
26946                                                                oldColumnElement.setIndexKey(columnModel.getIndexKey());
26947                                                        }
26948                                                        if(oldColumnElement.isUnqiueKey() == null && columnModel.getUnqiueKey() != null){
26949                                                                oldColumnElement.setUnqiueKey(columnModel.getUnqiueKey());
26950                                                        }
26951                                                        if(oldColumnElement.isForeignKey() == null && columnModel.getForeignKey() != null){
26952                                                                oldColumnElement.setForeignKey(columnModel.getForeignKey());
26953                                                        }
26954                                                        break;
26955                                                }
26956                                        }
26957                                }
26958
26959                        }
26960
26961                        if (!columnModel.isPseduo() && columnModel.hasStarLinkColumn() && !columnModel.isVariant()) {
26962                                List<String> starLinkColumnList = columnModel.getStarLinkColumnNames();
26963                                for (int k = 0; k < starLinkColumnList.size(); k++) {
26964                                        column columnElement = new column();
26965                                        columnElement.setId(String.valueOf(columnModel.getId()) + "_" + k);
26966                                        columnElement.setIdentifierKey(columnModel.starLinkIdentifier(k));
26967                                        columnElement.setSqlSpelling(columnModel.starLinkSqlSpelling(k));
26968                                        String columnName = starLinkColumnList.get(k);
26969                                        if (containStarColumn(columns, columnModel, k)) {
26970                                                continue;
26971                                        }
26972                                        columnElement.setName(columnName);
26973                                        if (columnModel.getStartPosition() != null && columnModel.getEndPosition() != null) {
26974                                                columnElement.setCoordinate(convertCoordinate(columnModel.getStartPosition()) + ","
26975                                                                + convertCoordinate(columnModel.getEndPosition()));
26976                                        }
26977                                        if (columnModel.getForeignKey()) {
26978                                                columnElement.setForeignKey(columnModel.getForeignKey());
26979                                        }
26980                                        if (columnModel.getIndexKey()) {
26981                                                columnElement.setIndexKey(columnModel.getIndexKey());
26982                                        }
26983                                        if (columnModel.getPrimaryKey()) {
26984                                                columnElement.setPrimaryKey(columnModel.getPrimaryKey());
26985                                        }
26986                                        if (columnModel.getUnqiueKey()) {
26987                                                columnElement.setUnqiueKey(columnModel.getUnqiueKey());
26988                                        }
26989                                        if (columnModel.getDataType() != null) {
26990                                                columnElement.setDataType(columnModel.getDataType());
26991                                        }
26992                                        tableElement.getColumns().add(columnElement);
26993                                }
26994                                if (columnModel.isShowStar()) {
26995                                        column columnElement = new column();
26996                                        columnElement.setId(String.valueOf(columnModel.getId()));
26997                                        columnElement.setName(columnModel.getName());
26998                                        DlineageUtil.publishStarProvenance(columnElement, columnModel);
26999                                        columnElement.setIdentifierKey(columnModel.getIdentifierKey());
27000                                        columnElement.setFieldPathKey(fieldPathMergeKey(columnModel));
27001                                        if (columnModel.getStartPosition() != null && columnModel.getEndPosition() != null) {
27002                                                columnElement.setCoordinate(convertCoordinate(columnModel.getStartPosition()) + ","
27003                                                                + convertCoordinate(columnModel.getEndPosition()));
27004                                        }
27005                                        if (columnModel.getForeignKey()) {
27006                                                columnElement.setForeignKey(columnModel.getForeignKey());
27007                                        }
27008                                        if (columnModel.getIndexKey()) {
27009                                                columnElement.setIndexKey(columnModel.getIndexKey());
27010                                        }
27011                                        if (columnModel.getPrimaryKey()) {
27012                                                columnElement.setPrimaryKey(columnModel.getPrimaryKey());
27013                                        }
27014                                        if (columnModel.getUnqiueKey()) {
27015                                                columnElement.setUnqiueKey(columnModel.getUnqiueKey());
27016                                        }
27017                                        if (columnModel.getDataType() != null) {
27018                                                columnElement.setDataType(columnModel.getDataType());
27019                                        }
27020                                        tableElement.getColumns().add(columnElement);
27021                                }
27022                        } else {
27023                                column columnElement = new column();
27024                                columnElement.setId(String.valueOf(columnModel.getId()));
27025                                columnElement.setName(columnModel.getName());
27026                                DlineageUtil.publishStarProvenance(columnElement, columnModel);
27027                                columnElement.setIdentifierKey(columnModel.getIdentifierKey());
27028                                columnElement.setFieldPathKey(fieldPathMergeKey(columnModel));
27029                                columnElement.setDisplayName(columnModel.getDisplayName());
27030                                if (columnModel.getStartPosition() != null && columnModel.getEndPosition() != null) {
27031                                        columnElement.setCoordinate(convertCoordinate(columnModel.getStartPosition()) + ","
27032                                                        + convertCoordinate(columnModel.getEndPosition()));
27033                                }
27034                                if (columnModel.isPseduo()) {
27035                                        columnElement.setSource("system");
27036                                }
27037                                if (columnModel.getForeignKey()) {
27038                                        columnElement.setForeignKey(columnModel.getForeignKey());
27039                                }
27040                                if (columnModel.getIndexKey()) {
27041                                        columnElement.setIndexKey(columnModel.getIndexKey());
27042                                }
27043                                if (columnModel.getPrimaryKey()) {
27044                                        columnElement.setPrimaryKey(columnModel.getPrimaryKey());
27045                                }
27046                                if (columnModel.getUnqiueKey()) {
27047                                        columnElement.setUnqiueKey(columnModel.getUnqiueKey());
27048                                }
27049                                if (columnModel.getDataType() != null) {
27050                                        columnElement.setDataType(columnModel.getDataType());
27051                                }
27052                                tableElement.getColumns().add(columnElement);
27053                        }
27054                }
27055
27056                TableRelationRows relationRows = tableModel.getRelationRows();
27057                if (relationRows.hasRelation()) {
27058                        column relationRowsElement = new column();
27059                        relationRowsElement.setId(String.valueOf(relationRows.getId()));
27060                        relationRowsElement.setName(relationRows.getName());
27061                        if (relationRows.getStartPosition() != null && relationRows.getEndPosition() != null) {
27062                                relationRowsElement.setCoordinate(convertCoordinate(relationRows.getStartPosition()) + ","
27063                                                + convertCoordinate(relationRows.getEndPosition()));
27064                        }
27065                        relationRowsElement.setSource("system");
27066                        tableElement.getColumns().add(relationRowsElement);
27067                }
27068        }
27069
27070        /** Whether one of {@code columns} publishes the name of the star link at {@code ordinal} of {@code link}. */
27071        private boolean containStarColumn(List<TableColumn> columns, Column link, int ordinal) {
27072                for (TableColumn tableColumn : columns) {
27073                        if (link.starLinkNamesColumn(ordinal, tableColumn)) {
27074                                return true;
27075                        }
27076                }
27077                return false;
27078        }
27079
27080        /** Whether one of the column {@code elements} publishes the name of the star link at {@code ordinal} of {@code link}. */
27081        private boolean containStarColumn(Collection<RelationshipElement<?>> elements, Column link, int ordinal) {
27082                for (RelationshipElement element : elements) {
27083                        if (element.getElement() instanceof TableColumn || element.getElement() instanceof ResultColumn) {
27084                                if (link.starLinkNamesColumn(ordinal, (Column) element.getElement())) {
27085                                        return true;
27086                                }
27087                        }
27088                }
27089                return false;
27090        }
27091
27092        /**
27093         * Returns true if there is a non-star source element matching the column name
27094         * whose parent (table/resultset) does NOT also contribute a star source in
27095         * the same relationship. This identifies "definitive" explicit sources like
27096         * subquery columns (e.g., "coalesce(...) as col_a") vs incidental references
27097         * (e.g., "aTab.id" where aTab.* is also a source).
27098         */
27099        private boolean hasDefinitiveNonStarSource(Collection<RelationshipElement<?>> elements, StarLinkMatcher targetLink) {
27100                // First, collect parent IDs of all star (*) sources
27101                Set<Long> starSourceParentIds = new HashSet<Long>();
27102                for (RelationshipElement<?> element : elements) {
27103                        if (element.getElement() instanceof TableColumn) {
27104                                TableColumn tc = (TableColumn) element.getElement();
27105                                if (tc.isStarColumn() && tc.getTable() != null) {
27106                                        starSourceParentIds.add(tc.getTable().getId());
27107                                }
27108                        } else if (element.getElement() instanceof ResultColumn) {
27109                                ResultColumn rc = (ResultColumn) element.getElement();
27110                                if (rc.isStarColumn() && rc.getResultSet() != null) {
27111                                        starSourceParentIds.add(rc.getResultSet().getId());
27112                                }
27113                        }
27114                }
27115
27116                // Then check if any non-star source matching the column name has a parent
27117                // that does NOT also have a star source
27118                for (RelationshipElement<?> element : elements) {
27119                        if (element.getElement() instanceof TableColumn) {
27120                                TableColumn tc = (TableColumn) element.getElement();
27121                                if (!tc.isStarColumn() && targetLink.isNamedBy(tc)) {
27122                                        if (tc.getTable() != null && !starSourceParentIds.contains(tc.getTable().getId())) {
27123                                                return true;
27124                                        }
27125                                }
27126                        } else if (element.getElement() instanceof ResultColumn) {
27127                                ResultColumn rc = (ResultColumn) element.getElement();
27128                                if (!rc.isStarColumn() && targetLink.isNamedBy(rc)) {
27129                                        if (rc.getResultSet() != null && !starSourceParentIds.contains(rc.getResultSet().getId())) {
27130                                                return true;
27131                                        }
27132                                }
27133                        }
27134                }
27135                return false;
27136        }
27137
27138        /** The USING columns of each SELECT's own FROM clause, collected once per statement and parse. */
27139        private final Map<TSelectSqlStatement, List<TObjectName>> usingColumnsByStatement =
27140                        new IdentityHashMap<TSelectSqlStatement, List<TObjectName>>();
27141
27142        /** resolver2's scopes of the statements being analyzed, or null when resolver2 did not run. */
27143        private ScopeBuildResult scopeBuildResult;
27144
27145        /**
27146         * Whether {@code tableColumn} of {@code sourceTable}, colliding in this {@code SELECT *} with a column of the same
27147         * identity that {@code earlierTables} published, is one column of a {@code JOIN ... USING} of this SELECT: a USING
27148         * of its own FROM clause lists that column, and the join merges every table involved (resolver2's
27149         * {@link gudusoft.gsqlparser.resolver2.scope.SelectScope#findCoveringMerge UsingMerge} participants). A third relation carrying the name stays a
27150         * separate column, and a USING in a subquery merges nothing here.
27151         *
27152         * <p>This used to be the regex {@code (?is).*using\s*\(\s*<name>\s*\).*} over the rendered statement, run for
27153         * every colliding column: it spliced the name in unescaped, saw a USING list only when it held that one column,
27154         * matched text in comments and in subqueries, and compared the published name, which has lost its quotes.
27155         */
27156        private boolean isUsingMerged(TSelectSqlStatement stmt, TableColumn tableColumn, TTable sourceTable,
27157                        List<TTable> earlierTables) {
27158                gudusoft.gsqlparser.resolver2.scope.SelectScope scope = scopeBuildResult == null ? null
27159                                : scopeBuildResult.getScopeForStatement(stmt);
27160                for (TObjectName usingColumn : usingColumnsOf(stmt)) {
27161                        if (!namesSameColumn(usingColumn, tableColumn)) {
27162                                continue;
27163                        }
27164                        if (scope == null) {
27165                                // resolver2 has no scope for this statement: its own USING lists decide alone
27166                                return true;
27167                        }
27168                        List<TTable> candidates = new ArrayList<TTable>(earlierTables);
27169                        candidates.add(sourceTable);
27170                        // the spelling resolver2 recorded for this USING column, so the merge is found by its participants
27171                        String spelling = usingColumn.getColumnNameOnly();
27172                        if (spelling == null || spelling.isEmpty()) {
27173                                spelling = usingColumn.toString();
27174                        }
27175                        if (scope.findCoveringMerge(option.getVendor(), spelling, candidates) != null) {
27176                                return true;
27177                        }
27178                }
27179                return false;
27180        }
27181
27182        /** A USING column and a table column are one column when their identities are, else when their names are. */
27183        private static boolean namesSameColumn(TObjectName usingColumn, TableColumn tableColumn) {
27184                Object key = tableColumn.getIdentifierKey();
27185                Object usingKey = DlineageUtil.columnKey(usingColumn);
27186                if (key != null && usingKey != null) {
27187                        return key.equals(usingKey);
27188                }
27189                return DlineageUtil.sameColumnName(tableColumn.getName(), usingColumn);
27190        }
27191
27192        /** The USING columns of the joins of {@code stmt}'s own FROM clause, not of its subqueries. */
27193        private List<TObjectName> usingColumnsOf(TSelectSqlStatement stmt) {
27194                List<TObjectName> columns = usingColumnsByStatement.get(stmt);
27195                if (columns != null) {
27196                        return columns;
27197                }
27198                columns = new ArrayList<TObjectName>();
27199                if (stmt.joins != null) {
27200                        for (int i = 0; i < stmt.joins.size(); i++) {
27201                                TJoin join = stmt.joins.getJoin(i);
27202                                if (join == null || join.getJoinItems() == null) {
27203                                        continue;
27204                                }
27205                                for (int j = 0; j < join.getJoinItems().size(); j++) {
27206                                        TJoinItem item = join.getJoinItems().getJoinItem(j);
27207                                        if (item != null) {
27208                                                addUsingColumns(columns, item.getUsingColumns());
27209                                        }
27210                                }
27211                        }
27212                }
27213                Deque<TTable> pending = new ArrayDeque<TTable>();
27214                if (stmt.getRelations() != null) {
27215                        pending.addAll(stmt.getRelations());
27216                }
27217                while (!pending.isEmpty()) {
27218                        TTable relation = pending.pop();
27219                        if (relation == null || relation.getTableType() != ETableSource.join || relation.getJoinExpr() == null) {
27220                                continue;
27221                        }
27222                        TJoinExpr join = relation.getJoinExpr();
27223                        addUsingColumns(columns, join.getUsingColumns());
27224                        if (join.getLeftTable() != null) {
27225                                pending.push(join.getLeftTable());
27226                        }
27227                        if (join.getRightTable() != null) {
27228                                pending.push(join.getRightTable());
27229                        }
27230                }
27231                usingColumnsByStatement.put(stmt, columns);
27232                return columns;
27233        }
27234
27235        private static void addUsingColumns(List<TObjectName> found, TObjectNameList columns) {
27236                if (columns == null) {
27237                        return;
27238                }
27239                for (int i = 0; i < columns.size(); i++) {
27240                        found.add(columns.getObjectName(i));
27241                }
27242        }
27243
27244        private void analyzeSelectStmt(TSelectSqlStatement stmt) {
27245                // SQL-LR: a nested analysis never inherits the reference of the loop that called it
27246                SqllrObservations sqllr = modelManager.getSqllrObservations();
27247                int sqllrBarrier = sqllr == null ? -1 : sqllr.openFrame();
27248                try {
27249                        analyzeSelectStmtInSqllrBarrier(stmt);
27250                } finally {
27251                        if (sqllr != null) {
27252                                sqllr.closeFrame(sqllrBarrier);
27253                        }
27254                }
27255        }
27256
27257        private void analyzeSelectStmtInSqllrBarrier(TSelectSqlStatement stmt) {
27258                if (!accessedSubqueries.contains(stmt)) {
27259                        accessedSubqueries.add(stmt);
27260                } else {
27261                        if (modelManager.getModel(stmt) != null) {
27262                                return;
27263                        }
27264                }
27265
27266                // ClickHouse external table functions must be modelled BEFORE the
27267                // select-list relations are analyzed: the column path resolves its
27268                // source through modelManager.getModel(table), and without an early
27269                // binding it materializes a generic function node and the external
27270                // upstream silently vanishes.
27271                if (option.getVendor() == EDbVendor.dbvclickhouse && stmt.getRelations() != null) {
27272                        for (TTable lcTable : stmt.getRelations()) {
27273                                TFunctionCall lcFuncCall = lcTable.getFuncCall();
27274                                if (lcFuncCall == null && lcTable.getTableExpr() != null) {
27275                                        lcFuncCall = lcTable.getTableExpr().getFunctionCall();
27276                                }
27277                                if (lcFuncCall != null) {
27278                                        modelClickhouseExternalTableFunction(lcTable, lcFuncCall);
27279                                }
27280                        }
27281                }
27282
27283                if (stmt.getParentStmt() == null && stmt.getIntoClause() == null && stmt.getIntoTableClause() == null) {
27284                        if(option.isIgnoreTopSelect() && (option.isIgnoreRecordSet() || option.isSimpleOutput())){
27285                                if(option.getAnalyzeMode() == null || option.getAnalyzeMode() == AnalyzeMode.dataflow){
27286                                        return;
27287                                }
27288                        }
27289                }
27290
27291                if (stmt.getSetOperatorType() != ESetOperatorType.none) {
27292
27293                        // Iteratively analyze all descendant UNION branches before processing this node.
27294                        // Uses iterative post-order traversal to preserve the original left-right-self
27295                        // processing order, avoiding StackOverflow with deeply nested UNION trees.
27296                        {
27297                                Deque<TSelectSqlStatement> stack1 = new ArrayDeque<>();
27298                                List<TSelectSqlStatement> postOrder = new ArrayList<>();
27299                                stack1.push(stmt);
27300                                while (!stack1.isEmpty()) {
27301                                        TSelectSqlStatement node = stack1.pop();
27302                                        postOrder.add(node);
27303                                        if (node.getSetOperatorType() != ESetOperatorType.none) {
27304                                                // Push left first, then right, so that after reversal left comes first
27305                                                if (node.getLeftStmt() != null) stack1.push(node.getLeftStmt());
27306                                                if (node.getRightStmt() != null) stack1.push(node.getRightStmt());
27307                                        }
27308                                }
27309                                Collections.reverse(postOrder);
27310                                // Process all descendants in post-order, skip stmt itself (processed below)
27311                                for (int pi = 0; pi < postOrder.size() - 1; pi++) {
27312                                        TSelectSqlStatement node = postOrder.get(pi);
27313                                        if (!accessedStatements.contains(node)) {
27314                                                accessedStatements.add(node);
27315                                                analyzeSelectStmt(node);
27316                                        }
27317                                }
27318                        }
27319
27320                        stmtStack.push(stmt);
27321                        SelectSetResultSet resultSet = modelFactory.createSelectSetResultSet(stmt);
27322
27323                        ResultSet leftResultSetModel = (ResultSet) modelManager.getModel(stmt.getLeftStmt());
27324                        if (leftResultSetModel != null && leftResultSetModel != resultSet
27325                                        && !leftResultSetModel.getRelationRows().getHoldRelations().isEmpty()) {
27326                                ImpactRelationship impactRelation = modelFactory.createImpactRelation();
27327                                impactRelation.setEffectType(EffectType.select);
27328                                impactRelation.addSource(new RelationRowsRelationshipElement<ResultSetRelationRows>(
27329                                                leftResultSetModel.getRelationRows()));
27330                                impactRelation.setTarget(
27331                                                new RelationRowsRelationshipElement<ResultSetRelationRows>(resultSet.getRelationRows()));
27332                        }
27333                        
27334                        ResultSet rightResultSetModel = (ResultSet) modelManager.getModel(stmt.getRightStmt());
27335                        if (rightResultSetModel != null && rightResultSetModel != resultSet
27336                                        && !rightResultSetModel.getRelationRows().getHoldRelations().isEmpty()) {
27337                                ImpactRelationship impactRelation = modelFactory.createImpactRelation();
27338                                impactRelation.setEffectType(EffectType.select);
27339                                impactRelation.addSource(new RelationRowsRelationshipElement<ResultSetRelationRows>(
27340                                                rightResultSetModel.getRelationRows()));
27341                                impactRelation.setTarget(
27342                                                new RelationRowsRelationshipElement<ResultSetRelationRows>(resultSet.getRelationRows()));
27343                        }
27344                        
27345                        if ((leftResultSetModel != null && leftResultSetModel.isDetermined())
27346                                        || (rightResultSetModel != null && rightResultSetModel.isDetermined())) {
27347                                resultSet.setDetermined(true);
27348                        }
27349                        
27350                        if (resultSet.getColumns() == null || resultSet.getColumns().isEmpty()) {
27351                                if (getResultColumnList(stmt.getLeftStmt()) != null) {
27352                                        createSelectSetResultColumns(resultSet, stmt.getLeftStmt());
27353                                } else if (getResultColumnList(stmt.getRightStmt()) != null) {
27354                                        createSelectSetResultColumns(resultSet, stmt.getRightStmt());
27355                                }
27356                        }
27357
27358                        List<ResultColumn> columns = resultSet.getColumns();
27359                        // a star of unknown width: each column reads the columns of each branch that can stand at its position
27360                        // (Mantis 4914)
27361                        List<List<List<ResultColumn>>> spanSources = setOperationSpanSources(stmt, columns);
27362                        // a star of the set operation names positions, and another branch's column may name one of them: such a
27363                        // name is no name of a branch's star, so the star's names are passed on to the stars of its branches only
27364                        // when every branch is one star of the same tables (Mantis 4974)
27365                        boolean passNames = spanSources != null && branchesReadTheSameTables(stmt);
27366                        for (int i = 0; i < columns.size(); i++) {
27367                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
27368                                relation.setEffectType(EffectType.select);
27369                                relation.setTarget(new ResultColumnRelationshipElement(columns.get(i)));
27370
27371                                if (spanSources != null) {
27372                                        List<ResultColumn> recorded = new ArrayList<ResultColumn>();
27373                                        for (List<List<ResultColumn>> branchSources : spanSources) {
27374                                                for (ResultColumn column : branchSources.get(i)) {
27375                                                        relation.addSource(new ResultColumnRelationshipElement(column), passNames);
27376                                                        recorded.add(column);
27377                                                }
27378                                        }
27379                                        setColumnSources.put(columns.get(i), recorded);
27380                                        continue;
27381                                }
27382
27383                                if (!stmt.getLeftStmt().isCombinedQuery()) {
27384                                        ResultSet sourceResultSet = (ResultSet) modelManager
27385                                                        .getModel(stmt.getLeftStmt().getResultColumnList());
27386                                        if (sourceResultSet!=null && sourceResultSet.getColumns().size() > i) {
27387                                                if (columns.get(i).isStarColumn()) {
27388                                                        for (ResultColumn column : sourceResultSet.getColumns()) {
27389                                                                relation.addSource(new ResultColumnRelationshipElement(column));
27390                                                        }
27391                                                } else {
27392                                                        relation.addSource(
27393                                                                        new ResultColumnRelationshipElement(sourceResultSet.getColumns().get(i)));
27394                                                }
27395                                        }
27396                                } else {
27397                                        ResultSet sourceResultSet = (ResultSet) modelManager.getModel(stmt.getLeftStmt());
27398                                        if (sourceResultSet != null && sourceResultSet.getColumns().size() > i) {
27399                                                if (columns.get(i).isStarColumn()) {
27400                                                        for (ResultColumn column : sourceResultSet.getColumns()) {
27401                                                                relation.addSource(new ResultColumnRelationshipElement(column));
27402                                                        }
27403                                                } else {
27404                                                        relation.addSource(
27405                                                                        new ResultColumnRelationshipElement(sourceResultSet.getColumns().get(i)));
27406                                                }
27407                                        }
27408                                }
27409
27410                                if (!stmt.getRightStmt().isCombinedQuery()) {
27411                                        ResultSet sourceResultSet = (ResultSet) modelManager
27412                                                        .getModel(stmt.getRightStmt().getResultColumnList());
27413                                        if (sourceResultSet != null && sourceResultSet.getColumns().size() > i) {
27414                                                if (columns.get(i).isStarColumn()) {
27415                                                        for (ResultColumn column : sourceResultSet.getColumns()) {
27416                                                                relation.addSource(new ResultColumnRelationshipElement(column));
27417                                                        }
27418                                                } else {
27419                                                        relation.addSource(
27420                                                                        new ResultColumnRelationshipElement(sourceResultSet.getColumns().get(i)));
27421                                                }
27422                                        } else if (sourceResultSet != null) {
27423                                                for (ResultColumn column : sourceResultSet.getColumns()) {
27424                                                        if (column.hasStarLinkColumn()) {
27425                                                                relation.addSource(new ResultColumnRelationshipElement(column));
27426                                                        }
27427                                                }
27428                                        }
27429                                } else {
27430                                        ResultSet sourceResultSet = (ResultSet) modelManager.getModel(stmt.getRightStmt());
27431                                        if (sourceResultSet != null && sourceResultSet.getColumns().size() > i) {
27432                                                relation.addSource(new ResultColumnRelationshipElement(sourceResultSet.getColumns().get(i)));
27433                                        } else if (sourceResultSet != null) {
27434                                                for (ResultColumn column : sourceResultSet.getColumns()) {
27435                                                        if (column.hasStarLinkColumn()) {
27436                                                                relation.addSource(new ResultColumnRelationshipElement(column));
27437                                                        }
27438                                                }
27439                                        }
27440                                }
27441                        }
27442                        
27443                        for (int i = 0; i < columns.size(); i++) {
27444                                // rule R8: the fields a branch's STRUCT has are fields of the set operation's column; a branch
27445                                // without them feeds the column itself, as above (plan acceptance row 26)
27446                                if (spanSources != null) {
27447                                        for (List<List<ResultColumn>> branchSources : spanSources) {
27448                                                if (branchSources.get(i).size() == 1 && !branchSources.get(i).get(0).isStarColumn()) {
27449                                                        setOperationFieldNodes(columns.get(i), branchSources.get(i).get(0));
27450                                                }
27451                                        }
27452                                        continue;
27453                                }
27454                                setOperationFieldNodes(columns.get(i), setOperationBranchColumn(stmt.getLeftStmt(), i));
27455                                setOperationFieldNodes(columns.get(i), setOperationBranchColumn(stmt.getRightStmt(), i));
27456                        }
27457
27458                        analyzeSelectIntoClause(stmt);
27459                        
27460                        stmtStack.pop();
27461                } else {
27462
27463                        // handle hive stmt, issue_id I3SGZB
27464                        if (stmt.getHiveBodyList() != null && stmt.getHiveBodyList().size() > 0) {
27465                                stmtStack.push(stmt);
27466                                hiveFromTables = stmt.tables;
27467                                if (hiveFromTables != null) {
27468                                        for (int i = 0; i < hiveFromTables.size(); i++) {
27469                                                if (hiveFromTables.getTable(i).getTableType() == ETableSource.lateralView) {
27470                                                        // one relation for every INSERT body, modelled with the first (analyzeLateralViewRelation)
27471                                                        continue;
27472                                                }
27473                                                modelFactory.createTable(hiveFromTables.getTable(i));
27474                                        }
27475                                }
27476                                for (int i = 0; i < stmt.getHiveBodyList().size(); i++) {
27477                                        analyzeCustomSqlStmt(stmt.getHiveBodyList().get(i));
27478                                }
27479                                stmtStack.pop();
27480                                return;
27481                        }
27482                        
27483                        if (stmt.getTransformClause() != null) {
27484                                analyzeHiveTransformClause(stmt, stmt.getTransformClause());
27485                                return;
27486                        }
27487
27488                        if (stmt.getResultColumnList() == null) {
27489                                return;
27490                        }
27491
27492                        stmtStack.push(stmt);
27493
27494                        TTableList fromTables = stmt.tables;
27495                        if ((fromTables == null || fromTables.size() == 0)
27496                                        && (hiveFromTables != null && hiveFromTables.size() > 0)) {
27497                                fromTables = hiveFromTables;
27498                        }
27499
27500                        for (int i = 0; i < fromTables.size(); i++) {
27501                                TTable table = fromTables.getTable(i);
27502                                if (table.getUnnestClause() != null && gudusoft.gsqlparser.nodes.structured.NestedAccessProfile
27503                                                .forVendor(option.getVendor()).usesPrestoUnnestPath()) {
27504                                        analyzeTableSubquery(table);
27505                                        analyzePrestoUnnest(stmt, table);
27506                                        stmtStack.pop();
27507                                        return;
27508                                }
27509                                if (table.getUnnestClause() != null && gudusoft.gsqlparser.nodes.structured.NestedAccessProfile
27510                                                .forVendor(option.getVendor()).getUnnestExpansion()
27511                                                == gudusoft.gsqlparser.nodes.structured.NestedAccessProfile.UnnestExpansion.VALUE_TABLE) {
27512                                        analyzeBigQueryUnnest(stmt, table);
27513                                }
27514                        }
27515                        
27516                        //用来获取 pivotedTable 对应的columns
27517                        TPivotedTable pivotedTable = null; 
27518                        if (stmt.getJoins() != null && stmt.getJoins().size() > 0) {
27519                                for (TJoin join : stmt.getJoins()) {
27520                                        if (join.getTable() != null && join.getTable().getPivotedTable() != null) {
27521                                                if (isUnPivotedTable(join.getTable().getPivotedTable())) {
27522                                                        analyzeUnPivotedTable(stmt, join.getTable().getPivotedTable());
27523                                                        pivotedTable = join.getTable().getPivotedTable();
27524                                                } else {
27525                                                        analyzePivotedTable(stmt, join.getTable().getPivotedTable());
27526                                                        pivotedTable = join.getTable().getPivotedTable();
27527                                                }
27528                                        }
27529                                }
27530                        }
27531
27532                        for (int i = 0; i < fromTables.size(); i++) {
27533                                TTable table = fromTables.getTable(i);
27534                                if (table.getTableType() == ETableSource.lateralView) {
27535                                        // modelled below, once every relation to its left is (analyzeLateralViewRelation)
27536                                        continue;
27537                                }
27538                                // Handle TABLE(func()) which has tableType=tableExpr and funcCall=null
27539                                if (table.getTableType() == ETableSource.tableExpr && table.getFuncCall() == null && pipelinedAnalyzer != null) {
27540                                        try {
27541                                                pipelinedAnalyzer.tryStitchTableExpr(table);
27542                                        } catch (Exception e) {
27543                                                // Don't let pipelined stitching failure break main flow
27544                                        }
27545                                }
27546
27547                                if (table.getFuncCall() != null || (table.getTableExpr()!=null && table.getTableExpr().getFunctionCall()!=null)) {
27548                                        TFunctionCall functionCall = table.getFuncCall();
27549                                        if (functionCall == null) {
27550                                                functionCall = table.getTableExpr().getFunctionCall();
27551                                        }
27552                                        Procedure callee = modelManager.getProcedure(callAliasKey(functionCall));
27553                                        if (callee == null && analyzeProcedureDefinition(callAliasKey(functionCall))) {
27554                                                callee = modelManager.getProcedure(callAliasKey(functionCall));
27555                                        }
27556
27557                                        if (gudusoft.gsqlparser.dlineage.routine.RoutineShadow.enabled()) shadowCallee("tableFunction", functionCall.getFunctionName(), callee, new Integer[] {gudusoft.gsqlparser.dlineage.routine.RoutineShadow.countAliasOf(functionCall)}, gudusoft.gsqlparser.catalog.runtime.CatalogObjectKind.FUNCTION);
27558                                        if(callee!=null) {
27559                                                if (callee.getArguments() != null) {
27560                                                        for (int j = 0; j < callee.getArguments().size(); j++) {
27561                                                                Argument argument = callee.getArguments().get(j);
27562                                                                Variable variable = resolveFormalVariable(callee, argument);
27563                                                                if(variable!=null) {
27564                                                                        if (argument.getMode() == EParameterMode.out || argument.getMode() == EParameterMode.output) {
27565                                                                                Transform transform = new Transform();
27566                                                                                transform.setType(Transform.FUNCTION);
27567                                                                                transform.setCode(functionCall);
27568                                                                                compositeBindingColumn(variable).setTransform(transform);
27569                                                                        }
27570                                                                        Process process = modelFactory.createProcess(functionCall);
27571                                                                        variable.addProcess(process);
27572                                                                        analyzeFunctionArgumentsDataFlowRelation(compositeBindingColumn(variable), functionCall, j, process);
27573                                                                }
27574                                                        }
27575                                                }
27576                                                Set<Object> functionTableModelObjs = modelManager.getFunctionResults(nameAliasKey(functionCall.getFunctionName(), null));
27577                                                if (functionTableModelObjs != null) {
27578                                                        modelManager.bindModel(table, functionTableModelObjs.iterator().next());
27579                                                }
27580                                                // Try pipelined function stitching
27581                                                if (pipelinedAnalyzer != null) {
27582                                                        try {
27583                                                                pipelinedAnalyzer.tryStitchCallSite(table, functionCall);
27584                                                        } catch (Exception e) {
27585                                                                // Don't let pipelined stitching failure break main flow
27586                                                        }
27587                                                }
27588                                                continue;
27589                                        } else {
27590                                                Set<Object> functionTableModelObjs = modelManager.getFunctionResults(nameAliasKey(functionCall.getFunctionName(), null));
27591                                                if (functionTableModelObjs == null) {
27592                                                        // ClickHouse external table functions expose real
27593                                                        // external data; a generic function node silently
27594                                                        // dropped their upstream (missing lineage is
27595                                                        // invisible to the user — the correctness ruling).
27596                                                        if (modelClickhouseExternalTableFunction(table, functionCall)) {
27597                                                                continue;
27598                                                        }
27599//                                                      Procedure procedure = modelManager.getProcedureByName(DlineageUtil
27600//                                                                      .getIdentifierNormalTableName(functionCall.getFunctionName().toString()));
27601//                                                      if (procedure != null) {
27602                                                                createFunction(functionCall);
27603                                                                continue;
27604//                                                      }
27605                                                }
27606                                                if (functionTableModelObjs!=null && functionTableModelObjs.iterator().next() instanceof Table) {
27607                                                        Table functionTableModel = (Table) functionTableModelObjs.iterator().next();
27608                                                        if (functionTableModel.getColumns() != null) {
27609                                                                Table functionTable = modelFactory.createTableFromCreateDDL(table, true);
27610                                                                if (functionTable == functionTableModel)
27611                                                                        continue;
27612                                                                for (int j = 0; j < functionTableModel.getColumns().size(); j++) {
27613                                                                        TableColumn column = modelFactory.createTableColumn(functionTable,
27614                                                                                        functionTableModel.getColumns().get(j).getColumnObject(), true);
27615                                                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
27616                                                                        relation.setEffectType(EffectType.select);
27617                                                                        relation.setTarget(new TableColumnRelationshipElement(column));
27618                                                                        relation.addSource(
27619                                                                                        new TableColumnRelationshipElement(functionTableModel.getColumns().get(j)));
27620                                                                }
27621                                                        }
27622                                                } else if (functionTableModelObjs!=null && functionTableModelObjs.iterator().next() instanceof ResultSet) {
27623                                                        ResultSet functionTableModel = (ResultSet) functionTableModelObjs.iterator().next();
27624                                                        if (functionTableModel.getColumns() != null) {
27625                                                                Table functionTable = modelFactory.createTableFromCreateDDL(table, true);
27626                                                                for (int j = 0; j < functionTableModel.getColumns().size(); j++) {
27627                                                                        TParseTreeNode columnObj = functionTableModel.getColumns().get(j).getColumnObject();
27628                                                                        if (columnObj instanceof TObjectName) {
27629                                                                                TableColumn column = modelFactory.createTableColumn(functionTable,
27630                                                                                                (TObjectName) columnObj, true);
27631                                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
27632                                                                                relation.setEffectType(EffectType.select);
27633                                                                                relation.setTarget(new TableColumnRelationshipElement(column));
27634                                                                                relation.addSource(new ResultColumnRelationshipElement(
27635                                                                                                functionTableModel.getColumns().get(j)));
27636                                                                        } else if (columnObj instanceof TResultColumn) {
27637                                                                                TableColumn column = modelFactory.createTableColumn(functionTable,
27638                                                                                                (TResultColumn) columnObj);
27639                                                                                if (column == null) {
27640                                                                                        // a function result column the table model cannot name (an
27641                                                                                        // unaliased expression): no relation instead of a null target
27642                                                                                        continue;
27643                                                                                }
27644                                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
27645                                                                                relation.setEffectType(EffectType.select);
27646                                                                                relation.setTarget(new TableColumnRelationshipElement(column));
27647                                                                                relation.addSource(new ResultColumnRelationshipElement(
27648                                                                                                functionTableModel.getColumns().get(j)));
27649                                                                        }
27650                                                                }
27651                                                        }
27652                                                }
27653                                                // Try pipelined function stitching for unresolved function calls
27654                                                if (pipelinedAnalyzer != null) {
27655                                                        try {
27656                                                                pipelinedAnalyzer.tryStitchCallSite(table, functionCall);
27657                                                        } catch (Exception e) {
27658                                                                // Don't let pipelined stitching failure break main flow
27659                                                        }
27660                                                }
27661                                        }
27662                                }
27663
27664                                if (table.getPartitionExtensionClause() != null
27665                                                && table.getPartitionExtensionClause().getKeyValues() != null) {
27666                                        TExpressionList values = table.getPartitionExtensionClause().getKeyValues();
27667                                        Table tableModel = modelFactory.createTable(table);
27668                                        for (TExpression value : values) {
27669                                                if (value.getExpressionType() == EExpressionType.simple_constant_t) {
27670                                                        ImpactRelationship impactRelation = modelFactory.createImpactRelation();
27671                                                        impactRelation.setEffectType(EffectType.select);
27672                                                        Table constantTable = modelFactory.createConstantsTable(stmtStack.peek());
27673                                                        TableColumn constantColumn = modelFactory.createTableColumn(constantTable, value.getConstantOperand());
27674                                                        impactRelation.addSource(new TableColumnRelationshipElement(constantColumn));
27675                                                        impactRelation.setTarget(new RelationRowsRelationshipElement<TableRelationRows>(
27676                                                                        tableModel.getRelationRows()));
27677                                                }
27678                                        }
27679                                }
27680
27681                                if (table.getSubquery() != null) {
27682                                        QueryTable queryTable = modelFactory.createQueryTable(table);
27683                                        TSelectSqlStatement subquery = table.getSubquery();
27684                                        analyzeSelectStmt(subquery);
27685
27686                                        ResultSet resultSetModel = (ResultSet) modelManager.getModel(subquery);
27687                                        if (resultSetModel != null && resultSetModel.isDetermined()) {
27688                                                queryTable.setDetermined(true);
27689                                        }
27690
27691                                        if (resultSetModel != null && resultSetModel != queryTable
27692                                                        && !resultSetModel.getRelationRows().getHoldRelations().isEmpty()) {
27693                                                ImpactRelationship impactRelation = modelFactory.createImpactRelation();
27694                                                impactRelation.setEffectType(EffectType.select);
27695                                                impactRelation.addSource(new RelationRowsRelationshipElement<ResultSetRelationRows>(
27696                                                                resultSetModel.getRelationRows()));
27697                                                impactRelation.setTarget(new RelationRowsRelationshipElement<ResultSetRelationRows>(
27698                                                                queryTable.getRelationRows()));
27699                                        }
27700
27701                                        // a set operation with a star of unknown width: each column of the alias list reads, from each
27702                                        // select, the items that can stand at its position (Mantis 4914)
27703                                        List<SelectPositions> setSelects = resultSetModel != null && resultSetModel != queryTable
27704                                                        && subquery.isCombinedQuery() && queryTable.getTableObject().getAliasClause() != null
27705                                                        && queryTable.getTableObject().getAliasClause().getColumns() != null
27706                                                        ? setOperationPositions(subquery, queryTable.getColumns().size(),
27707                                                                        shorterList(queryTable.getTableObject().getAliasClause().getColumns(),
27708                                                                                        queryTable.getTableObject())) : null;
27709                                        if (setSelects != null) {
27710                                                mapCteColumnsByCandidates(queryTable, setSelects,
27711                                                                queryTable.getTableObject().getAliasClause().getColumns());
27712                                        } else if (resultSetModel != null && resultSetModel != queryTable
27713                                                        && queryTable.getTableObject().getAliasClause() != null
27714                                                        && queryTable.getTableObject().getAliasClause().getColumns() != null) {
27715                                                int resultSetSize = resultSetModel.getColumns().size();
27716                                                ResultColumn lastColumn = resultSetSize == 0 ? null : resultSetModel.getColumns().get(resultSetSize - 1);
27717                                                // the names of FROM (SELECT * FROM t) d(a, b) past a star whose columns are not known are
27718                                                // columns of that star too (Mantis 4843)
27719                                                int aliasCount = lastColumn != null && lastColumn.isStarColumn()
27720                                                                ? queryTable.getTableObject().getAliasClause().getColumns().size() : resultSetSize;
27721                                                for (int j = 0; j < queryTable.getColumns().size()
27722                                                                && j < Math.max(resultSetSize, aliasCount); j++) {
27723                                                        ResultColumn sourceColumn = resultSetModel.getColumns().get(Math.min(j, resultSetSize - 1));
27724                                                        ResultColumn targetColumn = queryTable.getColumns().get(j);
27725
27726                                                        DataFlowRelationship queryRalation = modelFactory.createDataFlowRelation();
27727                                                        queryRalation.setEffectType(EffectType.select);
27728                                                        queryRalation.setTarget(new ResultColumnRelationshipElement(targetColumn));
27729                                                        queryRalation.addSource(new ResultColumnRelationshipElement(sourceColumn));
27730                                                }
27731                                                // FROM (SELECT c1, c2, c3 ...) d(a): a list shorter than the columns renames the first ones, the others
27732                                                // keep their names (PostgreSQL), in order (Mantis 4873). Past a star of unknown width no column has a
27733                                                // proven position, and a name the derived table already has is ambiguous: neither gets a column.
27734                                                int listed = queryTable.getTableObject().getAliasClause().getColumns().size();
27735                                                for (int j = 0; j < resultSetSize && !resultSetModel.getColumns().get(j).isStarColumn(); j++) {
27736                                                        ResultColumn sourceColumn = resultSetModel.getColumns().get(j);
27737                                                        if (j >= listed && !namedIn(queryTable, sourceColumn.getName())) {
27738                                                                derivedColumnPastList(queryTable, sourceColumn);
27739                                                        }
27740                                                }
27741                                        } else if (subquery.getSetOperatorType() != ESetOperatorType.none) {
27742                                                SelectSetResultSet selectSetResultSetModel = (SelectSetResultSet) modelManager
27743                                                                .getModel(subquery);
27744                                                for (int j = 0; j < selectSetResultSetModel.getColumns().size(); j++) {
27745                                                        ResultColumn sourceColumn = selectSetResultSetModel.getColumns().get(j);
27746                                                        ResultColumn targetColumn = modelFactory.createSelectSetResultColumn(queryTable,
27747                                                                        sourceColumn);
27748                                                        for (TObjectName starLinkColumn : sourceColumn.getStarLinkColumnList()) {
27749                                                                targetColumn.bindStarLinkColumn(starLinkColumn);
27750                                                        }
27751                                                        DataFlowRelationship selectSetRalation = modelFactory.createDataFlowRelation();
27752                                                        selectSetRalation.setEffectType(EffectType.select);
27753                                                        selectSetRalation.setTarget(new ResultColumnRelationshipElement(targetColumn));
27754                                                        selectSetRalation.addSource(new ResultColumnRelationshipElement(sourceColumn));
27755                                                }
27756                                        } else if (resultSetModel == null && subquery.getValueClause() != null) {
27757                                                analyzeValuesDerivedTable(queryTable, subquery.getValueClause());
27758                                        }
27759                                } else if (table.getOutputMerge() != null) {
27760                                        QueryTable queryTable = modelFactory.createQueryTable(table);
27761                                        TMergeSqlStatement subquery = table.getOutputMerge();
27762                                        analyzeMergeStmt(subquery);
27763
27764                                        for (TResultColumn column : subquery.getOutputClause().getSelectItemList()) {
27765                                                modelFactory.createResultColumn(queryTable, column);
27766                                                analyzeResultColumn(column, EffectType.select);
27767                                        }
27768
27769                                        ResultSet resultSetModel = (ResultSet) modelManager
27770                                                        .getModel(subquery.getOutputClause().getSelectItemList());
27771
27772                                        if (resultSetModel != null && resultSetModel != queryTable
27773                                                        && !resultSetModel.getRelationRows().getHoldRelations().isEmpty()) {
27774                                                ImpactRelationship impactRelation = modelFactory.createImpactRelation();
27775                                                impactRelation.setEffectType(EffectType.select);
27776                                                impactRelation.addSource(new RelationRowsRelationshipElement<ResultSetRelationRows>(
27777                                                                resultSetModel.getRelationRows()));
27778                                                impactRelation.setTarget(new RelationRowsRelationshipElement<ResultSetRelationRows>(
27779                                                                queryTable.getRelationRows()));
27780                                        }
27781
27782                                        if (resultSetModel != null && resultSetModel != queryTable
27783                                                        && queryTable.getTableObject().getAliasClause() != null
27784                                                        && queryTable.getTableObject().getAliasClause().getColumns() != null) {
27785                                                for (int j = 0; j < queryTable.getColumns().size()
27786                                                                && j < resultSetModel.getColumns().size(); j++) {
27787                                                        ResultColumn sourceColumn = resultSetModel.getColumns().get(j);
27788                                                        ResultColumn targetColumn = queryTable.getColumns().get(j);
27789
27790                                                        DataFlowRelationship queryRalation = modelFactory.createDataFlowRelation();
27791                                                        queryRalation.setEffectType(EffectType.select);
27792                                                        queryRalation.setTarget(new ResultColumnRelationshipElement(targetColumn));
27793                                                        queryRalation.addSource(new ResultColumnRelationshipElement(sourceColumn));
27794                                                }
27795                                        }
27796                                } else if (table.getTableExpr() != null && table.getTableExpr().getSubQuery() != null) {
27797                                        QueryTable queryTable = modelFactory.createQueryTable(table);
27798                                        TSelectSqlStatement subquery = table.getTableExpr().getSubQuery();
27799                                        analyzeSelectStmt(subquery);
27800
27801                                        ResultSet resultSetModel = (ResultSet) modelManager.getModel(subquery);
27802
27803                                        if (resultSetModel != null && resultSetModel != queryTable
27804                                                        && !resultSetModel.getRelationRows().getHoldRelations().isEmpty()) {
27805                                                ImpactRelationship impactRelation = modelFactory.createImpactRelation();
27806                                                impactRelation.setEffectType(EffectType.select);
27807                                                impactRelation.addSource(new RelationRowsRelationshipElement<ResultSetRelationRows>(
27808                                                                resultSetModel.getRelationRows()));
27809                                                impactRelation.setTarget(new RelationRowsRelationshipElement<ResultSetRelationRows>(
27810                                                                queryTable.getRelationRows()));
27811                                        }
27812
27813                                        if (resultSetModel != null && resultSetModel != queryTable
27814                                                        && queryTable.getTableObject().getAliasClause() != null) {
27815                                                for (int j = 0; j < resultSetModel.getColumns().size(); j++) {
27816                                                        ResultColumn sourceColumn = resultSetModel.getColumns().get(j);
27817                                                        ResultColumn targetColumn = modelFactory.createSelectSetResultColumn(queryTable,
27818                                                                        sourceColumn);
27819
27820                                                        DataFlowRelationship queryRalation = modelFactory.createDataFlowRelation();
27821                                                        queryRalation.setEffectType(EffectType.select);
27822                                                        queryRalation.setTarget(new ResultColumnRelationshipElement(targetColumn));
27823                                                        queryRalation.addSource(new ResultColumnRelationshipElement(sourceColumn));
27824                                                }
27825                                        } else if (subquery.getSetOperatorType() != ESetOperatorType.none) {
27826                                                SelectSetResultSet selectSetResultSetModel = (SelectSetResultSet) modelManager
27827                                                                .getModel(subquery);
27828                                                for (int j = 0; j < selectSetResultSetModel.getColumns().size(); j++) {
27829                                                        ResultColumn sourceColumn = selectSetResultSetModel.getColumns().get(j);
27830                                                        ResultColumn targetColumn = modelFactory.createSelectSetResultColumn(queryTable,
27831                                                                        sourceColumn);
27832                                                        for (TObjectName starLinkColumn : sourceColumn.getStarLinkColumnList()) {
27833                                                                targetColumn.bindStarLinkColumn(starLinkColumn);
27834                                                        }
27835                                                        DataFlowRelationship selectSetRalation = modelFactory.createDataFlowRelation();
27836                                                        selectSetRalation.setEffectType(EffectType.select);
27837                                                        selectSetRalation.setTarget(new ResultColumnRelationshipElement(targetColumn));
27838                                                        selectSetRalation.addSource(new ResultColumnRelationshipElement(sourceColumn));
27839                                                }
27840                                        }
27841                                } else if (table.getCTE() != null) {
27842                                        QueryTable queryTable = modelFactory.createQueryTable(table);
27843
27844                                        TObjectNameList cteColumns = table.getCTE().getColumnList();
27845                                        if (cteColumns != null) {
27846                                                for (int j = 0; j < cteColumns.size(); j++) {
27847                                                        modelFactory.createResultColumn(queryTable, cteColumns.getObjectName(j));
27848                                                }
27849                                                queryTable.setDetermined(true);
27850                                        }
27851                                        TSelectSqlStatement subquery = table.getCTE().getSubquery();
27852                                        if (subquery != null && !stmtStack.contains(subquery) && subquery.getResultColumnList() != null) {
27853                                                analyzeSelectStmt(subquery);
27854
27855                                                ResultSet resultSetModel = (ResultSet) modelManager.getModel(subquery);
27856                                                if (resultSetModel != null && resultSetModel != queryTable
27857                                                                && !resultSetModel.getRelationRows().getHoldRelations().isEmpty()) {
27858                                                        ImpactRelationship impactRelation = modelFactory.createImpactRelation();
27859                                                        impactRelation.setEffectType(EffectType.select);
27860                                                        impactRelation.addSource(new RelationRowsRelationshipElement<ResultSetRelationRows>(
27861                                                                        resultSetModel.getRelationRows()));
27862                                                        impactRelation.setTarget(new RelationRowsRelationshipElement<ResultSetRelationRows>(
27863                                                                        queryTable.getRelationRows()));
27864                                                }
27865
27866                                                if (subquery.getSetOperatorType() != ESetOperatorType.none) {
27867                                                        SelectSetResultSet selectSetResultSetModel = (SelectSetResultSet) modelManager
27868                                                                        .getModel(subquery);
27869                                                        int x = 0;
27870                                                        boolean oneStar = starColumnCount(resultSetModel.getColumns()) == 1;
27871                                                        int cteItems = resultSetModel.getColumns().size();
27872                                                        // a star of unknown width in a select: each CTE column reads, from each select, the items that
27873                                                        // can stand at its position, as an INSERT column list does (Mantis 4914)
27874                                                        List<SelectPositions> setSelects = cteColumns == null ? null
27875                                                                        : setOperationPositions(subquery, queryTable.getColumns().size(),
27876                                                                                        shorterList(cteColumns, table));
27877                                                        if (setSelects != null) {
27878                                                                mapCteColumnsByCandidates(queryTable, setSelects, cteColumns);
27879                                                        }
27880                                                        for (int j = 0; setSelects == null && j < selectSetResultSetModel.getColumns().size(); j++) {
27881                                                                ResultColumn sourceColumn = selectSetResultSetModel.getColumns().get(j);
27882                                                                ResultColumn targetColumn = null;
27883                                                                if (cteColumns != null) {
27884                                                                        if (queryTable.getColumns().size() <= j) {
27885                                                                                for (int k = 0; k < queryTable.getColumns().size(); k++) {
27886                                                                                        if (queryTable.getColumns().get(k).getName().equals(sourceColumn.getName())
27887                                                                                                        || queryTable.getColumns().get(k).getName()
27888                                                                                                                        .equals(sourceColumn.getAlias())) {
27889                                                                                                targetColumn = queryTable.getColumns().get(k);
27890                                                                                        }
27891                                                                                }
27892                                                                        } else {
27893                                                                                if (x < j) {
27894                                                                                        x = j;
27895                                                                                }
27896                                                                                targetColumn = queryTable.getColumns().get(x);
27897                                                                                x++;
27898                                                                                // one star of unknown width takes the CTE columns the items after it leave
27899                                                                                if (resultSetModel.getColumns().get(j).isStarColumn() && (oneStar
27900                                                                                                ? cteColumns.size() - x > selectItemsFrom(resultSetModel.getColumns(), j + 1, cteItems)
27901                                                                                                : x < cteColumns.size())) {
27902                                                                                        j--;
27903                                                                                }
27904                                                                        }
27905                                                                } else {
27906                                                                        targetColumn = modelFactory.createSelectSetResultColumn(queryTable, sourceColumn);
27907                                                                }
27908                                                                for (TObjectName starLinkColumn : sourceColumn.getStarLinkColumnList()) {
27909                                                                        targetColumn.bindStarLinkColumn(starLinkColumn);
27910                                                                }
27911                                                                createCteSelectRelation(null, targetColumn, sourceColumn, cteColumns != null);
27912                                                                if (targetColumn != null) {
27913                                                                        // the field nodes the column carries (rule R8, Codex review of phase 3)
27914                                                                        setOperationFieldNodes(targetColumn, sourceColumn);
27915                                                                }
27916                                                        }
27917                                                        if (!queryTable.isDetermined()) {
27918                                                                queryTable.setDetermined(selectSetResultSetModel.isDetermined());
27919                                                        }
27920                                                } else {
27921                                                        int x = 0;
27922                                                        int grouped = 0;
27923                                                        boolean oneStar = starColumnCount(resultSetModel.getColumns()) == 1;
27924                                                        int cteItems = resultSetModel.getColumns().size();
27925                                                        // two or more stars of unknown width leave the boundary between them unknown: each CTE column reads every
27926                                                        // select item that can stand at its position, as an INSERT column list does (Mantis 4916)
27927                                                        boolean byCandidates = cteColumns != null && starColumnCount(resultSetModel.getColumns()) > 1;
27928                                                        if (byCandidates) {
27929                                                                mapCteColumnsByCandidates(queryTable, Collections.singletonList(
27930                                                                                selectPositions(resultSetModel.getColumns(), queryTable.getColumns().size(),
27931                                                                                                shorterList(cteColumns, table))), cteColumns);
27932                                                        }
27933                                                        for (int j = 0; !byCandidates && j < resultSetModel.getColumns().size(); j++) {
27934                                                                ResultColumn sourceColumn = resultSetModel.getColumns().get(j);
27935                                                                ResultColumn targetColumn = null;
27936                                                                if (cteColumns != null && structFieldPaths.containsKey(sourceColumn)
27937                                                                                && Math.max(x, j - grouped) < queryTable.getColumns().size()) {
27938                                                                        // a select item that builds a STRUCT is one CTE column, all of its fields: whole
27939                                                                        // edges, and a field node per field (rule R8, Codex review of phase 3)
27940                                                                        int width = selectItemWidth(resultSetModel.getColumns(), j);
27941                                                                        targetColumn = queryTable.getColumns().get(Math.max(x, j - grouped));
27942                                                                        for (int k = j; k < j + width; k++) {
27943                                                                                ResultColumn field = resultSetModel.getColumns().get(k);
27944                                                                                createCteSelectRelation(null, targetColumn, field);
27945                                                                                StructFieldPath path = structFieldPaths.get(field);
27946                                                                                ResultColumn node = builtFieldNodeAt(targetColumn, path.path);
27947                                                                                if (node == null) {
27948                                                                                        node = structFieldNode(targetColumn, path.path,
27949                                                                                                        cteColumns.getObjectName(Math.max(x, j - grouped)), path.item);
27950                                                                                }
27951                                                                                DataFlowRelationship nodeRelation = modelFactory.createDataFlowRelation();
27952                                                                                nodeRelation.setEffectType(EffectType.select);
27953                                                                                nodeRelation.setTarget(new ResultColumnRelationshipElement(node));
27954                                                                                nodeRelation.addSource(new ResultColumnRelationshipElement(field));
27955                                                                        }
27956                                                                        x = Math.max(x, j - grouped) + 1;
27957                                                                        grouped += width - 1;
27958                                                                        j += width - 1;
27959                                                                        continue;
27960                                                                }
27961                                                                if (cteColumns != null) {
27962                                                                        if (queryTable.getColumns().size() <= j - grouped) {
27963                                                                                for (int k = 0; k < queryTable.getColumns().size(); k++) {
27964                                                                                        if (queryTable.getColumns().get(k).getName().equals(sourceColumn.getName())
27965                                                                                                        || queryTable.getColumns().get(k).getName()
27966                                                                                                                        .equals(sourceColumn.getAlias())) {
27967                                                                                                targetColumn = queryTable.getColumns().get(k);
27968                                                                                        }
27969                                                                                }
27970                                                                        } else {
27971                                                                                if (x < j - grouped) {
27972                                                                                        x = j - grouped;
27973                                                                                }
27974                                                                                targetColumn = queryTable.getColumns().get(x);
27975                                                                                x++;
27976                                                                                // one star of unknown width takes the CTE columns the items after it leave, a
27977                                                                                // STRUCT item one of them (rule R8, owner ruling 2026-10-07); two or more
27978                                                                                // leave the boundary between them unknown and keep the old greedy pairing
27979                                                                                if (resultSetModel.getColumns().get(j).isStarColumn() && (oneStar
27980                                                                                                ? cteColumns.size() - x > selectItemsFrom(resultSetModel.getColumns(), j + 1, cteItems)
27981                                                                                                : x < cteColumns.size())) {
27982                                                                                        j--;
27983                                                                                }
27984                                                                        }
27985                                                                } else {
27986                                                                        targetColumn = modelFactory.createSelectSetResultColumn(queryTable, sourceColumn);
27987                                                                }
27988                                                                for (TObjectName starLinkColumn : sourceColumn.getStarLinkColumnList()) {
27989                                                                        targetColumn.bindStarLinkColumn(starLinkColumn);
27990                                                                }
27991                                                                createCteSelectRelation(null, targetColumn, sourceColumn, cteColumns != null);
27992                                                                if (targetColumn != null) {
27993                                                                        // the field nodes the column carries (rule R8, Codex review of phase 3)
27994                                                                        setOperationFieldNodes(targetColumn, sourceColumn);
27995                                                                }
27996                                                        }
27997                                                        if (!queryTable.isDetermined()) {
27998                                                                queryTable.setDetermined(resultSetModel.isDetermined());
27999                                                        }
28000                                                }
28001                                        } else if (subquery != null && !stmtStack.contains(subquery) && subquery.getValueClause() != null) {
28002                                                // a CTE defined by VALUES: its rows feed its columns, as an UPDATE reading it does (Mantis 5010).
28003                                                // Since the CTE list of WITH ... VALUES is kept (Mantis 5017) a query can bind to such a CTE.
28004                                                analyzeValuesCte(queryTable, subquery.getValueClause(), cteColumns, null);
28005                                        } else if (table.getCTE().getUpdateStmt() != null) {
28006                                                analyzeCustomSqlStmt(table.getCTE().getUpdateStmt());
28007                                        } else if (table.getCTE().getInsertStmt() != null) {
28008                                                analyzeCustomSqlStmt(table.getCTE().getInsertStmt());
28009                                        } else if (table.getCTE().getDeleteStmt() != null) {
28010                                                analyzeCustomSqlStmt(table.getCTE().getDeleteStmt());
28011                                        } else if (table.getCTE().getMergeStmt() != null) {
28012                                                analyzeCustomSqlStmt(table.getCTE().getMergeStmt());
28013                                        }
28014                                } else if (table.getTableType().name().startsWith("open")) {
28015                                        continue;
28016                                } else if (table.getTableType() == ETableSource.jsonTable) {
28017                                        Table functionTable = modelFactory.createJsonTable(table);
28018                                        TJsonTable jsonTable = table.getJsonTable();
28019                                        TColumnDefinitionList definitions = jsonTable.getColumnDefinitions();
28020                                        if (definitions != null) {
28021                                                for (int j = 0; j < definitions.size(); j++) {
28022                            TColumnDefinitionList nestDefinitions = definitions.getColumn(j).getNestedTableColumns();
28023                                                        if(nestDefinitions!=null) {
28024                                                                for(int k=0;k<nestDefinitions.size();k++){
28025                                                                        if (nestDefinitions.getColumn(k).getColumnName() == null) {
28026                                                                                continue;
28027                                                                        }
28028                                                                        TableColumn column = modelFactory.createTableColumn(functionTable,
28029                                            nestDefinitions.getColumn(k).getColumnName(), true);
28030                                    if (nestDefinitions.getColumn(k).getColumnPath() != null) {
28031                                        column.setDisplayName(
28032                                                column.getName() + ":" + nestDefinitions.getColumn(k).getColumnPath());
28033                                    }
28034                                                                }
28035                                                        }
28036                                                        else {
28037                                TableColumn column = modelFactory.createTableColumn(functionTable,
28038                                        definitions.getColumn(j).getColumnName(), true);
28039                                if (definitions.getColumn(j).getColumnPath() != null) {
28040                                    column.setDisplayName(
28041                                            column.getName() + ":" + definitions.getColumn(j).getColumnPath());
28042                                }
28043                            }
28044                                                }
28045                                        } else {
28046                                                TObjectName keyColumn = new TObjectName();
28047                                                keyColumn.setString("key");
28048                                                modelFactory.createJsonTableColumn(functionTable, keyColumn);
28049                                                TObjectName valueColumn = new TObjectName();
28050                                                valueColumn.setString("value");
28051                                                modelFactory.createJsonTableColumn(functionTable, valueColumn);
28052                                                TObjectName typeColumn = new TObjectName();
28053                                                typeColumn.setString("type");
28054                                                modelFactory.createJsonTableColumn(functionTable, typeColumn);
28055                                        }
28056
28057                                        functionTable.setCreateTable(true);
28058                                        functionTable.setSubType(SubType.function);
28059                                        modelManager.bindCreateModel(table, functionTable);
28060
28061                                        if (jsonTable.getJsonExpression() == null) {
28062                                                ErrorInfo errorInfo = new ErrorInfo();
28063                                                errorInfo.setErrorType(ErrorInfo.ANALYZE_ERROR);
28064                                                errorInfo.setErrorMessage("Can't handle the json table: " + jsonTable.toString());
28065                                                errorInfo.setStartPosition(new Pair3<Long, Long, String>(jsonTable.getStartToken().lineNo,
28066                                                                jsonTable.getStartToken().columnNo, ModelBindingManager.getGlobalHash()));
28067                                                errorInfo.setEndPosition(new Pair3<Long, Long, String>(jsonTable.getEndToken().lineNo,
28068                                                                jsonTable.getEndToken().columnNo + jsonTable.getEndToken().getAstext().length(),
28069                                                                ModelBindingManager.getGlobalHash()));
28070                                                errorInfo.fillInfo(this);
28071                                                errorInfos.add(errorInfo);
28072                                        } else {
28073                                                String jsonName = jsonTable.getJsonExpression().toString();
28074                                                if (!jsonName.startsWith("@")) {
28075                                                        columnsInExpr visitor = new columnsInExpr();
28076                                                        jsonTable.getJsonExpression().inOrderTraverse(visitor);
28077                                                        List<TObjectName> objectNames = visitor.getObjectNames();
28078                                                        List<TParseTreeNode> functions = visitor.getFunctions();
28079                                                        List<TParseTreeNode> constants = visitor.getConstants();
28080                                                        List<TSelectSqlStatement> subquerys = visitor.getSubquerys();
28081
28082                                                        for (int j = 0; j < functionTable.getColumns().size(); j++) {
28083                                                                TableColumn tableColumn = functionTable.getColumns().get(j);
28084                                                                if (functions != null && !functions.isEmpty()) {
28085                                                                        analyzeFunctionDataFlowRelation(tableColumn, functions, EffectType.function);
28086                                                                }
28087                                                                if (subquerys != null && !subquerys.isEmpty()) {
28088                                                                        analyzeSubqueryDataFlowRelation(tableColumn, subquerys, EffectType.select);
28089                                                                }
28090                                                                if (objectNames != null && !objectNames.isEmpty()) {
28091                                                                        analyzeDataFlowRelation(tableColumn, objectNames, EffectType.select, functions);
28092                                                                }
28093                                                                if (constants != null && !constants.isEmpty()) {
28094                                                                        analyzeConstantDataFlowRelation(tableColumn, constants, EffectType.select,
28095                                                                                        functions);
28096                                                                }
28097                                                        }
28098                                                } else {
28099                                                        TStatementList stmts = stmt.getGsqlparser().getSqlstatements();
28100                                                        for (int j = 0; j < stmts.size(); j++) {
28101                                                                TCustomSqlStatement item = stmts.get(j);
28102                                                                if (item instanceof TMssqlDeclare) {
28103                                                                        if (analyzeMssqlJsonDeclare((TMssqlDeclare) item, jsonName, functionTable)) {
28104                                                                                break;
28105                                                                        }
28106                                                                }
28107                                                        }
28108                                                }
28109                                        }
28110                                } else if (table.getTableType() == ETableSource.xmltable) {
28111                                        Table functionTable = modelFactory.createXmlTable(table);
28112                                        TXmlTable xmlTable = table.getXmlTable();
28113                                        TXmlTableParameter param = xmlTable != null ? xmlTable.getArg() : null;
28114
28115                                        // Output columns from COLUMNS clause
28116                                        if (param != null && param.getXmlTableColumns() != null) {
28117                                                TColumnDefinitionList defs = param.getXmlTableColumns();
28118                                                for (int j = 0; j < defs.size(); j++) {
28119                                                        TColumnDefinition def = defs.getColumn(j);
28120                                                        TableColumn column = modelFactory.createTableColumn(
28121                                                                        functionTable, def.getColumnName(), true);
28122                                                        if (def.getXmlTableColumnPath() != null) {
28123                                                                column.setDisplayName(
28124                                                                                column.getName() + ":" + def.getXmlTableColumnPath());
28125                                                        }
28126                                                }
28127                                        }
28128                                        functionTable.setCreateTable(true);
28129                                        functionTable.setSubType(SubType.function);
28130                                        modelManager.bindCreateModel(table, functionTable);
28131
28132                                        // Source columns from PASSING (v1: many-to-many)
28133                                        TResultColumnList passing = null;
28134                                        if (param != null && param.getXmlPassingClause() != null) {
28135                                                passing = param.getXmlPassingClause().getPassingList();
28136                                        }
28137                                        if (passing != null && passing.size() > 0) {
28138                                                columnsInExpr visitor = new columnsInExpr();
28139                                                for (int p = 0; p < passing.size(); p++) {
28140                                                        TExpression e = passing.getResultColumn(p).getExpr();
28141                                                        if (e != null) e.inOrderTraverse(visitor);
28142                                                }
28143                                                List<TObjectName> objectNames = visitor.getObjectNames();
28144                                                List<TParseTreeNode> functions = visitor.getFunctions();
28145                                                List<TParseTreeNode> constants = visitor.getConstants();
28146                                                List<TSelectSqlStatement> subquerys = visitor.getSubquerys();
28147
28148                                                for (int j = 0; j < functionTable.getColumns().size(); j++) {
28149                                                        TableColumn tc = functionTable.getColumns().get(j);
28150                                                        if (functions != null && !functions.isEmpty())
28151                                                                analyzeFunctionDataFlowRelation(tc, functions, EffectType.function);
28152                                                        if (subquerys != null && !subquerys.isEmpty())
28153                                                                analyzeSubqueryDataFlowRelation(tc, subquerys, EffectType.select);
28154                                                        if (objectNames != null && !objectNames.isEmpty())
28155                                                                analyzeDataFlowRelation(tc, objectNames, EffectType.select, functions);
28156                                                        if (constants != null && !constants.isEmpty())
28157                                                                analyzeConstantDataFlowRelation(tc, constants, EffectType.select, functions);
28158                                                }
28159                                        } else {
28160                                                ErrorInfo errorInfo = new ErrorInfo();
28161                                                errorInfo.setErrorType(ErrorInfo.ANALYZE_ERROR);
28162                                                errorInfo.setErrorMessage("Can't analyze XMLTABLE: missing PASSING clause.");
28163                                                errorInfos.add(errorInfo);
28164                                        }
28165                                } else if (getTableLinkedColumns(table) != null && getTableLinkedColumns(table).size() > 0) {
28166                                        if (table.getTableType() == ETableSource.rowList && table.getRowList() != null
28167                                                        && table.getRowList().size() > 0) {
28168                                                Table tableModel = modelFactory.createTable(table);
28169                                                for (int j = 0; j < table.getRowList().size(); j++) {
28170                                                        TMultiTarget rowList = table.getRowList().getMultiTarget(j);
28171                                                        for (int k = 0; k < rowList.getColumnList().size(); k++) {
28172                                                                TResultColumn column = rowList.getColumnList().getResultColumn(k);
28173                                                                if (column.getFieldAttr() == null)
28174                                                                        continue;
28175                                                                TableColumn tableColumn = modelFactory.createTableColumn(tableModel,
28176                                                                                column.getFieldAttr(), true);
28177
28178                                                                columnsInExpr visitor = new columnsInExpr();
28179                                                                column.getExpr().inOrderTraverse(visitor);
28180                                                                List<TObjectName> objectNames = visitor.getObjectNames();
28181                                                                List<TParseTreeNode> functions = visitor.getFunctions();
28182                                                                List<TParseTreeNode> constants = visitor.getConstants();
28183                                                                List<TSelectSqlStatement> subquerys = visitor.getSubquerys();
28184
28185                                                                if (functions != null && !functions.isEmpty()) {
28186                                                                        analyzeFunctionDataFlowRelation(tableColumn, functions, EffectType.function);
28187                                                                }
28188                                                                if (subquerys != null && !subquerys.isEmpty()) {
28189                                                                        analyzeSubqueryDataFlowRelation(tableColumn, subquerys, EffectType.select);
28190                                                                }
28191                                                                if (objectNames != null && !objectNames.isEmpty()) {
28192                                                                        analyzeDataFlowRelation(tableColumn, objectNames, EffectType.select, functions);
28193                                                                }
28194                                                                if (constants != null && !constants.isEmpty()) {
28195                                                                        analyzeConstantDataFlowRelation(tableColumn, constants, EffectType.select,
28196                                                                                        functions);
28197                                                                }
28198                                                        }
28199                                                }
28200                                        } else {
28201                                                if(table.getTableType() == ETableSource.pivoted_table) {
28202                                                        continue;
28203                                                }
28204                                                if (table.getTableType() == ETableSource.rowList) {
28205                                                        List<TResultColumnList> rowList = table.getValueClause().getRows();
28206                                                        
28207                                                        QueryTable tableModel = modelFactory.createQueryTable(table);
28208                                                        TAliasClause aliasClause = table.getAliasClause();
28209                                                        if (aliasClause != null && aliasClause.getColumns() != null) {
28210                                                                for (TObjectName column : aliasClause.getColumns()) {
28211                                                                        modelFactory.createResultColumn(tableModel, column, true);
28212                                                                }       
28213                                                        } else {        
28214                                                                int columnCount = rowList.get(0).size();
28215                                                                for (int j = 1; j <= columnCount; j++) {
28216                                                                        TObjectName columnName = new TObjectName();
28217                                                                        // the vendor's name for the column (Mantis 4823), column<j> where no rule is known
28218                                                                        String name = TValueClause.defaultColumnName(option.getVendor(), j);
28219                                                                        columnName.setString(name != null ? name : "column" + j);
28220                                                                        modelFactory.createResultColumn(tableModel, columnName, true);
28221                                                                }
28222                                                        }
28223                                                        tableModel.setDetermined(true);
28224                                                        
28225                                                        for (TResultColumnList resultColumnList : rowList) {
28226                                                                for (int j = 0; j < resultColumnList.size(); j++) {
28227                                                                        TResultColumn resultColumn = resultColumnList.getResultColumn(j);
28228                                                                        analyzeValueColumn(tableModel.getColumns().get(j), resultColumn, EffectType.select);
28229                                                                }
28230                                                        }
28231                                                        
28232                                                } else {
28233                                                        Table tableModel = modelFactory.createTable(table);
28234                                                        for (int j = 0; j < getTableLinkedColumns(table).size(); j++) {
28235                                                                TObjectName object = getTableLinkedColumns(table).getObjectName(j);
28236
28237                                                                if (object.getDbObjectType() == EDbObjectType.variable) {
28238                                                                        continue;
28239                                                                }
28240
28241                                                                if (object.getColumnNameOnly().startsWith("@")
28242                                                                                && (option.getVendor() == EDbVendor.dbvmssql
28243                                                                                                || option.getVendor() == EDbVendor.dbvazuresql)) {
28244                                                                        continue;
28245                                                                }
28246
28247                                                                if (object.getColumnNameOnly().startsWith(":")
28248                                                                                && (option.getVendor() == EDbVendor.dbvhana
28249                                                                                                || option.getVendor() == EDbVendor.dbvteradata)) {
28250                                                                        continue;
28251                                                                }
28252
28253                                                                if (isBuiltInFunctionName(object) && isFromFunction(object)) {
28254                                                                        continue;
28255                                                                }
28256
28257                                                                if (!DlineageUtil.isStarName((object))) {
28258                                                                        if (isStructColumn(object)) {
28259
28260                                                                        } else {
28261                                                                                if (pivotedTable != null) {
28262                                                                                        ResultColumn resultColumn = getPivotedTableColumn(pivotedTable, object);
28263                                                                                        if (resultColumn != null) {
28264                                                                                                continue;
28265                                                                                        }
28266                                                                                }
28267                                                                                TableColumn tableColumn = modelFactory.createTableColumn(tableModel, object,
28268                                                                                                false);
28269                                                                                if(tableColumn == null) {
28270                                                                                        continue;
28271                                                                                }
28272                                                                                if (table.getUnnestClause() != null
28273                                                                                                && table.getUnnestClause().getArrayExpr() != null) {
28274                                                                                        columnsInExpr visitor = new columnsInExpr();
28275                                                                                        table.getUnnestClause().getArrayExpr().inOrderTraverse(visitor);
28276
28277                                                                                        List<TObjectName> objectNames = visitor.getObjectNames();
28278                                                                                        List<TParseTreeNode> functions = visitor.getFunctions();
28279
28280                                                                                        if (functions != null && !functions.isEmpty()) {
28281                                                                                                analyzeFunctionDataFlowRelation(tableColumn, functions,
28282                                                                                                                EffectType.select);
28283
28284                                                                                        }
28285
28286                                                                                        List<TSelectSqlStatement> subquerys = visitor.getSubquerys();
28287                                                                                        if (subquerys != null && !subquerys.isEmpty()) {
28288                                                                                                analyzeSubqueryDataFlowRelation(tableColumn, subquerys,
28289                                                                                                                EffectType.select);
28290                                                                                        }
28291
28292                                                                                        analyzeDataFlowRelation(tableColumn, objectNames, EffectType.select,
28293                                                                                                        functions);
28294
28295                                                                                        List<TParseTreeNode> constants = visitor.getConstants();
28296                                                                                        analyzeConstantDataFlowRelation(tableColumn, constants, EffectType.select,
28297                                                                                                        functions);
28298                                                                                }
28299                                                                        }
28300                                                                } else {
28301                                                                        boolean flag = false;
28302                                                                        for (TObjectName column : getTableLinkedColumns(table)) {
28303                                                                                if (DlineageUtil.isStarName((column))) {
28304                                                                                        continue;
28305                                                                                }
28306                                                                                if (column.getLocation() != ESqlClause.where
28307                                                                                                && column.getLocation() != ESqlClause.joinCondition) {
28308                                                                                        flag = true;
28309                                                                                }
28310                                                                        }
28311                                                                        if (!flag) {
28312                                                                                TableColumn tableColumn = modelFactory.createTableColumn(tableModel, object,
28313                                                                                                false);
28314                                                                                if (tableColumn != null && !tableModel.hasSQLEnv()) {
28315                                                                                        tableColumn.setShowStar(true);
28316                                                                                }
28317                                                                        }
28318                                                                }
28319
28320                                                        }
28321                                                }
28322                                        }
28323                                }
28324                                else {
28325                                        modelFactory.createTable(table);
28326                                }
28327                        }
28328
28329                        // plan section 4.3: a Hive-family LATERAL VIEW is one more relation of the FROM clause, modelled in FROM order
28330                        // so a view reads the relations to its left, an earlier view included (Mantis 4887-4890)
28331                        for (int i = 0; i < fromTables.size(); i++) {
28332                                if (fromTables.getTable(i).getTableType() == ETableSource.lateralView) {
28333                                        analyzeLateralViewRelation(fromTables.getTable(i));
28334                                }
28335                        }
28336
28337                        gudusoft.gsqlparser.nodes.structured.NestedAccessProfile unnestProfile =
28338                                        gudusoft.gsqlparser.nodes.structured.NestedAccessProfile.forVendor(option.getVendor());
28339                        if (unnestProfile.getUnnestExpansion()
28340                                        == gudusoft.gsqlparser.nodes.structured.NestedAccessProfile.UnnestExpansion.POSITIONAL_EXPANSION
28341                                        && !unnestProfile.usesPrestoUnnestPath()) {
28342                                // every FROM item is modelled now, so an UNNEST argument finds its owner's columns, be it a CTE, a derived
28343                                // table or a physical table; joins, filters and the select list stay with the general flow (Mantis 4826)
28344                                for (int i = 0; i < fromTables.size(); i++) {
28345                                        if (fromTables.getTable(i).getUnnestClause() != null) {
28346                                                analyzeTrinoUnnest(fromTables.getTable(i));
28347                                        }
28348                                }
28349                        }
28350
28351                        if (pivotedTable!=null) {
28352                                for (TJoin join : stmt.getJoins()) {
28353                                        if (join.getTable() != null && join.getTable().getPivotedTable() != null) {
28354                                                if (isUnPivotedTable(join.getTable().getPivotedTable())) {
28355                                                        analyzeUnPivotedTable(stmt, join.getTable().getPivotedTable());
28356                                                } else {
28357                                                        analyzePivotedTable(stmt, join.getTable().getPivotedTable());
28358                                                }
28359                                                stmtStack.pop();
28360                                                return;
28361                                        }
28362                                }
28363                        }
28364
28365                        if (!stmt.isCombinedQuery()) {
28366                                Object queryModel = modelManager.getModel(stmt.getResultColumnList());
28367
28368                                if (queryModel == null) {
28369                                        TSelectSqlStatement parentStmt = getParentSetSelectStmt(stmt);
28370                                        if (isTopResultSet(stmt) || parentStmt == null) {
28371                                                ResultSet resultSetModel = modelFactory.createResultSet(stmt,
28372                                                                isTopResultSet(stmt) && isShowTopSelectResultSet() && stmt.getIntoClause() == null);
28373
28374                                                createPseudoImpactRelation(stmt, resultSetModel, EffectType.select);
28375
28376                                                boolean isDetermined = true;
28377                                                Map<String, AtomicInteger> keyMap = new HashMap<String, AtomicInteger>();
28378                                                Set<Object> columnIdentities = new HashSet<>();
28379                                                Set<String> columnNames = new HashSet<String>();
28380                                                // the source tables that published each column identity, for JOIN ... USING merging
28381                                                Map<Object, List<TTable>> tablesByIdentity = new HashMap<Object, List<TTable>>();
28382                                                for (int i = 0; i < stmt.getResultColumnList().size(); i++) {
28383                                                        TResultColumn column = stmt.getResultColumnList().getResultColumn(i);
28384
28385                                                        if (column.getExpr().getComparisonType() == EComparisonType.equals
28386                                                                        && column.getExpr().getLeftOperand().getObjectOperand() != null) {
28387                                                                TObjectName columnObject = column.getExpr().getLeftOperand().getObjectOperand();
28388
28389                                                                ResultColumn resultColumn = modelFactory.createResultColumn(resultSetModel,
28390                                                                                columnObject);
28391                                                                if (columnObject.getDbObjectType() == EDbObjectType.variable) {
28392                                                                        Table variable = modelManager.visibleLocal(columnObject.toString(),
28393                                                                                        LocalSymbolKey.Kind.VARIABLE);
28394                                                                        if (variable != null) {
28395                                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
28396                                                                                relation.setEffectType(EffectType.select);
28397                                                                                TableColumn columnModel = variable instanceof Variable
28398                                                                                                ? compositeBindingColumn((Variable) variable)
28399                                                                                                : variable.getColumns().get(0);
28400                                                                                relation.setTarget(new TableColumnRelationshipElement(columnModel));
28401                                                                                relation.addSource(new ResultColumnRelationshipElement(resultColumn));
28402                                                                        } else {
28403                                                                                variable = modelFactory.createReferencedVariable(columnObject);
28404                                                                                variable.setCreateTable(true);
28405                                                                                variable.setSubType(SubType.record);
28406                                                                                TObjectName variableProperties = DlineageUtil.syntheticStarName();
28407                                                                                TableColumn variableProperty = modelFactory.createTableColumn(variable,
28408                                                                                                variableProperties, true);
28409                                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
28410                                                                                relation.setEffectType(EffectType.select);
28411                                                                                relation.setTarget(new TableColumnRelationshipElement(variableProperty));
28412                                                                                relation.addSource(new ResultColumnRelationshipElement(resultColumn));
28413                                                                        }
28414                                                                }
28415
28416                                                                columnsInExpr visitor = new columnsInExpr();
28417                                                                column.getExpr().getRightOperand().inOrderTraverse(visitor);
28418
28419                                                                List<TObjectName> objectNames = visitor.getObjectNames();
28420                                                                List<TParseTreeNode> functions = visitor.getFunctions();
28421
28422                                                                if (functions != null && !functions.isEmpty()) {
28423                                                                        analyzeFunctionDataFlowRelation(resultColumn, functions, EffectType.select);
28424
28425                                                                }
28426
28427                                                                List<TSelectSqlStatement> subquerys = visitor.getSubquerys();
28428                                                                if (subquerys != null && !subquerys.isEmpty()) {
28429                                                                        analyzeSubqueryDataFlowRelation(resultColumn, subquerys, EffectType.select);
28430                                                                }
28431
28432                                                                analyzeDataFlowRelation(resultColumn, objectNames, EffectType.select, functions);
28433
28434                                                                List<TParseTreeNode> constants = visitor.getConstants();
28435                                                                analyzeConstantDataFlowRelation(resultColumn, constants, EffectType.select, functions);
28436                                                        } else {
28437                                                                if (column.getFieldAttr() != null && isStructColumn(column.getFieldAttr())) {
28438                                                                        Table table = modelFactory.createTable(column.getFieldAttr().getSourceTable());
28439                                                                        for (int j = 0; j < table.getColumns().size(); j++) {
28440                                                                                TObjectName columnName = new TObjectName();
28441                                                                                if (table.getColumns().get(j).getName().equals(table.getAlias())) {
28442                                                                                        if (!SQLUtil.isEmpty(column.getColumnAlias())) {
28443                                                                                                columnName.setString(column.getColumnAlias());
28444                                                                                        } else {
28445                                                                                                columnName.setString(table.getColumns().get(j).getName());
28446                                                                                        }
28447                                                                                } else {
28448                                                                                        columnName.setString(
28449                                                                                                        table.getAlias() + "." + table.getColumns().get(j).getName());
28450                                                                                }
28451                                                                                ResultColumn resultColumn = modelFactory.createResultColumn(resultSetModel,
28452                                                                                                columnName);
28453                                                                                DataFlowRelationship relationship = modelFactory.createDataFlowRelation();
28454                                                                                relationship.setTarget(new ResultColumnRelationshipElement(resultColumn));
28455                                                                                relationship.addSource(
28456                                                                                                new TableColumnRelationshipElement(table.getColumns().get(j)));
28457                                                                        }
28458                                                                } else if (buildsStructItem(column)) {
28459                                                                        analyzeStructItem(resultSetModel, column);
28460                                                                } else if (column.getExpr().getFunctionCall() != null && column.getExpr().getFunctionCall().getFunctionType() == EFunctionType.array_t) {
28461                                                                        Function function = (Function) createFunction(column.getExpr().getFunctionCall());
28462                                                                        String functionName = getResultSetName(function);
28463                                                                        if (function.getColumns().size() == 1) {
28464                                                                                // Scalar result inside ARRAY() - use just alias as column name
28465                                                                                TObjectName columnName = new TObjectName();
28466                                                                                if (column.getAliasClause() != null) {
28467                                                                                        columnName.setString(column.getAliasClause().toString());
28468                                                                                } else {
28469                                                                                        columnName.setString(functionName);
28470                                                                                }
28471                                                                                ResultColumn resultColumn = modelFactory.createResultColumn(resultSetModel,
28472                                                                                                columnName);
28473                                                                                DataFlowRelationship relationship = modelFactory.createDataFlowRelation();
28474                                                                                relationship.setTarget(new ResultColumnRelationshipElement(resultColumn));
28475                                                                                relationship.addSource(
28476                                                                                                new ResultColumnRelationshipElement(function.getColumns().get(0)));
28477                                                                        } else {
28478                                                                                for (int j = 0; j < function.getColumns().size(); j++) {
28479                                                                                        TObjectName columnName = new TObjectName();
28480                                                                                        if (column.getAliasClause() != null) {
28481                                                                                                columnName.setString(column.getAliasClause() + "."
28482                                                                                                                + getColumnNameOnly(function.getColumns().get(j).getName()));
28483                                                                                        }
28484                                                                                        else {
28485                                                                                                columnName.setString(functionName + "."
28486                                                                                                                + getColumnNameOnly(function.getColumns().get(j).getName()));
28487                                                                                        }
28488                                                                                        ResultColumn resultColumn = modelFactory.createResultColumn(resultSetModel,
28489                                                                                                        columnName);
28490                                                                                        resultColumn.setStruct(true);
28491                                                                                        DataFlowRelationship relationship = modelFactory.createDataFlowRelation();
28492                                                                                        relationship.setTarget(new ResultColumnRelationshipElement(resultColumn));
28493                                                                                        relationship.addSource(
28494                                                                                                        new ResultColumnRelationshipElement(function.getColumns().get(j)));
28495                                                                                }
28496                                                                        }
28497                                                                } else if (column.getExpr().getExprList() != null && column.getExpr().getExpressionType() == EExpressionType.array_t) {
28498                                                                        if(column.getExpr().getObjectOperand()!=null) {
28499                                                                                TObjectName exprColumnName = column.getExpr().getObjectOperand();
28500                                                                                Table table = modelFactory.createTable(exprColumnName.getSourceTable());
28501                                                                                List<TableColumn> arraySources = arrayItemSources(table, exprColumnName);
28502                                                                                for (int z = 0; z < arraySources.size(); z++) {
28503                                                                                        TableColumn tableColumn = arraySources.get(z);
28504                                                                                        {
28505                                                                                                TObjectName columnName = new TObjectName();
28506                                                                                                if (column.getAliasClause() != null) {
28507                                                                                                        columnName.setString(column.getAliasClause().toString());
28508                                                                                                } else {
28509                                                                                                        // BigQuery names an unaliased items[OFFSET(0)].item_name after its last
28510                                                                                                        // field, and items[OFFSET(0)] after the column; the empty name read here
28511                                                                                                        // threw NullPointerException (Mantis 4833)
28512                                                                                                        columnName.setString(unaliasedArrayItemName(exprColumnName));
28513                                                                                                }
28514                                                                                                ResultColumn resultColumn = modelFactory
28515                                                                                                                .createResultColumn(resultSetModel, columnName);
28516                                                                                                resultColumn.setStruct(true);
28517                                                                                                DataFlowRelationship relationship = modelFactory
28518                                                                                                                .createDataFlowRelation();
28519                                                                                                relationship.setTarget(
28520                                                                                                                new ResultColumnRelationshipElement(resultColumn));
28521                                                                                                relationship.addSource(new TableColumnRelationshipElement(
28522                                                                                                                tableColumn));
28523                                                                                        }
28524                                                                                }
28525                                                                        }
28526                                                                        else {
28527                                                                                for (int j = 0; j < column.getExpr().getExprList().size(); j++) {
28528                                                                                        TExpression expression = column.getExpr().getExprList().getExpression(j);
28529                                                                                        if(expression.getExpressionType() == EExpressionType.function_t) {
28530                                                                                                Function function = (Function)createFunction(expression.getFunctionCall());
28531                                                                                                String functionName = getResultSetName(function);
28532                                                                                                if (function != null && function.getColumns() != null) {
28533                                                                                                        if (function.getColumns().size() == 1) {
28534                                                                                                                // Scalar function inside ARRAY[] (e.g., ARRAY[TO_JSON_STRING(col)])
28535                                                                                                                // Use just the alias as column name, not alias.functionName
28536                                                                                                                TObjectName columnName = new TObjectName();
28537                                                                                                                if (column.getAliasClause() != null) {
28538                                                                                                                        columnName.setString(column.getAliasClause().toString());
28539                                                                                                                } else {
28540                                                                                                                        columnName.setString(functionName);
28541                                                                                                                }
28542                                                                                                                ResultColumn resultColumn = modelFactory
28543                                                                                                                                .createResultColumn(resultSetModel, columnName);
28544                                                                                                                DataFlowRelationship relationship = modelFactory
28545                                                                                                                                .createDataFlowRelation();
28546                                                                                                                relationship.setTarget(
28547                                                                                                                                new ResultColumnRelationshipElement(resultColumn));
28548                                                                                                                relationship.addSource(new ResultColumnRelationshipElement(
28549                                                                                                                                function.getColumns().get(0)));
28550                                                                                                        } else {
28551                                                                                                                // Multi-column (struct-like) function - use dotted naming
28552                                                                                                                for (int x = 0; x < function.getColumns().size(); x++) {
28553                                                                                                                        TObjectName columnName = new TObjectName();
28554                                                                                                                        if (column.getAliasClause() != null) {
28555                                                                                                                                columnName.setString(column.getAliasClause() + "."
28556                                                                                                                                                + getColumnNameOnly(
28557                                                                                                                                                function.getColumns().get(x).getName()));
28558                                                                                                                        } else {
28559                                                                                                                                columnName.setString(
28560                                                                                                                                                functionName + "." + getColumnNameOnly(
28561                                                                                                                                                                function.getColumns().get(x).getName()));
28562                                                                                                                        }
28563                                                                                                                        ResultColumn resultColumn = modelFactory
28564                                                                                                                                        .createResultColumn(resultSetModel, columnName);
28565                                                                                                                        resultColumn.setStruct(true);
28566                                                                                                                        DataFlowRelationship relationship = modelFactory
28567                                                                                                                                        .createDataFlowRelation();
28568                                                                                                                        relationship.setTarget(
28569                                                                                                                                        new ResultColumnRelationshipElement(resultColumn));
28570                                                                                                                        relationship.addSource(new ResultColumnRelationshipElement(
28571                                                                                                                                        function.getColumns().get(x)));
28572                                                                                                                }
28573                                                                                                        }
28574                                                                                                }
28575                                                                                        }
28576                                                                                        else if(expression.getExpressionType() == EExpressionType.simple_object_name_t) {
28577                                                                                                TObjectName exprColumnName = expression.getObjectOperand();
28578                                                                                                Table table = modelFactory.createTable(exprColumnName.getSourceTable());
28579                                                                                                List<TableColumn> arraySources = arrayItemSources(table, exprColumnName);
28580                                                                                                for (int z = 0; z < arraySources.size(); z++) {
28581                                                                                                        TableColumn tableColumn = arraySources.get(z);
28582                                                                                                        {
28583                                                                                                                TObjectName columnName = new TObjectName();
28584                                                                                                                if (column.getAliasClause() != null) {
28585                                                                                                                        columnName.setString(column.getAliasClause().toString());
28586                                                                                                                } else {
28587                                                                                                                        columnName
28588                                                                                                                                        .setString(getColumnNameOnly(columnName.toString()));
28589                                                                                                                }
28590                                                                                                                ResultColumn resultColumn = modelFactory
28591                                                                                                                                .createResultColumn(resultSetModel, columnName);
28592                                                                                                                resultColumn.setStruct(true);
28593                                                                                                                DataFlowRelationship relationship = modelFactory
28594                                                                                                                                .createDataFlowRelation();
28595                                                                                                                relationship.setTarget(
28596                                                                                                                                new ResultColumnRelationshipElement(resultColumn));
28597                                                                                                                relationship.addSource(new TableColumnRelationshipElement(
28598                                                                                                                                tableColumn));
28599                                                                                                        }
28600                                                                                                }
28601                                                                                        }
28602                                                                                }
28603                                                                        }
28604                                                                } else {
28605                                                                        if (propagateStructItem(resultSetModel, column)) {
28606                                                                                // a projection of a STRUCT a CTE or derived table built keeps its fields
28607                                                                                // (rule R8, Mantis 4893)
28608                                                                                continue;
28609                                                                        }
28610                                                                        Boolean structStar = analyzeStructStar(resultSetModel, column);
28611                                                                        if (structStar != null) {
28612                                                                                if (!structStar) {
28613                                                                                        isDetermined = false;
28614                                                                                }
28615                                                                                continue;
28616                                                                        }
28617                                                                        if (isStarItem(column)) {
28618                                                                                Map<String, Pair<String, TExpression>> replaceAsIdentifierMap = new HashMap<String, Pair<String, TExpression>>();
28619                                                                                Map<String, TObjectName> replaceColumnMap = new HashMap<String, TObjectName>();
28620                                                                                if(column.getReplaceExprAsIdentifiers()!=null && column.getReplaceExprAsIdentifiers().size()>0) {
28621                                                                                        for(TReplaceExprAsIdentifier replace: column.getReplaceExprAsIdentifiers()) {
28622                                                                                                replaceAsIdentifierMap.put(replace.getIdentifier().toString(), new Pair<String, TExpression>(column.getExpr().getExceptReplaceClause().toString(), replace.getExpr()));
28623                                                                                                replaceColumnMap.put(replace.getIdentifier().toString(), replace.getIdentifier());
28624                                                                                        }
28625                                                                                }
28626                                                                                
28627                                                                                TObjectName columnObject = column.getFieldAttr();
28628                                                                                List<TTable> sourceTables = columnObject.getSourceTableList();
28629                                                                                if (sourceTables != null && !sourceTables.isEmpty()) {
28630                                                                                        boolean[] determine = new boolean[sourceTables.size()];
28631                                                                                        // the one * of the tables whose columns are not known, and those tables
28632                                                                                        ResultColumn starColumn = null;
28633                                                                                        List<TTable> unexpanded = new ArrayList<TTable>();
28634                                                                                        for (int k = 0; k < sourceTables.size(); k++) {
28635                                                                                                TTable sourceTable = sourceTables.get(k);
28636                                                                                                Object tableModel = modelManager.getModel(sourceTable);
28637                                                                                                List<TableColumn> starColumns = tableModel instanceof Table
28638                                                                                                                ? starExpansionColumns((Table) tableModel) : null;
28639                                                                                                // a table several SELECT ... INTO statements made, in the query an INSERT reads by
28640                                                                                                // position: the first one's columns, each read with the columns the others wrote at
28641                                                                                                // its position (Mantis 4860); a reference by name reads the column of its name
28642                                                                                                List<List<TableColumn>> alternatives = starColumns == null && isInsertSubquery(stmt)
28643                                                                                                                && tableModel instanceof Table && sourceTables.size() == 1
28644                                                                                                                && column.getExceptColumnList() == null && replaceAsIdentifierMap.isEmpty()
28645                                                                                                                ? starAlternatives((Table) tableModel) : null;
28646                                                                                                if (alternatives != null) {
28647                                                                                                        starColumns = alternatives.get(0);
28648                                                                                                }
28649                                                                                                if (starColumns != null) {
28650                                                                                                        for (int j = 0; j < starColumns.size(); j++) {
28651                                                                                                                TableColumn tableColumn = starColumns.get(j);
28652                                                                                                                if (column.getExceptColumnList() != null) {
28653                                                                                                                        boolean except = false;
28654                                                                                                                        for (TObjectName objectName : column.getExceptColumnList()) {
28655                                                                                                                                if(DlineageUtil.sameColumnName(objectName.toString(), tableColumn.getName())) {
28656                                                                                                                                        except = true;
28657                                                                                                                                        break;
28658                                                                                                                                }
28659                                                                                                                        }
28660                                                                                                                        if (!except && tableColumn.isStruct()) {
28661                                                                                                                                List<String> names = SQLUtil
28662                                                                                                                                                .parseNames(tableColumn.getName());
28663                                                                                                                                for (String name : names) {
28664                                                                                                                                        for (TObjectName objectName : column
28665                                                                                                                                                        .getExceptColumnList()) {
28666                                                                                                                                                if (DlineageUtil.sameColumnName(objectName.toString(), name)) {
28667                                                                                                                                                        except = true;
28668                                                                                                                                                        break;
28669                                                                                                                                                }
28670                                                                                                                                        }
28671                                                                                                                                        if (except) {
28672                                                                                                                                                break;
28673                                                                                                                                        }
28674                                                                                                                                }
28675                                                                                                                        }
28676                                                                                                                        if(except){
28677                                                                                                                                continue;
28678                                                                                                                        }
28679                                                                                                                }
28680                                                                                                                
28681                                                                                                                if (replaceAsIdentifierMap.containsKey(tableColumn.getName())) {
28682                                                                                                                        Pair<String, TExpression> expr = replaceAsIdentifierMap.get(tableColumn.getName());
28683                                                                                                                        ResultColumn resultColumn = modelFactory.createResultColumn(resultSetModel, replaceColumnMap.get(tableColumn.getName()));
28684                                                                                                                        Transform transform = new Transform();
28685                                                                                                                        transform.setType(Transform.EXPRESSION);
28686                                                                                                                        TObjectName expression = new TObjectName();
28687                                                                                                                        expression.setString(expr.first);
28688                                                                                                                transform.setCode(expression);
28689                                                                                                                        resultColumn.setTransform(transform);
28690                                                                                                                        analyzeResultColumnExpressionRelation(resultColumn, expr.second);
28691                                                                                                                }
28692                                                                                                                else {
28693                                                                                                                        String columnName = LegacyDlineageWireNamesV1.columnName(tableColumn.getName());
28694                                                                                                                        Object identity = tableColumn.getIdentifierKey() != null ? tableColumn.getIdentifierKey() : columnName;
28695                                                                                                                        //bigquery
28696                                                                                                                        boolean exist = false;
28697                                                                                                                        boolean collisionRenamed = false;
28698                                                                                                                        if (!columnName.matches("(?i)f\\d+_")) {
28699                                                                                                                                List<TTable> publishers = tablesByIdentity.get(identity);
28700                                                                                                                                if (publishers == null) {
28701                                                                                                                                        publishers = new ArrayList<TTable>();
28702                                                                                                                                        tablesByIdentity.put(identity, publishers);
28703                                                                                                                                }
28704                                                                                                                                List<TTable> earlierPublishers = new ArrayList<TTable>(publishers);
28705                                                                                                                                if (!publishers.contains(sourceTable)) {
28706                                                                                                                                        publishers.add(sourceTable);
28707                                                                                                                                }
28708                                                                                                                                if (columnIdentities.add(identity) || !keyMap.containsKey(columnName)) {
28709                                                                                                                                        keyMap.put(columnName, new AtomicInteger(0));
28710                                                                                                                                } else {
28711                                                                                                                                        boolean usingMerged = columnName.indexOf("*") == -1
28712                                                                                                                                                        && isUsingMerged(stmt, tableColumn, sourceTable, earlierPublishers);
28713                                                                                                                                        while (columnNames.contains(columnName)) {
28714                                                                                                                                                if (usingMerged) {
28715                                                                                                                                                        exist = true;
28716                                                                                                                                                        break;
28717                                                                                                                                                } else if (keyMap.containsKey(columnName)) {
28718                                                                                                                                                        int index = keyMap.get(columnName)
28719                                                                                                                                                                        .incrementAndGet();
28720                                                                                                                                                                        columnName = columnName + index;
28721                                                                                                                                                                        collisionRenamed = true;
28722                                                                                                                                                }
28723                                                                                                                                        }
28724                                                                                                                                }
28725                                                                                                                                columnNames.add(columnName);
28726                                                                                                                        }
28727                                                                                                                        if (exist) {
28728                                                                                                                                String targetColumn = columnName;
28729                                                                                                                                DataFlowRelationship relation = modelFactory
28730                                                                                                                                                .createDataFlowRelation();
28731                                                                                                                                relation.setEffectType(EffectType.select);
28732                                                                                                                                relation.setTarget(new ResultColumnRelationshipElement(
28733                                                                                                                                                resultSetModel.getColumns().stream()
28734                                                                                                                                                                .filter(t -> SQLUtil.sameName(option.getVendor(), ESQLDataObjectType.dotColumn,
28735                                                                                                                                                                                t.getName(), tableColumn.getName()))
28736                                                                                                                                                                .findFirst().get()));
28737                                                                                                                                relation.addSource(new TableColumnRelationshipElement(
28738                                                                                                                                                tableColumn));
28739                                                                                                                                continue;
28740                                                                                                                        }
28741                                                                                                                        // Normal-form bookkeeping over synthetic collision-renamed keys
28742                                                                                                                        // (columnName may carry an appended index) — NOT identifier
28743                                                                                                                        // equality; stays on normalized-key compare by design (P0d.3c2),
28744                                                                                                                        // narrowed only where two names differ just in case (NormalNameCollisions).
28745                                                                                                                        // A name the collision bookkeeping did not rename is published as its source's own,
28746                                                                                                                        // unless the rendering is not stable under re-rendering (Oracle """Name"""), where
28747                                                                                                                        // output version 1 keeps the rendered name.
28748                                                                                                                        if (!collisionRenamed && DlineageUtil.sameColumnNormalName(columnName,
28749                                                                                                                                        LegacyDlineageWireNamesV1.columnName(columnName))) {
28750                                                                                                                                columnName = tableColumn.getName();
28751                                                                                                                        }
28752                                                                                                                        ResultColumn resultColumn = modelFactory.createStarResultColumn(resultSetModel, column, columnName);
28753                                                                                                                        resultColumn.inheritStarColumn(tableColumn);
28754                                                                                                                        if (!collisionRenamed) resultColumn.inheritIdentifier(tableColumn);
28755                                                                                                                        resultColumn.setStruct(tableColumn.isStruct());
28756                                                                                                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
28757                                                                                                                        relation.setEffectType(EffectType.select);
28758                                                                                                                        relation.setTarget(new ResultColumnRelationshipElement(resultColumn));
28759                                                                                                                        relation.addSource(new TableColumnRelationshipElement(tableColumn));
28760                                                                                                                        Set<TableColumn> read = Collections.newSetFromMap(new IdentityHashMap<TableColumn, Boolean>());
28761                                                                                                                        read.add(tableColumn);
28762                                                                                                                        for (int alt = 1; alternatives != null && alt < alternatives.size(); alt++) {
28763                                                                                                                                if (read.add(alternatives.get(alt).get(j))) {
28764                                                                                                                                        relation.addSource(new TableColumnRelationshipElement(alternatives.get(alt).get(j)));
28765                                                                                                                                }
28766                                                                                                                        }
28767                                                                                                                }
28768                                                                                                        }
28769                                                                                                        determine[k] = true;
28770                                                                                                        continue;
28771                                                                                                }
28772                                                                                                else if (tableModel instanceof ResultSet && ((ResultSet) tableModel).isDetermined()) {
28773                                                                                                        ResultSet table = (ResultSet) tableModel;
28774                                                                                                        for (int j = 0; j < table.getColumns().size(); j++) {
28775                                                                                                                ResultColumn tableColumn = table.getColumns().get(j);
28776                                                                                                                if (column.getExceptColumnList() != null) {
28777                                                                                                                        boolean except = false;
28778                                                                                                                        for (TObjectName objectName : column.getExceptColumnList()) {
28779                                                                                                                                if(DlineageUtil.sameColumnName(objectName.toString(), tableColumn.getName())) {
28780                                                                                                                                        except = true;
28781                                                                                                                                        break;
28782                                                                                                                                }
28783                                                                                                                        }
28784                                                                                                                        if (!except && tableColumn.isStruct()) {
28785                                                                                                                                List<String> names = SQLUtil
28786                                                                                                                                                .parseNames(tableColumn.getName());
28787                                                                                                                                for (String name : names) {
28788                                                                                                                                        for (TObjectName objectName : column
28789                                                                                                                                                        .getExceptColumnList()) {
28790                                                                                                                                                if (DlineageUtil.sameColumnName(objectName.toString(), name)) {
28791                                                                                                                                                        except = true;
28792                                                                                                                                                        break;
28793                                                                                                                                                }
28794                                                                                                                                        }
28795                                                                                                                                        if (except) {
28796                                                                                                                                                break;
28797                                                                                                                                        }
28798                                                                                                                                }
28799                                                                                                                        }
28800                                                                                                                        if(except){
28801                                                                                                                                continue;
28802                                                                                                                        }
28803                                                                                                                }
28804                                                                                                                if (replaceAsIdentifierMap.containsKey(tableColumn.getName())) {
28805                                                                                                                        Pair<String, TExpression> expr = replaceAsIdentifierMap.get(tableColumn.getName());
28806                                                                                                                        ResultColumn resultColumn = modelFactory.createResultColumn(resultSetModel, replaceColumnMap.get(tableColumn.getName()));
28807                                                                                                                        Transform transform = new Transform();
28808                                                                                                                        transform.setType(Transform.EXPRESSION);
28809                                                                                                                        TObjectName expression = new TObjectName();
28810                                                                                                                        expression.setString(expr.first);
28811                                                                                                                transform.setCode(expression);
28812                                                                                                                        resultColumn.setTransform(transform);
28813                                                                                                                        analyzeResultColumnExpressionRelation(resultColumn, expr.second);
28814                                                                                                                }
28815                                                                                                                else if(tableColumn.getRefColumnName()!=null) {
28816                                                                                                                        ResultColumn resultColumn = modelFactory.createStarResultColumn(resultSetModel, column, tableColumn.getRefColumnName(), tableColumn);
28817                                                                                                                        if(tableColumn.isStruct()) {
28818                                                                                                                                resultColumn.setStruct(true);
28819                                                                                                                        }
28820                                                                                                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
28821                                                                                                                        relation.setEffectType(EffectType.select);
28822                                                                                                                        relation.setTarget(new ResultColumnRelationshipElement(resultColumn));
28823                                                                                                                        relation.addSource(new ResultColumnRelationshipElement(tableColumn));
28824                                                                                                                }
28825                                                                                                                else {
28826                                                                                                                        String columnName = LegacyDlineageWireNamesV1.columnName(tableColumn.getName());
28827                                                                                                                        Object identity = tableColumn.getIdentifierKey() != null ? tableColumn.getIdentifierKey() : columnName;
28828                                                                                                                        //bigquery
28829                                                                                                                        boolean collisionRenamed = false;
28830                                                                                                                        if (!columnName.matches("(?i)f\\d+_")) {
28831                                                                                                                                if (columnIdentities.add(identity) || !keyMap.containsKey(columnName)) {
28832                                                                                                                                        keyMap.put(columnName, new AtomicInteger(0));
28833                                                                                                                                } else {
28834                                                                                                                                        while (columnNames.contains(columnName)) {
28835                                                                                                                                                int index = keyMap.get(columnName)
28836                                                                                                                                                                .incrementAndGet();
28837                                                                                                                                                                columnName = columnName + index;
28838                                                                                                                                                                collisionRenamed = true;
28839                                                                                                                                        }
28840                                                                                                                                }
28841                                                                                                                                columnNames.add(columnName);
28842                                                                                                                        }
28843                                                                                                                        // Normal-form bookkeeping over synthetic collision-renamed keys
28844                                                                                                                        // (columnName may carry an appended index) — NOT identifier
28845                                                                                                                        // equality; stays on normalized-key compare by design (P0d.3c2),
28846                                                                                                                        // narrowed only where two names differ just in case (NormalNameCollisions).
28847                                                                                                                        // A name the collision bookkeeping did not rename is published as its source's own,
28848                                                                                                                        // unless the rendering is not stable under re-rendering (Oracle """Name"""), where
28849                                                                                                                        // output version 1 keeps the rendered name.
28850                                                                                                                        if (!collisionRenamed && DlineageUtil.sameColumnNormalName(columnName,
28851                                                                                                                                        LegacyDlineageWireNamesV1.columnName(columnName))) {
28852                                                                                                                                columnName = tableColumn.getName();
28853                                                                                                                        }
28854                                                                                                                        if (modelManager.getModel(column) instanceof ResultColumn) {
28855                                                                                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
28856                                                                                                                                relation.setEffectType(EffectType.select);
28857                                                                                                                                relation.setTarget(new ResultColumnRelationshipElement((ResultColumn)modelManager.getModel(column)));
28858                                                                                                                                relation.addSource(new ResultColumnRelationshipElement(tableColumn));
28859                                                                                                                        }
28860                                                                                                                        else {
28861                                                                                                                                ResultColumn resultColumn = modelFactory.createStarResultColumn(resultSetModel, column, columnName);
28862                                                                                                                                resultColumn.inheritStarColumn(tableColumn);
28863                                                                                                                                if (!collisionRenamed) resultColumn.inheritIdentifier(tableColumn);
28864                                                                                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
28865                                                                                                                                relation.setEffectType(EffectType.select);
28866                                                                                                                                relation.setTarget(new ResultColumnRelationshipElement(resultColumn));
28867                                                                                                                                relation.addSource(new ResultColumnRelationshipElement(tableColumn));
28868                                                                                                                        }
28869                                                                                                                }
28870                                                                                                        }
28871                                                                                                        determine[k] = true;
28872                                                                                                        continue;
28873                                                                                                }
28874                                                                                                else {
28875                                                                                                        ResultColumn resultColumn = starColumn != null ? starColumn
28876                                                                                                                        : modelFactory.createResultColumn(resultSetModel, column);
28877                                                                                                        starColumn = resultColumn;
28878                                                                                                        unexpanded.add(sourceTable);
28879                                                                                                        if (tableModel instanceof Function) {
28880                                                                                                
28881                                                                                                        } else {
28882                                                                                                                TObjectName[] columns = modelManager
28883                                                                                                                                .getTableColumns(sourceTable);
28884                                                                                                                for (int j = 0; j < columns.length; j++) {
28885                                                                                                                        TObjectName columnName = columns[j];
28886                                                                                                                        if (columnName == null
28887                                                                                                                                        || DlineageUtil.isStarName((columnName))) {
28888                                                                                                                                continue;
28889                                                                                                                        }
28890                                                                                                                        if (isStructColumn(columnName)) {
28891                                                                                                                                continue;
28892                                                                                                                        }
28893
28894                                                                                                                        resultColumn.bindStarLinkColumn(columnName);
28895                                                                                                                        if (column.getExceptColumnList() != null) {
28896                                                                                                                                for (TObjectName objectName : column
28897                                                                                                                                                .getExceptColumnList()) {
28898                                                                                                                                        resultColumn.unbindStarLinkColumn(objectName);
28899                                                                                                                                }
28900                                                                                                                        }
28901                                                                                                                }
28902                                                                                                                if (tableModel instanceof ResultSet) {
28903                                                                                                                        ResultSet queryTable = (ResultSet) tableModel;
28904                                                                                                                        if (!containStarColumn(queryTable)) {
28905                                                                                                                                resultColumn.setShowStar(false);
28906                                                                                                                        }
28907                                                                                                                }
28908                                                                                                                if (tableModel instanceof Table) {
28909                                                                                                                        Table table = (Table) tableModel;
28910                                                                                                                        if (table.isCreateTable()) {
28911                                                                                                                                resultColumn.setShowStar(false);
28912                                                                                                                        }
28913                                                                                                                }
28914                                                                                                        }
28915                                                                                                }
28916                                                                                        }
28917                                                                                        if(!Arrays.toString(determine).contains("false")) {
28918                                                                                                continue;
28919                                                                                        }
28920                                                                                        if (unexpanded.size() < sourceTables.size()) {
28921                                                                                                // Mantis 4981: the columns of the other tables are already result
28922                                                                                                // columns of their own, so the * reads only these tables. Expanding
28923                                                                                                // a table rebinds the item to its columns: the * must be the item's
28924                                                                                                // model again, or it is never analyzed and these tables are lost.
28925                                                                                                modelManager.bindModel(column, starColumn);
28926                                                                                                isDetermined = false;
28927                                                                                                analyzeStarOfTables(column, columnObject, unexpanded);
28928                                                                                                // then the item is bound to all its result columns in FROM order, the
28929                                                                                                // * among them, as an expanded star is: a view, a CTAS or an INSERT
28930                                                                                                // writes each of them, the * included
28931                                                                                                modelManager.bindModel(column, itemColumns(resultSetModel, column, starColumn));
28932                                                                                                continue;
28933                                                                                        }
28934                                                                                } else {
28935                                                                                        ResultColumn resultColumn = modelFactory.createResultColumn(resultSetModel, column);
28936                                                                                        TTableList tables = stmt.getTables();
28937                                                                                        for (int k = 0; k < tables.size(); k++) {
28938                                                                                                TTable table = tables.getTable(k);
28939                                                                                                TObjectName[] columns = modelManager.getTableColumns(table);
28940                                                                                                for (int j = 0; j < columns.length; j++) {
28941                                                                                                        TObjectName columnName = columns[j];
28942                                                                                                        if (columnName == null) {
28943                                                                                                                continue;
28944                                                                                                        }
28945                                                                                                        if (DlineageUtil.isStarName((columnName))) {
28946                                                                                                                if (modelManager.getModel(table) instanceof Table) {
28947                                                                                                                        Table tableModel = (Table) modelManager.getModel(table);
28948                                                                                                                        if (tableModel != null
28949                                                                                                                                        && !tableModel.getColumns().isEmpty()) {
28950                                                                                                                                for (TableColumn item : tableModel.getColumns()) {
28951                                                                                                                                        resultColumn
28952                                                                                                                                                        .bindStarLinkColumn(item.getColumnObject());
28953                                                                                                                                        if (table.getSubquery() == null
28954                                                                                                                                                        && table.getCTE() == null
28955                                                                                                                                                        && !tableModel.isCreateTable()) {
28956                                                                                                                                                resultColumn.setShowStar(true);
28957                                                                                                                                        }
28958                                                                                                                                }
28959                                                                                                                        }
28960                                                                                                                } else if (modelManager.getModel(table) instanceof QueryTable) {
28961                                                                                                                        QueryTable tableModel = (QueryTable) modelManager
28962                                                                                                                                        .getModel(table);
28963                                                                                                                        if (tableModel != null
28964                                                                                                                                        && !tableModel.getColumns().isEmpty()) {
28965                                                                                                                                for (ResultColumn item : tableModel.getColumns()) {
28966                                                                                                                                        if (item.hasStarLinkColumn()) {
28967                                                                                                                                                for (TObjectName starLinkColumn : item
28968                                                                                                                                                                .getStarLinkColumnList()) {
28969                                                                                                                                                        resultColumn
28970                                                                                                                                                                        .bindStarLinkColumn(starLinkColumn);
28971                                                                                                                                                }
28972                                                                                                                                        } else if (item
28973                                                                                                                                                        .getColumnObject() instanceof TObjectName) {
28974                                                                                                                                                resultColumn.bindStarLinkColumn(
28975                                                                                                                                                                (TObjectName) item.getColumnObject());
28976                                                                                                                                        } else if (item
28977                                                                                                                                                        .getColumnObject() instanceof TResultColumn) {
28978                                                                                                                                                TResultColumn queryTableColumn = (TResultColumn) item
28979                                                                                                                                                                .getColumnObject();
28980                                                                                                                                                TObjectName tableColumnObject = queryTableColumn
28981                                                                                                                                                                .getFieldAttr();
28982                                                                                                                                                if (tableColumnObject != null) {
28983                                                                                                                                                        resultColumn.bindStarLinkColumn(
28984                                                                                                                                                                        tableColumnObject);
28985                                                                                                                                                } else if (queryTableColumn
28986                                                                                                                                                                .getAliasClause() != null && !item.isStruct()) {
28987                                                                                                                                                        resultColumn.bindStarLinkColumn(
28988                                                                                                                                                                        queryTableColumn.getAliasClause()
28989                                                                                                                                                                                        .getAliasName());
28990                                                                                                                                                }
28991                                                                                                                                        }
28992                                                                                                                                }
28993                                                                                                                        }
28994                                                                                                                }
28995                                                                                                                continue;
28996                                                                                                        }
28997                                                                                                        resultColumn.bindStarLinkColumn(columnName);
28998                                                                                                }
28999                                                                                        }
29000                                                                                }
29001                                                                                isDetermined = false;
29002                                                                        }
29003                                                                        else {
29004                                                                                if(column.getAliasClause()!=null && column.getAliasClause().getColumns()!=null) {
29005                                                                                        for(TObjectName aliasColumn: column.getAliasClause().getColumns()) {
29006                                                                                                modelFactory.createResultColumn(resultSetModel, aliasColumn);
29007                                                                                        }
29008                                                                                }
29009                                                                                else {
29010                                                                                        modelFactory.createResultColumn(resultSetModel, column);
29011                                                                                }
29012                                                                        }
29013                                                                        analyzeResultColumn(column, EffectType.select);
29014                                                                        // an unaliased constructor keeps its one column and gains a field node per
29015                                                                        // field, so a column list pairs its fields (rule R8, Codex review of phase 3)
29016                                                                        if (column.getExpr() != null
29017                                                                                        && column.getExpr().getExpressionType() == EExpressionType.function_t) {
29018                                                                                addStructFieldNodes(resultSetModel, column);
29019                                                                        }
29020                                                                }
29021                                                        }
29022                                                }
29023                                                if (isDetermined) {
29024                                                        resultSetModel.setDetermined(isDetermined);
29025                                                }
29026                                        }
29027
29028                                        TSelectSqlStatement parent = getParentSetSelectStmt(stmt);
29029                                        if (parent != null && parent.getSetOperatorType() != ESetOperatorType.none) {
29030                                                ResultSet resultSetModel = modelFactory.createResultSet(stmt, false);
29031                                                if(queryModel == null) {
29032                                                        queryModel = resultSetModel;
29033                                                } 
29034
29035                                                createPseudoImpactRelation(stmt, resultSetModel, EffectType.select);
29036
29037                                                boolean isDetermined = true;
29038                                                for (int i = 0; i < stmt.getResultColumnList().size(); i++) {
29039                                                        TResultColumn column = stmt.getResultColumnList().getResultColumn(i);
29040                                                        Boolean structStar = analyzeStructStar(resultSetModel, column);
29041                                                        if (structStar != null) {
29042                                                                if (!structStar) {
29043                                                                        isDetermined = false;
29044                                                                }
29045                                                                continue;
29046                                                        }
29047                                                        if (isStarItem(column)) {
29048                                                                
29049                                                                Map<String, Pair<String, TExpression>> replaceAsIdentifierMap = new HashMap<String, Pair<String, TExpression>>();
29050                                                                Map<String, TObjectName> replaceColumnMap = new HashMap<String, TObjectName>();
29051                                                                if(column.getReplaceExprAsIdentifiers()!=null && column.getReplaceExprAsIdentifiers().size()>0) {
29052                                                                        for(TReplaceExprAsIdentifier replace: column.getReplaceExprAsIdentifiers()) {
29053                                                                                replaceAsIdentifierMap.put(replace.getIdentifier().toString(), new Pair<String, TExpression>(column.getExpr().getExceptReplaceClause().toString(), replace.getExpr()));
29054                                                                                replaceColumnMap.put(replace.getIdentifier().toString(), replace.getIdentifier());
29055                                                                        }
29056                                                                }
29057                                                                
29058                                                                TObjectName columnObject = column.getFieldAttr();
29059                                                                TTable sourceTable = columnObject.getSourceTable();
29060                                                                List<TTable> starTables = starReadsSeveralTables(columnObject)
29061                                                                                ? columnObject.getSourceTableList()
29062                                                                                : sourceTable == null ? Collections.<TTable>emptyList()
29063                                                                                                : Collections.singletonList(sourceTable);
29064                                                                if (!starTables.isEmpty()) {
29065                                                                        if (expandSetBranchStar(stmt, resultSetModel, queryModel, column, starTables,
29066                                                                                        replaceAsIdentifierMap, replaceColumnMap)) {
29067                                                                                continue;
29068                                                                        }
29069                                                                        isDetermined = false;
29070                                                                }
29071
29072                                                                // Mantis 4975: over a join of tables of known columns and of unknown columns, the known ones
29073                                                                // are result columns of their own where they stand, and the star reads only the others, as
29074                                                                // in a plain select list (Mantis 4981): the branches pair the columns by position
29075                                                                // (not with REPLACE, which moves columns, nor over a USING or NATURAL join: Codex review)
29076                                                                List<TTable> expandedTables = columnObject.getTableToken() == null
29077                                                                                && replaceAsIdentifierMap.isEmpty() && !mergesJoinColumns(stmt)
29078                                                                                ? splitSetBranchStarTables(starTables) : Collections.<TTable>emptyList();
29079                                                                List<TTable> unexpanded = new ArrayList<TTable>();
29080                                                                ResultColumn resultColumn = null;
29081                                                                Set<String> earlierNames = new HashSet<String>();
29082                                                                Set<String> names = new HashSet<String>();
29083                                                                List<TTable> earlierTables = new ArrayList<TTable>();
29084                                                                for (TTable starTable : expandedTables.isEmpty() ? Collections.<TTable>emptyList() : starTables) {
29085                                                                        if (expandedTables.contains(starTable)) {
29086                                                                                expandSetBranchStarTable(stmt, resultSetModel, queryModel, column, starTable,
29087                                                                                                replaceAsIdentifierMap, replaceColumnMap, earlierNames, names, earlierTables);
29088                                                                        } else {
29089                                                                                if (resultColumn == null) {
29090                                                                                        resultColumn = modelFactory.createResultColumn(resultSetModel, column);
29091                                                                                }
29092                                                                                unexpanded.add(starTable);
29093                                                                        }
29094                                                                }
29095                                                                if (resultColumn == null) {
29096                                                                        resultColumn = modelFactory.createResultColumn(resultSetModel, column);
29097                                                                }
29098                                                                
29099                                                                if (columnObject.getTableToken() != null && sourceTable != null) {
29100                                                                        TObjectName[] columns = modelManager.getTableColumns(sourceTable);
29101                                                                        for (int j = 0; j < columns.length; j++) {
29102                                                                                TObjectName columnName = columns[j];
29103                                                                                if (columnName == null || DlineageUtil.isStarName((columnName))) {
29104                                                                                        continue;
29105                                                                                }
29106                                                                                resultColumn.bindStarLinkColumn(columnName);
29107                                                                        }
29108
29109                                                                        if (modelManager.getModel(sourceTable) instanceof Table) {
29110                                                                                Table tableModel = (Table) modelManager.getModel(sourceTable);
29111                                                                                if (tableModel != null && !tableModel.getColumns().isEmpty()) {
29112                                                                                        for (TableColumn item : tableModel.getColumns()) {
29113                                                                                                if (DlineageUtil.isStarName((item.getColumnObject()))) {
29114                                                                                                        continue;
29115                                                                                                }
29116                                                                                                resultColumn.bindStarLinkColumn(item.getColumnObject());
29117                                                                                        }
29118                                                                                }
29119                                                                        } else if (modelManager.getModel(sourceTable) instanceof QueryTable) {
29120                                                                                QueryTable tableModel = (QueryTable) modelManager.getModel(sourceTable);
29121                                                                                if (tableModel != null && !tableModel.getColumns().isEmpty()) {
29122                                                                                        for (ResultColumn item : tableModel.getColumns()) {
29123                                                                                                if (item.hasStarLinkColumn()) {
29124                                                                                                        for (TObjectName starLinkColumn : item.getStarLinkColumnList()) {
29125                                                                                                                if (DlineageUtil.isStarName((starLinkColumn))) {
29126                                                                                                                        continue;
29127                                                                                                                }
29128                                                                                                                resultColumn.bindStarLinkColumn(starLinkColumn);
29129                                                                                                        }
29130                                                                                                } else if (item.getColumnObject() instanceof TObjectName) {
29131                                                                                                        TObjectName starLinkColumn = (TObjectName) item.getColumnObject();
29132                                                                                                        if (DlineageUtil.isStarName((starLinkColumn))) {
29133                                                                                                                continue;
29134                                                                                                        }
29135                                                                                                        resultColumn.bindStarLinkColumn(starLinkColumn);
29136                                                                                                }
29137                                                                                        }
29138                                                                                }
29139                                                                        }
29140
29141                                                                } else {
29142                                                                        TTableList tables = stmt.getTables();
29143                                                                        for (int k = 0; k < tables.size(); k++) {
29144                                                                                TTable table = tables.getTable(k);
29145                                                                                if (expandedTables.contains(table)) {
29146                                                                                        continue;
29147                                                                                }
29148                                                                                TObjectName[] columns = modelManager.getTableColumns(table);
29149                                                                                for (int j = 0; j < columns.length; j++) {
29150                                                                                        TObjectName columnName = columns[j];
29151                                                                                        if (columnName == null) {
29152                                                                                                continue;
29153                                                                                        }
29154                                                                                        if (DlineageUtil.isStarName((columnName))) {
29155                                                                                                if (modelManager.getModel(table) instanceof Table) {
29156                                                                                                        Table tableModel = (Table) modelManager.getModel(table);
29157                                                                                                        if (tableModel != null && !tableModel.getColumns().isEmpty()) {
29158                                                                                                                for (TableColumn item : tableModel.getColumns()) {
29159                                                                                                                        resultColumn.bindStarLinkColumn(item.getColumnObject());
29160                                                                                                                }
29161                                                                                                        }
29162                                                                                                } else if (modelManager.getModel(table) instanceof QueryTable) {
29163                                                                                                        QueryTable tableModel = (QueryTable) modelManager.getModel(table);
29164                                                                                                        if (tableModel != null && !tableModel.getColumns().isEmpty()) {
29165                                                                                                                for (ResultColumn item : tableModel.getColumns()) {
29166                                                                                                                        if (item.hasStarLinkColumn()) {
29167                                                                                                                                for (TObjectName starLinkColumn : item
29168                                                                                                                                                .getStarLinkColumnList()) {
29169                                                                                                                                        resultColumn.bindStarLinkColumn(starLinkColumn);
29170                                                                                                                                }
29171                                                                                                                        } else if (item.getColumnObject() instanceof TObjectName) {
29172                                                                                                                                resultColumn.bindStarLinkColumn(
29173                                                                                                                                                (TObjectName) item.getColumnObject());
29174                                                                                                                        } else if (item
29175                                                                                                                                        .getColumnObject() instanceof TResultColumn) {
29176                                                                                                                                TResultColumn queryTableColumn = (TResultColumn) item
29177                                                                                                                                                .getColumnObject();
29178                                                                                                                                TObjectName tableColumnObject = queryTableColumn
29179                                                                                                                                                .getFieldAttr();
29180                                                                                                                                if (tableColumnObject != null) {
29181                                                                                                                                        resultColumn.bindStarLinkColumn(tableColumnObject);
29182                                                                                                                                } else if (queryTableColumn.getAliasClause() != null) {
29183                                                                                                                                        resultColumn.bindStarLinkColumn(queryTableColumn
29184                                                                                                                                                        .getAliasClause().getAliasName());
29185                                                                                                                                }
29186                                                                                                                        }
29187                                                                                                                }
29188                                                                                                        }
29189                                                                                                }
29190
29191                                                                                                continue;
29192                                                                                        }
29193                                                                                        resultColumn.bindStarLinkColumn(columnName);
29194                                                                                }
29195                                                                        }
29196                                                                }
29197                                                                if (!expandedTables.isEmpty()) {
29198                                                                        // the star reads only the other tables, and the item is all its result columns in
29199                                                                        // FROM order, as in a plain select list (Mantis 4981)
29200                                                                        modelManager.bindModel(column, resultColumn);
29201                                                                        analyzeStarOfTables(column, columnObject, unexpanded);
29202                                                                        modelManager.bindModel(column, itemColumns(resultSetModel, column, resultColumn));
29203                                                                        continue;
29204                                                                }
29205                                                        }
29206                                                        else {
29207                                                                ResultColumn resultColumn = modelFactory.createResultColumn(resultSetModel, column);
29208                                                        }
29209                                                        analyzeResultColumn(column, EffectType.select);
29210                                                        if (!isStarItem(column)) {
29211                                                                // a STRUCT a branch of a set operation builds keeps its fields: field nodes below
29212                                                                // the item's one column (rule R8, plan acceptance row 26)
29213                                                                addStructFieldNodes(resultSetModel, column);
29214                                                        }
29215
29216                                                }
29217                                                
29218                                                resultSetModel.setDetermined(isDetermined);
29219                                        }
29220                                } else {
29221                                        for (int i = 0; i < stmt.getResultColumnList().size(); i++) {
29222                                                TResultColumn column = stmt.getResultColumnList().getResultColumn(i);
29223
29224                                                if (!(queryModel instanceof ResultSet)) {
29225                                                        continue;
29226                                                }
29227                                                
29228                                                ResultSet resultSetModel = (ResultSet)queryModel;
29229                                                        
29230                                                if (analyzeStructStar(resultSetModel, column) != null) {
29231                                                        continue;
29232                                                }
29233                                                if (isStarItem(column)) {
29234                                                        TObjectName columnObject = column.getFieldAttr();
29235                                                        TTable sourceTable = columnObject.getSourceTable();
29236                                                        if (column.toString().indexOf(".") == -1 && stmt.getTables().size() > 1) {
29237                                                                sourceTable = null;
29238                                                        }
29239                                                        if (sourceTable != null) {
29240                                                                {
29241                                                                        Object tableModel = modelManager.getModel(sourceTable);
29242                                                                        if (tableModel instanceof Table && ((Table) tableModel).isCreateTable()) {
29243                                                                                Table table = (Table) tableModel;
29244                                                                                for (int j = 0; j < table.getColumns().size(); j++) {
29245                                                                                        TableColumn tableColumn = table.getColumns().get(j);
29246                                                                                        if (column.getExceptColumnList() != null) {
29247                                                                                                boolean except = false;
29248                                                                                                for (TObjectName objectName : column.getExceptColumnList()) {
29249                                                                                                        if (DlineageUtil.sameColumnName(objectName.toString(), tableColumn.getName())) {
29250                                                                                                                except = true;
29251                                                                                                                break;
29252                                                                                                        }
29253                                                                                                }
29254                                                                                                if (!except && tableColumn.isStruct()) {
29255                                                                                                        List<String> names = SQLUtil
29256                                                                                                                        .parseNames(tableColumn.getName());
29257                                                                                                        for (String name : names) {
29258                                                                                                                for (TObjectName objectName : column
29259                                                                                                                                .getExceptColumnList()) {
29260                                                                                                                        if (DlineageUtil.sameColumnName(objectName.toString(), name)) {
29261                                                                                                                                except = true;
29262                                                                                                                                break;
29263                                                                                                                        }
29264                                                                                                                }
29265                                                                                                                if (except) {
29266                                                                                                                        break;
29267                                                                                                                }
29268                                                                                                        }
29269                                                                                                }
29270                                                                                                if (except) {
29271                                                                                                        continue;
29272                                                                                                }
29273                                                                                        }
29274                                                                                        ResultColumn resultColumn = modelFactory.createStarResultColumn(
29275                                                                                                        resultSetModel, column, tableColumn.getName(), tableColumn);
29276                                                                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
29277                                                                                        relation.setEffectType(EffectType.select);
29278                                                                                        relation.setTarget(new ResultColumnRelationshipElement(resultColumn));
29279                                                                                        relation.addSource(new TableColumnRelationshipElement(tableColumn));
29280                                                                                }
29281                                                                                if(stmt.getResultColumnList().size() == 1) {
29282                                                                                        resultSetModel.setDetermined(true);
29283                                                                                }
29284                                                                                else {
29285                                                                                        int starCount = 0;
29286                                                                                        for (int j = 0; j < stmt.getResultColumnList().size(); j++) {
29287                                                                                                if (isStarItem(stmt.getResultColumnList().getResultColumn(j))) {
29288                                                                                                        starCount += 1;
29289                                                                                                }
29290                                                                                        }
29291                                                                                        if (starCount <= 1) {
29292                                                                                                resultSetModel.setDetermined(true);
29293                                                                                        }
29294                                                                                }
29295                                                                                continue;
29296                                                                        } else if (tableModel instanceof ResultSet
29297                                                                                        && ((ResultSet) tableModel).isDetermined()) {
29298                                                                                ResultSet table = (ResultSet) tableModel;
29299                                                                                for (int j = 0; j < table.getColumns().size(); j++) {
29300                                                                                        ResultColumn tableColumn = table.getColumns().get(j);
29301                                                                                        if (column.getExceptColumnList() != null) {
29302                                                                                                boolean except = false;
29303                                                                                                for (TObjectName objectName : column.getExceptColumnList()) {
29304                                                                                                        if (DlineageUtil.sameColumnName(objectName.toString(), tableColumn.getName())) {
29305                                                                                                                except = true;
29306                                                                                                                break;
29307                                                                                                        }
29308                                                                                                }
29309                                                                                                if (!except && tableColumn.isStruct()) {
29310                                                                                                        List<String> names = SQLUtil
29311                                                                                                                        .parseNames(tableColumn.getName());
29312                                                                                                        for (String name : names) {
29313                                                                                                                for (TObjectName objectName : column
29314                                                                                                                                .getExceptColumnList()) {
29315                                                                                                                        if (DlineageUtil.sameColumnName(objectName.toString(), name)) {
29316                                                                                                                                except = true;
29317                                                                                                                                break;
29318                                                                                                                        }
29319                                                                                                                }
29320                                                                                                                if (except) {
29321                                                                                                                        break;
29322                                                                                                                }
29323                                                                                                        }
29324                                                                                                }
29325                                                                                                if (except) {
29326                                                                                                        continue;
29327                                                                                                }
29328                                                                                        }
29329                                                                                        if (tableColumn.getRefColumnName() != null) {
29330                                                                                                ResultColumn resultColumn = modelFactory.createStarResultColumn(
29331                                                                                                                (ResultSet)queryModel, column, tableColumn.getRefColumnName(), tableColumn);
29332                                                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
29333                                                                                                relation.setEffectType(EffectType.select);
29334                                                                                                relation.setTarget(new ResultColumnRelationshipElement(resultColumn));
29335                                                                                                relation.addSource(new ResultColumnRelationshipElement(tableColumn));
29336                                                                                        } else {
29337                                                                                                ResultColumn resultColumn = modelFactory.createStarResultColumn(
29338                                                                                                                resultSetModel, column, tableColumn.getName(), tableColumn);
29339                                                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
29340                                                                                                relation.setEffectType(EffectType.select);
29341                                                                                                relation.setTarget(new ResultColumnRelationshipElement(resultColumn));
29342                                                                                                relation.addSource(new ResultColumnRelationshipElement(tableColumn));
29343                                                                                        }
29344                                                                                }
29345                                                                                if(stmt.getResultColumnList().size() == 1) {
29346                                                                                        resultSetModel.setDetermined(true);
29347                                                                                }
29348                                                                                else {
29349                                                                                        int starCount = 0;
29350                                                                                        for (int j = 0; j < stmt.getResultColumnList().size(); j++) {
29351                                                                                                if (isStarItem(stmt.getResultColumnList().getResultColumn(j))) {
29352                                                                                                        starCount += 1;
29353                                                                                                }
29354                                                                                        }
29355                                                                                        if (starCount <= 1) {
29356                                                                                                resultSetModel.setDetermined(true);
29357                                                                                        }
29358                                                                                }
29359                                                                                continue;
29360                                                                        }
29361                                                                }
29362                                                                
29363                                                                ResultColumn resultColumn  = modelFactory.createResultColumn(resultSetModel, column);   
29364                                                                if (modelManager.getModel(sourceTable) instanceof Table) {
29365                                                                        Table tableModel = (Table) modelManager.getModel(sourceTable);
29366                                                                        if (tableModel != null) {
29367                                                                                modelFactory.createTableColumn(tableModel, columnObject, false);
29368                                                                        }
29369                                                                        TObjectName[] columns = modelManager.getTableColumns(sourceTable);
29370                                                                        for (int j = 0; j < columns.length; j++) {
29371                                                                                TObjectName columnName = columns[j];
29372                                                                                if (columnName == null || DlineageUtil.isStarName((columnName))) {
29373                                                                                        continue;
29374                                                                                }
29375                                                                                resultColumn.bindStarLinkColumn(columnName);
29376                                                                        }
29377
29378                                                                        if (tableModel.getColumns() != null) {
29379                                                                                for (int j = 0; j < tableModel.getColumns().size(); j++) {
29380                                                                                        TableColumn tableColumn = tableModel.getColumns().get(j);
29381                                                                                        TObjectName columnName = tableColumn.getColumnObject();
29382                                                                                        if (columnName == null || DlineageUtil.isStarName((columnName))) {
29383                                                                                                continue;
29384                                                                                        }
29385                                                                                        resultColumn.bindStarLinkColumn(columnName);
29386                                                                                }
29387                                                                        }
29388                                                                } else if (modelManager.getModel(sourceTable) instanceof QueryTable) {
29389                                                                        QueryTable tableModel = (QueryTable) modelManager.getModel(sourceTable);
29390                                                                        if (tableModel != null && !tableModel.getColumns().isEmpty()) {
29391                                                                                for (ResultColumn item : tableModel.getColumns()) {
29392                                                                                        if (item.hasStarLinkColumn()) {
29393                                                                                                for (TObjectName starLinkColumn : item.getStarLinkColumnList()) {
29394                                                                                                        resultColumn.bindStarLinkColumn(starLinkColumn);
29395                                                                                                }
29396                                                                                        } else if (item.getColumnObject() instanceof TObjectName) {
29397                                                                                                resultColumn.bindStarLinkColumn((TObjectName) item.getColumnObject());
29398                                                                                        } else if (item.getColumnObject() instanceof TResultColumn) {
29399                                                                                                TResultColumn queryTableColumn = (TResultColumn) item.getColumnObject();
29400                                                                                                TObjectName tableColumnObject = queryTableColumn.getFieldAttr();
29401                                                                                                if (tableColumnObject != null) {
29402                                                                                                        resultColumn.bindStarLinkColumn(tableColumnObject);
29403                                                                                                } else if (queryTableColumn.getAliasClause() != null) {
29404                                                                                                        resultColumn.bindStarLinkColumn(
29405                                                                                                                        queryTableColumn.getAliasClause().getAliasName());
29406                                                                                                }
29407                                                                                        }
29408                                                                                }
29409                                                                        }
29410                                                                }
29411                                                        } else {
29412                                                                ResultColumn resultColumn  = modelFactory.createResultColumn(resultSetModel, column);   
29413                                                                TTableList tables = stmt.getTables();
29414                                                                for (int k = 0; k < tables.size(); k++) {
29415                                                                        TTable table = tables.getTable(k);
29416                                                                        TObjectName[] columns = modelManager.getTableColumns(table);
29417                                                                        for (int j = 0; j < columns.length; j++) {
29418                                                                                TObjectName columnName = columns[j];
29419                                                                                if (columnName == null) {
29420                                                                                        continue;
29421                                                                                }
29422                                                                                if (DlineageUtil.isStarName((columnName))) {
29423                                                                                        if (modelManager.getModel(table) instanceof Table) {
29424                                                                                                Table tableModel = (Table) modelManager.getModel(table);
29425                                                                                                if (tableModel != null) {
29426                                                                                                        modelFactory.createTableColumn(tableModel, columnName, false);
29427                                                                                                }
29428                                                                                                if (tableModel != null && !tableModel.getColumns().isEmpty()) {
29429                                                                                                        for (int z = 0; z < tableModel.getColumns().size(); z++) {
29430                                                                                                                resultColumn.bindStarLinkColumn(
29431                                                                                                                                tableModel.getColumns().get(z).getColumnObject());
29432                                                                                                        }
29433                                                                                                }
29434                                                                                        } else if (modelManager.getModel(table) instanceof QueryTable) {
29435                                                                                                QueryTable tableModel = (QueryTable) modelManager.getModel(table);
29436                                                                                                if (tableModel != null && !tableModel.getColumns().isEmpty()) {
29437                                                                                                        for (ResultColumn item : tableModel.getColumns()) {
29438                                                                                                                if (item.hasStarLinkColumn()) {
29439                                                                                                                        for (TObjectName starLinkColumn : item
29440                                                                                                                                        .getStarLinkColumnList()) {
29441                                                                                                                                resultColumn.bindStarLinkColumn(starLinkColumn);
29442                                                                                                                        }
29443                                                                                                                } else if (item.getColumnObject() instanceof TObjectName) {
29444                                                                                                                        resultColumn.bindStarLinkColumn(
29445                                                                                                                                        (TObjectName) item.getColumnObject());
29446                                                                                                                } else if (item.getColumnObject() instanceof TResultColumn) {
29447                                                                                                                        TResultColumn queryTableColumn = (TResultColumn) item
29448                                                                                                                                        .getColumnObject();
29449                                                                                                                        TObjectName tableColumnObject = queryTableColumn
29450                                                                                                                                        .getFieldAttr();
29451                                                                                                                        if (tableColumnObject != null) {
29452                                                                                                                                resultColumn.bindStarLinkColumn(tableColumnObject);
29453                                                                                                                        } else if (queryTableColumn.getAliasClause() != null) {
29454                                                                                                                                resultColumn.bindStarLinkColumn(queryTableColumn
29455                                                                                                                                                .getAliasClause().getAliasName());
29456                                                                                                                        }
29457                                                                                                                }
29458                                                                                                        }
29459                                                                                                }
29460                                                                                        }
29461                                                                                        continue;
29462                                                                                }
29463                                                                                resultColumn.bindStarLinkColumn(columnName);
29464                                                                        }
29465                                                                }
29466                                                        }
29467                                                }
29468                                                else {
29469                                                        ResultColumn resultColumn  = modelFactory.createResultColumn(resultSetModel, column);   
29470                                                }
29471                                                
29472                                                analyzeResultColumn(column, EffectType.select);
29473                                                if (!isStarItem(column)) {
29474                                                        // a STRUCT the query of a derived table builds, or passes on, keeps its fields: field
29475                                                        // nodes below the item's one column (rule R8, Mantis 4893)
29476                                                        addStructFieldNodes(resultSetModel, column);
29477                                                }
29478
29479                                        }
29480                                        
29481                                        if (queryModel instanceof ResultSet) {
29482                                                boolean isDetermined = true;
29483                                                ResultSet resultSet = (ResultSet) queryModel;
29484                                                for (ResultColumn column : resultSet.getColumns()) {
29485                                                        if (column.isStarColumn()) {
29486                                                                isDetermined = false;
29487                                                                break;
29488                                                        }
29489                                                }
29490                                                if (isDetermined) {
29491                                                        resultSet.setDetermined(isDetermined);
29492                                                }
29493                                        }
29494                                }
29495                        }
29496
29497                
29498                        analyzeSelectIntoClause(stmt);
29499                
29500
29501                        if (stmt.getJoins() != null && stmt.getJoins().size() > 0) {
29502                                for (int i = 0; i < stmt.getJoins().size(); i++) {
29503                                        TJoin join = stmt.getJoins().getJoin(i);
29504                                        ResultSet topResultSet = (ResultSet) modelManager.getModel(stmt);
29505                                        if (join.getJoinItems() != null && join.getJoinItems().size() > 0) {
29506                                                for (int k = 0; k < join.getJoinItems().size(); k++) {
29507                                                        TTable table = join.getJoinItems().getJoinItem(k).getTable();
29508                                                        if (table != null && table.getSubquery() != null) {
29509                                                                
29510                                                                // A VALUES derived table has no result set of its own (Mantis 4802): its columns
29511                                                                // live on the query table analyzeValuesDerivedTable filled, already linked to the
29512                                                                // select list, so there is nothing to match here.
29513                                                                ResultSet joinResultSet = (ResultSet) modelManager.getModel(table.getSubquery());
29514                                                                for (int x = 0; joinResultSet != null && x < joinResultSet.getColumns().size(); x++) {
29515                                                                        ResultColumn sourceColumn = joinResultSet.getColumns().get(x);
29516                                                                        ResultColumn resultColumn = matchResultColumn(topResultSet.getColumns(),
29517                                                                                        sourceColumn);
29518                                                                        if (resultColumn != null
29519                                                                                        && resultColumn.getColumnObject() instanceof TResultColumn) {
29520                                                                                TResultColumn column = (TResultColumn) resultColumn.getColumnObject();
29521                                                                                if (column.getAliasClause() == null && column.getFieldAttr() != null) {
29522                                                                                        TObjectName resultObject = column.getFieldAttr();
29523                                                                                        if (resultObject.getSourceTable() == null
29524                                                                                                        || resultObject.getSourceTable().equals(table)) {
29525                                                                                                DataFlowRelationship combinedQueryRelation = modelFactory
29526                                                                                                                .createDataFlowRelation();
29527                                                                                                combinedQueryRelation.setEffectType(EffectType.select);
29528                                                                                                combinedQueryRelation
29529                                                                                                                .setTarget(new ResultColumnRelationshipElement(resultColumn));
29530                                                                                                combinedQueryRelation
29531                                                                                                                .addSource(new ResultColumnRelationshipElement(sourceColumn));
29532                                                                                        }
29533                                                                                }
29534                                                                        }
29535                                                                }
29536                                                        }
29537                                                        
29538                                                        if(join.getJoinItems().getJoinItem(k).getJoin()!=null) {
29539                                                                analyzeJoin(join.getJoinItems().getJoinItem(k).getJoin(), EffectType.select);
29540                                                        }
29541                                                }
29542                                        }
29543                                        analyzeJoin(join, EffectType.select);
29544                                }
29545                        }
29546
29547                        if (stmt.getWhereClause() != null) {
29548                                TExpression expr = stmt.getWhereClause().getCondition();
29549                                if (expr != null) {
29550                                        analyzeFilterCondition(null, expr, null, JoinClauseType.where, EffectType.select);
29551                                }
29552                        }
29553
29554                        // AS OF (SELECT ...) (Oracle, Teradata), AT(TIMESTAMP => (SELECT ...)) (Snowflake): the point in time that a
29555                        // subquery reads picks the rows read from the table, as a filter does (Mantis 4920)
29556                        for (int i = 0; i < stmt.tables.size(); i++) {
29557                                TExpression pointInTime = stmt.tables.getTable(i).getPointInTime();
29558                                if (pointInTime != null && containsSubquery(pointInTime)) {
29559                                        analyzeFilterCondition(null, pointInTime, null, JoinClauseType.where, EffectType.select);
29560                                }
29561                        }
29562
29563                        // HAVING filters the groups as WHERE filters the rows: the same fdr on the result's rows
29564                        if (stmt.getGroupByClause() != null && stmt.getGroupByClause().getHavingClause() != null) {
29565                                analyzeFilterCondition(null, stmt.getGroupByClause().getHavingClause(), null, JoinClauseType.having,
29566                                                EffectType.select);
29567                        }
29568
29569                        stmtStack.pop();
29570                }
29571        }
29572
29573        /** Whether a subquery, EXISTS included, stands anywhere in {@code expr}, the object of a method call too. */
29574        private static boolean containsSubquery(TExpression expr) {
29575                final boolean[] found = { false };
29576                expr.acceptChildren(new TParseTreeVisitor() {
29577                        @Override
29578                        public void preVisit(TExpression node) {
29579                                if (node.getSubQuery() != null) {
29580                                        found[0] = true;
29581                                } else if (node.getExpressionType() == EExpressionType.object_access_t && node.getObjectAccess() != null
29582                                                && node.getObjectAccess().getObjectExpr() != null) {
29583                                        // TObjectAccess visits nothing: XMLTYPE((SELECT ...)).getStringVal()
29584                                        node.getObjectAccess().getObjectExpr().acceptChildren(this);
29585                                }
29586                        }
29587                });
29588                return found[0];
29589        }
29590
29591        protected TObjectNameList getTableLinkedColumns(TTable table) {
29592                if(structObjectMap.containsKey(table)) {
29593                        return structObjectMap.get(table);
29594                }
29595                return table.getLinkedColumns();
29596        }
29597
29598        protected boolean isTopResultSet(TSelectSqlStatement stmt) {
29599                TCustomSqlStatement parent = stmt.getParentStmt();
29600                if (parent == null)
29601                        return true;
29602                if (parent instanceof TMssqlReturn) {
29603                        return true;
29604                }
29605                if (parent instanceof TReturnStmt) {
29606                        return true;
29607                }
29608                if (parent instanceof TCommonBlock) {
29609                        TCommonBlock block = (TCommonBlock) parent;
29610                        if (block.getStatements() != null) {
29611                                for (int i = 0; i < block.getStatements().size(); i++) {
29612                                        TCustomSqlStatement child = block.getStatements().get(i);
29613                                        if(stmt == child) {
29614                                                return true;
29615                                        }
29616                                }
29617                        }
29618                }
29619                if (parent instanceof TMssqlBlock) {
29620                        TMssqlBlock block = (TMssqlBlock) parent;
29621                        if (block.getStatements() != null) {
29622                                for (int i = 0; i < block.getStatements().size(); i++) {
29623                                        TCustomSqlStatement child = block.getStatements().get(i);
29624                                        if(stmt == child) {
29625                                                return true;
29626                                        }
29627                                }
29628                        }
29629                }
29630                if (parent instanceof TStoredProcedureSqlStatement) {
29631                        TStoredProcedureSqlStatement block = (TStoredProcedureSqlStatement) parent;
29632                        if (block.getStatements() != null) {
29633                                for (int i = 0; i < block.getStatements().size(); i++) {
29634                                        TCustomSqlStatement child = block.getStatements().get(i);
29635                                        if(child == stmt) {
29636                                                return true;
29637                                        }
29638                                        if (child instanceof TReturnStmt) {
29639                                                TReturnStmt returnStmt = (TReturnStmt) child;
29640                                                if (returnStmt.getStatements() != null) {
29641                                                        for (int j = 0; j < returnStmt.getStatements().size(); j++) {
29642                                                                TCustomSqlStatement child1 = returnStmt.getStatements().get(j);
29643                                                                if(child1 == stmt) {
29644                                                                        return true;
29645                                                                }
29646                                                        }
29647                                                }
29648                                        }
29649                                        if (child instanceof TMssqlReturn) {
29650                                                TMssqlReturn returnStmt = (TMssqlReturn) child;
29651                                                if (returnStmt.getStatements() != null) {
29652                                                        for (int j = 0; j < returnStmt.getStatements().size(); j++) {
29653                                                                TCustomSqlStatement child1 = returnStmt.getStatements().get(j);
29654                                                                if(child1 == stmt) {
29655                                                                        return true;
29656                                                                }
29657                                                        }
29658                                                }
29659                                        }
29660                                }
29661                        }
29662                }
29663                return false;
29664        }
29665
29666        protected void analyzeTableSubquery(TTable table) {
29667                if(table.getSubquery()!=null) {
29668                        QueryTable queryTable = modelFactory.createQueryTable(table);
29669                        TSelectSqlStatement subquery = table.getSubquery();
29670                        analyzeSelectStmt(subquery);
29671
29672                        ResultSet resultSetModel = (ResultSet) modelManager.getModel(subquery);
29673
29674                        if (resultSetModel != null && resultSetModel != queryTable
29675                                        && !resultSetModel.getRelationRows().getHoldRelations().isEmpty()) {
29676                                ImpactRelationship impactRelation = modelFactory.createImpactRelation();
29677                                impactRelation.setEffectType(EffectType.select);
29678                                impactRelation.addSource(new RelationRowsRelationshipElement<ResultSetRelationRows>(
29679                                                resultSetModel.getRelationRows()));
29680                                impactRelation.setTarget(new RelationRowsRelationshipElement<ResultSetRelationRows>(
29681                                                queryTable.getRelationRows()));
29682                        }
29683
29684                        if (resultSetModel != null && resultSetModel != queryTable
29685                                        && queryTable.getTableObject().getAliasClause() != null
29686                                        && queryTable.getTableObject().getAliasClause().getColumns() != null) {
29687                                for (int j = 0; j < queryTable.getColumns().size()
29688                                                && j < resultSetModel.getColumns().size(); j++) {
29689                                        ResultColumn sourceColumn = resultSetModel.getColumns().get(j);
29690                                        ResultColumn targetColumn = queryTable.getColumns().get(j);
29691
29692                                        DataFlowRelationship queryRalation = modelFactory.createDataFlowRelation();
29693                                        queryRalation.setEffectType(EffectType.select);
29694                                        queryRalation.setTarget(new ResultColumnRelationshipElement(targetColumn));
29695                                        queryRalation.addSource(new ResultColumnRelationshipElement(sourceColumn));
29696                                }
29697                        } else if (subquery.getSetOperatorType() != ESetOperatorType.none) {
29698                                SelectSetResultSet selectSetResultSetModel = (SelectSetResultSet) modelManager
29699                                                .getModel(subquery);
29700                                for (int j = 0; j < selectSetResultSetModel.getColumns().size(); j++) {
29701                                        ResultColumn sourceColumn = selectSetResultSetModel.getColumns().get(j);
29702                                        ResultColumn targetColumn = modelFactory.createSelectSetResultColumn(queryTable,
29703                                                        sourceColumn);
29704                                        for (TObjectName starLinkColumn : sourceColumn.getStarLinkColumnList()) {
29705                                                targetColumn.bindStarLinkColumn(starLinkColumn);
29706                                        }
29707                                        DataFlowRelationship selectSetRalation = modelFactory.createDataFlowRelation();
29708                                        selectSetRalation.setEffectType(EffectType.select);
29709                                        selectSetRalation.setTarget(new ResultColumnRelationshipElement(targetColumn));
29710                                        selectSetRalation.addSource(new ResultColumnRelationshipElement(sourceColumn));
29711                                }
29712                        }
29713                }
29714        }
29715
29716        private ResultColumn getPivotedTableColumn(TPivotedTable pivotedTable, TObjectName columnName) {
29717                List<TPivotClause> pivotClauses = new ArrayList<TPivotClause>();
29718                if (pivotedTable.getPivotClause() != null) {
29719                        pivotClauses.add(pivotedTable.getPivotClause());
29720                }
29721                if (pivotedTable.getPivotClauseList() != null) {
29722                        for (int i = 0; i < pivotedTable.getPivotClauseList().size(); i++) {
29723                                pivotClauses.add(pivotedTable.getPivotClauseList().getElement(i));
29724                        }
29725                }
29726                // identity where the reference and the generated column both carry one ("Store" is not Store in Oracle), else
29727                // the display comparison this lookup always made: a generated name is display text
29728                CanonKey referenceKey = DlineageUtil.columnKey(columnName);
29729                for (TPivotClause clause : pivotClauses) {
29730                        Object model = modelManager.getModel(clause);
29731                        if (model instanceof PivotedTable) {
29732                                PivotedTable pivotedTableModel = (PivotedTable) model;
29733                                if (pivotedTableModel.getColumns() != null) {
29734                                        for (ResultColumn column : pivotedTableModel.getColumns()) {
29735                                                if (referenceKey != null && column.getIdentifierKey() != null
29736                                                                ? referenceKey.equals(column.getIdentifierKey())
29737                                                                : DlineageUtil.sameColumnDisplay(SQLUtil.trimColumnStringQuote(column.getName()), columnName)) {
29738                                                        return column;
29739                                                }
29740                                        }
29741                                }
29742                        }
29743                }
29744                return null;
29745        }
29746
29747        private void analyzeHiveTransformClause(TSelectSqlStatement stmt, THiveTransformClause transformClause) {
29748                Table mapSourceTable = null;
29749                QueryTable mapQueryTable = null;
29750                if(stmt.getTables()!=null) {
29751                        for(int i=0;i<stmt.getTables().size();i++) {
29752                                TTable table = stmt.getTables().getTable(i);
29753                                if (table.getSubquery() != null) {
29754                                        if (transformClause.getTransformType() == ETransformType.ettReduce) {
29755                                                mapQueryTable = modelFactory.createQueryTable(table);
29756                                        }
29757                                        analyzeSelectStmt(table.getSubquery());
29758                                }
29759                                else {
29760                                        mapSourceTable = modelFactory.createTable(table);
29761                                }
29762                        }
29763                }
29764                
29765                if (transformClause.getTransformType() == ETransformType.ettReduce) {
29766                        modelFactory.createResultSet(stmt, false);
29767                }
29768                
29769                List<TableColumn> mapTableColumns = new ArrayList<TableColumn>();
29770                List<ResultColumn> mapResultSetColumns = new ArrayList<ResultColumn>();
29771                List<ResultColumn> redueResultSetColumns = new ArrayList<ResultColumn>();
29772                
29773                if(transformClause.getExpressionList()!=null) {
29774                        for(TExpression expression: transformClause.getExpressionList()) {
29775                                if(expression.getObjectOperand()!=null) {
29776                                        if (transformClause.getTransformType() == ETransformType.ettMap || transformClause.getTransformType() == ETransformType.ettSelect) {
29777                                                if (mapSourceTable != null) {
29778                                                        TableColumn tableColumn = modelFactory.createTableColumn(mapSourceTable,
29779                                                                        expression.getObjectOperand(), false);
29780                                                        if (tableColumn != null) {
29781                                                                mapTableColumns.add(tableColumn);
29782                                                        }
29783                                                }
29784                                        }
29785                                        else if (transformClause.getTransformType() == ETransformType.ettReduce) {
29786                                                if (mapQueryTable != null) {
29787                                                        ResultColumn resultColumn = modelFactory.createResultColumn(mapQueryTable,
29788                                                                        expression.getObjectOperand(), false);
29789                                                        if (resultColumn != null) {
29790                                                                mapResultSetColumns.add(resultColumn);
29791                                                        }
29792                                                }
29793                                        }
29794                                }
29795                        }
29796                }
29797                
29798                if (transformClause.getAliasClause() != null) {
29799                        Object model = modelManager.getModel(stmt);
29800                        if (model instanceof ResultSet) {
29801                                ResultSet result = (ResultSet) model;
29802                                if (result!=null && transformClause.getAliasClause().getColumns() != null) {
29803                                        for (TObjectName column : transformClause.getAliasClause().getColumns()) {
29804                                                ResultColumn resultColumn = modelFactory.createResultColumn(result, column);
29805                                                if (resultColumn != null) {
29806                                                        if (transformClause.getTransformType() == ETransformType.ettMap 
29807                                                                        || transformClause.getTransformType() == ETransformType.ettSelect) {
29808                                                                mapResultSetColumns.add(resultColumn);
29809                                                        }
29810                                                        else if (transformClause.getTransformType() == ETransformType.ettReduce) {
29811                                                                redueResultSetColumns.add(resultColumn);
29812                                                        }
29813                                                }
29814                                        }
29815                                }
29816                        }
29817                }
29818                
29819                if (transformClause.getTransformType() == ETransformType.ettMap 
29820                                || transformClause.getTransformType() == ETransformType.ettSelect) {
29821                        if (!mapTableColumns.isEmpty() && !mapResultSetColumns.isEmpty()) {
29822                                for (ResultColumn resultColumn : mapResultSetColumns) {
29823                                        for (TableColumn tableColumn : mapTableColumns) {
29824                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
29825                                                relation.setTarget(new ResultColumnRelationshipElement(resultColumn));
29826                                                relation.addSource(new TableColumnRelationshipElement(tableColumn));
29827                                                relation.setEffectType(EffectType.select);
29828                                        }
29829                                }
29830                        }
29831                }
29832                else if (transformClause.getTransformType() == ETransformType.ettReduce) {
29833                        if (!redueResultSetColumns.isEmpty() && !mapResultSetColumns.isEmpty()) {
29834                                for (ResultColumn reduceResultColumn : redueResultSetColumns) {
29835                                        for (ResultColumn mapResultColumn : mapResultSetColumns) {
29836                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
29837                                                relation.setTarget(new ResultColumnRelationshipElement(reduceResultColumn));
29838                                                relation.addSource(new ResultColumnRelationshipElement(mapResultColumn));
29839                                                relation.setEffectType(EffectType.select);
29840                                        }
29841                                }
29842                        }
29843                }
29844        }
29845
29846        /**
29847         * Whether a reference names a column of a table model: by identity where both carry one, else by display, the
29848         * comparison this check always made (a model column's name is display text).
29849         */
29850        private static boolean sameTableColumn(TObjectName reference, TableColumn column) {
29851                CanonKey referenceKey = DlineageUtil.columnKey(reference);
29852                return referenceKey != null && column.getIdentifierKey() != null ? referenceKey.equals(column.getIdentifierKey())
29853                                : DlineageUtil.sameColumnDisplay(column.getName(), reference);
29854        }
29855
29856        protected boolean isStructColumn(TObjectName columnName) {
29857                return columnName.getSourceTable() != null && columnName.getSourceTable().getAliasClause() != null
29858                                && columnName.getSourceTable().getUnnestClause() != null
29859                                && DlineageUtil.sameColumnName(columnName, columnName.getSourceTable().getAliasClause().getAliasName());
29860        }
29861
29862        private TObjectName getObjectName(ResultColumn column) {
29863                if (column.getColumnObject() instanceof TResultColumn) {
29864                        TResultColumn resultColumn = (TResultColumn) column.getColumnObject();
29865                        if (resultColumn.getAliasClause() != null && resultColumn.getAliasClause().getAliasName() != null) {
29866                                return resultColumn.getAliasClause().getAliasName();
29867                        }
29868                        if (resultColumn.getFieldAttr() != null) {
29869                                return resultColumn.getFieldAttr();
29870                        }
29871                        if (resultColumn.getExpr() != null
29872                                        && resultColumn.getExpr().getExpressionType() == EExpressionType.simple_object_name_t) {
29873                                return resultColumn.getExpr().getObjectOperand();
29874                        }
29875                } else if (column.getColumnObject() instanceof TObjectName) {
29876                        return (TObjectName) column.getColumnObject();
29877                }
29878                return null;
29879        }
29880
29881        private boolean isShowTopSelectResultSet() {
29882                if (option.isSimpleOutput() && !option.isSimpleShowTopSelectResultSet())
29883                        return false;
29884                return true;
29885        }
29886
29887        /**
29888         * The record field the i-th SELECT ... INTO item binds to (#695).
29889         * SELECT INTO a record is POSITIONAL: item k fills field k. Binding
29890         * get(0) put every select item on the first field once records expanded
29891         * to per-field columns — five sources asserted on one field, none on the
29892         * others. Items beyond the known fields land on the record's star
29893         * (an unresolved component), never on a wrong field.
29894         */
29895        private TableColumn selectIntoRecordColumn(Table tableModel, TableColumn variableColumn, int ordinal) {
29896                if (variableColumn != null) {
29897                        return variableColumn;
29898                }
29899                List<TableColumn> columns = tableModel.getColumns();
29900                if (columns == null || columns.isEmpty()) {
29901                        return null;
29902                }
29903                if (columns.size() == 1) {
29904                        return columns.get(0);
29905                }
29906                if (!(tableModel instanceof Variable) || !((Variable) tableModel).isTypeOrderedFields()) {
29907                        // Fields discovered from consumption carry no positional meaning,
29908                        // and expanding a star here fans every select item onto every
29909                        // consumed pseudo-field. Keep the historical whole-record binding.
29910                        return columns.get(0);
29911                }
29912                int k = 0;
29913                for (TableColumn column : columns) {
29914                        if (column.isStarColumn()) {
29915                                continue;
29916                        }
29917                        if (k == ordinal) {
29918                                return column;
29919                        }
29920                        k++;
29921                }
29922                return tableStarColumn(tableModel);
29923        }
29924
29925        private static boolean hasRealTokenPosition(Pair3<Long, Long, String> start) {
29926                return start != null && start.first != null && start.second != null && start.first > 0
29927                                && !(start.first == 1L && start.second == 1L);
29928        }
29929
29930        /**
29931         * A SELECT INTO target column minted from a name-only object (star-expanded
29932         * columns of SELECT * INTO #t, set-operation and expanded-star branches) has
29933         * no source token, so its position renders as the synthetic
29934         * "[1,1,0],[1,n,0]" start-of-module coordinate. Give it the producing
29935         * result column's span instead (for a star column, the '*' token), so
29936         * definition-site consumers see an honest coordinate.
29937         */
29938        private static void fillSyntheticColumnPosition(TableColumn tableColumn, ResultColumn producedBy) {
29939                if (tableColumn == null || producedBy == null) {
29940                        return;
29941                }
29942                if (hasRealTokenPosition(tableColumn.getStartPosition())) {
29943                        return;
29944                }
29945                if (!hasRealTokenPosition(producedBy.getStartPosition())) {
29946                        return;
29947                }
29948                tableColumn.setStartPosition(producedBy.getStartPosition());
29949                tableColumn.setEndPosition(producedBy.getEndPosition());
29950        }
29951
29952        private void analyzeSelectIntoClause(TSelectSqlStatement stmt) {
29953                if (stmt.getParentStmt() instanceof TSelectSqlStatement) {
29954                        return;
29955                }
29956                
29957                // Keep Oracle trigger pseudo-record fields aligned with their INTO
29958                // expressions. A single shared slot made every source position target the
29959                // last trigger correlation field in a multi-column list (MantisBT 4707).
29960                List<TObjectName> oracleIntoColumnNames = new ArrayList<TObjectName>();
29961                
29962                TIntoClause intoClause = stmt.getIntoClause();
29963                
29964                TSelectSqlStatement leftStmt = DlineageUtil.getLeftStmt(stmt);
29965                
29966                if (intoClause == null && leftStmt != null) {
29967                        intoClause = leftStmt.getIntoClause();
29968                }
29969                
29970                if (intoClause != null) {
29971                        List<TObjectName> tableNames = new ArrayList<TObjectName>();
29972                        if (intoClause.getExprList() != null) {
29973                                for (int j = 0; j < intoClause.getExprList().size(); j++) {
29974                                        TObjectName tableName = intoClause.getExprList().getExpression(j).getObjectOperand();
29975                                        if (tableName != null) {
29976                                                if (tableName.toString().startsWith(":") && option.getVendor() == EDbVendor.dbvoracle
29977                                                                && tableName.getDbObjectType() == EDbObjectType.column) {
29978                                                        TObjectName tableAlias = new TObjectName();
29979                                                        if (!SQLUtil.isEmpty(tableName.getTableString())) {
29980                                                                tableAlias.setString(tableName.getTableString());
29981                                                        }
29982                                                        else {
29983                                                                tableAlias.setString(tableName.toString());
29984                                                        }
29985                                                        tableNames.add(tableAlias);
29986                                                        TTable sourceTable = tableName.getSourceTable();
29987                                                        Table boundTable = modelManager.visibleLocal(tableAlias.toString(),
29988                                                                        LocalSymbolKey.Kind.CORRELATION);
29989                                                        boolean sourceMatchesBinding = sourceTable != null && boundTable != null
29990                                                                        && SQLUtil.sameName(option.getVendor(), ESQLDataObjectType.dotTable,
29991                                                                                        boundTable.getName(), sourceTable.getTableName().toString());
29992                                                        if (sourceTable != null && (boundTable == null || sourceMatchesBinding)) {
29993                                                                // Prefer a trigger-correlation binding registered by
29994                                                                // bindTriggerCorrelationNames(). Resolver fallback can
29995                                                                // otherwise misclassify a custom REFERENCING alias as the
29996                                                                // SELECT's only FROM table.
29997                                                                modelFactory.createTable(sourceTable, tableAlias);
29998                                                        }
29999                                                        // A bare host bind such as :first_name is a scalar
30000                                                        // variable, not a pseudo-record field. Preserve its
30001                                                        // historical result-column-derived name.
30002                                                        oracleIntoColumnNames.add(SQLUtil.isEmpty(tableName.getTableString())
30003                                                                        ? null : tableName);
30004                                                } else {
30005                                                        if (tableName != null) {
30006                                                                tableNames.add(tableName);
30007                                                                oracleIntoColumnNames.add(null);
30008                                                        }
30009                                                }
30010                                        }
30011                                        else if(intoClause.getExprList().getExpression(j).getFunctionCall()!=null) {
30012                                                TObjectName variableName = intoClause.getExprList().getExpression(j).getFunctionCall().getFunctionName();
30013                                                tableNames.add(variableName);
30014                                                oracleIntoColumnNames.add(null);
30015                                                Variable variable = modelFactory.createReferencedVariable(variableName);
30016                                                variable.setSubType(SubType.record);
30017                                                TObjectName variableProperties = DlineageUtil.syntheticStarName();
30018                                                modelFactory.createTableColumn(variable, variableProperties, true);
30019                                        }
30020                                }
30021                        } else if (intoClause.getVariableList() != null) {
30022                                for (int j = 0; j < intoClause.getVariableList().size(); j++) {
30023                                        TObjectName tableName = intoClause.getVariableList().getObjectName(j);
30024                                        if (tableName != null) {
30025                                                tableNames.add(tableName);
30026                                                oracleIntoColumnNames.add(null);
30027                                        }
30028                                }
30029                        } else if (intoClause.getIntoName() != null) {
30030                                tableNames.add(intoClause.getIntoName());
30031                                oracleIntoColumnNames.add(null);
30032                        }
30033
30034                        ResultSet queryModel = (ResultSet) modelManager.getModel(stmt.getResultColumnList());
30035                        if (stmt.getSetOperatorType() != ESetOperatorType.none) {
30036                                queryModel = (ResultSet) modelManager.getModel(stmt);
30037                        }
30038                        for (int j = 0; j < tableNames.size(); j++) {
30039                                TObjectName tableName = tableNames.get(j);
30040                                TObjectName oracleIntoColumnName = oracleIntoColumnNames.get(j);
30041                                if (tableName.getColumnNameOnly().startsWith("@")
30042                                                && (option.getVendor() == EDbVendor.dbvmssql || option.getVendor() == EDbVendor.dbvazuresql)) {
30043                                        continue;
30044                                }
30045
30046                                if (tableName.getColumnNameOnly().startsWith(":")
30047                                                && (option.getVendor() == EDbVendor.dbvhana || option.getVendor() == EDbVendor.dbvteradata)) {
30048                                        continue;
30049                                }
30050
30051                                Table tableModel;
30052                                TableColumn variableColumn = null;
30053                                // PL/pgSQL runs no SELECT ... INTO through EXECUTE ("EXECUTE of SELECT ... INTO is not
30054                                // implemented"), so the INTO name of one in dynamic SQL is no table it creates (Mantis 4857)
30055                                boolean variableTarget = tableName.getDbObjectType() == EDbObjectType.variable
30056                                                || (dynamicFoldDepth > 0 && intoClause.getVariableList() != null);
30057
30058                                if (variableTarget) {
30059                                        if (tableName.toString().indexOf(".") != -1) {
30060                                                List<String> splits = SQLUtil.parseNames(tableName.toString());
30061                                                tableModel = modelFactory.createReferencedVariable(splits.get(splits.size() - 2), true);
30062                                                // INTO rec.field names its target field explicitly (#695);
30063                                                // bind that field, never an ordinal of the record.
30064                                                variableColumn = modelFactory.createTableColumn(tableModel, tableName, true);
30065                                        } else {
30066                                                tableModel = modelFactory.createReferencedVariable(tableName);
30067                                        }
30068                                        if (tableModel.getSubType() == null) {
30069                                                tableModel.setSubType(SubType.record);
30070                                        }
30071                                        if(variableColumn == null
30072                                                        && (tableModel.getColumns() == null || tableModel.getColumns().isEmpty())) {
30073                                                TObjectName variableProperties = DlineageUtil.syntheticStarName();
30074                                                variableColumn = modelFactory.createTableColumn(tableModel, variableProperties, true);
30075                                        }
30076                                } else {
30077                                        tableModel = modelFactory.createTableByName(tableName);
30078                                        // SELECT ... INTO <table> creates the target. Parse fact, set
30079                                        // regardless of isDetermined() (unlike the gated setCreateTable
30080                                        // below). See dlineage-authoritative-endpoint-classification.md.
30081                                        tableModel.setCreatedInSql(true);
30082                                        tableModel.setEndpointIntroduction(EndpointIntroduction.SELECT_INTO);
30083                                }
30084                                TableColumn oracleIntoTableColumn = oracleIntoColumnName == null ? null
30085                                                : modelFactory.createTableColumn(tableModel, oracleIntoColumnName, true);
30086
30087                                if (queryModel instanceof ResultSet && (stmt.getWhereClause() != null || hasJoin(stmt))) {
30088                                        ImpactRelationship impactRelation = modelFactory.createImpactRelation();
30089                                        impactRelation.setEffectType(EffectType.insert);
30090                                        impactRelation.addSource(
30091                                                        new RelationRowsRelationshipElement<ResultSetRelationRows>(((ResultSet)queryModel).getRelationRows()));
30092                                        impactRelation.setTarget(
30093                                                        new RelationRowsRelationshipElement<TableRelationRows>(tableModel.getRelationRows()));
30094                                }
30095                                
30096                                Process process = modelFactory.createProcess(stmt);
30097                                tableModel.addProcess(process);
30098                                
30099                                if (stmt.getSetOperatorType() != ESetOperatorType.none) {
30100                                        for (int setopOrdinal = 0; setopOrdinal < queryModel.getColumns().size(); setopOrdinal++) {
30101                                                ResultColumn resultColumn = queryModel.getColumns().get(setopOrdinal);
30102                                                if (tableNames.size() > 1
30103                                                                && (variableTarget
30104                                                                                || oracleIntoTableColumn != null)
30105                                                                && setopOrdinal != j) {
30106                                                        continue;
30107                                                }
30108                                                TableColumn tableColumn;
30109                                                if (oracleIntoTableColumn != null) {
30110                                                        tableColumn = oracleIntoTableColumn;
30111                                                } else if (tableModel.isVariable()) {
30112                                                        // SELECT ... INTO variables and typed records binds
30113                                                        // positionally even through a set operation (codex-696 F4).
30114                                                        tableColumn = selectIntoRecordColumn(tableModel, variableColumn, setopOrdinal);
30115                                                } else {
30116                                                        tableColumn = modelFactory.createInsertTableColumn(tableModel,
30117                                                                        resultColumn);
30118                                                        fillSyntheticColumnPosition(tableColumn, resultColumn);
30119                                                }
30120
30121                                                if (DlineageUtil.isTempTable(tableModel, option.getVendor()) && sqlenv != null
30122                                                                && tableModel.getDatabase() != null && tableModel.getSchema() != null) {
30123                                                        TSQLSchema schema = sqlenv
30124                                                                        .getSQLSchema(tableModel.getDatabase() + "." + tableModel.getSchema(), true);
30125                                                        if (schema != null) {
30126                                                                TSQLTable tempTable = schema
30127                                                                                .createTable(DlineageUtil.getSimpleTableName(tableModel.getName()));
30128                                                                if (tableColumn.isStarColumn()) {
30129                                                                        addUnresolvedStar(tempTable);
30130                                                                } else {
30131                                                                        tempTable.addColumn(tableColumn.getName());
30132                                                                }
30133                                                        }
30134                                                }
30135
30136                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
30137                                                relation.setEffectType(EffectType.insert);
30138                                                relation.setTarget(new TableColumnRelationshipElement(tableColumn));
30139                                                relation.addSource(new ResultColumnRelationshipElement(resultColumn));
30140                                                relation.setProcess(process);
30141                                        }
30142                                        
30143                                        tableModel.setDetermined(queryModel.isDetermined());
30144                                        recordSelectIntoTarget(stmt, tableModel, tableName, queryModel.isDetermined(), null);
30145                                        if (j + 1 == tableNames.size()) {
30146                                                return;
30147                                        }
30148                                        continue;
30149                                }
30150
30151                                boolean isDetermined = true;
30152                                // the column each select item writes, in order, while every item writes one known column
30153                                List<TableColumn> written = new ArrayList<TableColumn>();
30154                                for (int i = 0; i < stmt.getResultColumnList().size(); i++) {
30155                                        if (tableNames.size() > 1
30156                                                        && (variableTarget
30157                                                                        || oracleIntoTableColumn != null)) {
30158                                                if (i != j) {
30159                                                        continue;
30160                                                }
30161                                        }
30162                                        TResultColumn column = stmt.getResultColumnList().getResultColumn(i);
30163                                        // a star the analysis expanded writes its columns in order (Mantis 4876); one whose columns are not
30164                                        // known leaves the order of the table's columns unknown, and so does a USING or NATURAL join, whose
30165                                        // merged columns stand first (Codex review)
30166                                        boolean expandedStar = isStarItem(column) && column.getFieldAttr() != null
30167                                                        && column.getFieldAttr().getSourceTable() != null
30168                                                        && modelManager.getModel(column) instanceof LinkedHashMap && !mergesJoinColumns(stmt);
30169                                        if (isStarItem(column) && !expandedStar) {
30170                                                written = null;
30171                                        }
30172
30173                                        if (isStarItem(column) && column.getFieldAttr() != null
30174                                                        && column.getFieldAttr().getSourceTable() != null) {
30175                                                Object model = modelManager.getModel(column);
30176                                                if(model instanceof LinkedHashMap) {
30177                                                        LinkedHashMap<String, ResultColumn> columns = (LinkedHashMap<String, ResultColumn>)model;
30178                                                        int mapOrdinal = 0;
30179                                                        for(String key: columns.keySet()) {
30180                                                                ResultColumn sourceColumn = columns.get(key);
30181                                                                TableColumn tableColumn;
30182                                                                if (tableModel instanceof Variable
30183                                                                                && ((Variable) tableModel).isTypeOrderedFields()) {
30184                                                                        tableColumn = selectIntoRecordColumn(tableModel, null, mapOrdinal);
30185                                                                } else {
30186                                                                        tableColumn = itemColumnTarget(tableModel, sourceColumn);
30187                                                                        fillSyntheticColumnPosition(tableColumn, sourceColumn);
30188                                                                }
30189                                                                if (tableColumn != null && tableColumn.isStarColumn()) {
30190                                                                        // the * of a star over a join that expanded its other tables (Mantis 4981)
30191                                                                        isDetermined = false;
30192                                                                }
30193                                                                if (written != null) {
30194                                                                        if (tableColumn != null && !tableColumn.isStarColumn()) {
30195                                                                                written.add(tableColumn);
30196                                                                        } else {
30197                                                                                written = null;
30198                                                                        }
30199                                                                }
30200                                                                mapOrdinal++;
30201                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
30202                                                                relation.setEffectType(EffectType.insert);
30203                                                                relation.setTarget(new TableColumnRelationshipElement(tableColumn));
30204                                                                relation.addSource(new ResultColumnRelationshipElement(sourceColumn));
30205                                                                relation.setProcess(process);
30206                                                        }
30207                                                }
30208                                                else if(model instanceof ResultColumn) {
30209                                                        isDetermined = false;
30210                                                        ResultColumn resultColumn = (ResultColumn) model;
30211                                                        List<TObjectName> columns = resultColumn.getStarLinkColumnList();
30212                                                        if (columns.size() > 0) {
30213                                                                for (int k = 0; k < columns.size(); k++) {
30214        
30215                                                                        TableColumn tableColumn;
30216                                                                        if (tableModel instanceof Variable
30217                                                                                        && ((Variable) tableModel).isTypeOrderedFields()) {
30218                                                                                // SELECT * INTO a typed record fills its
30219                                                                                // DECLARED fields by position — creating
30220                                                                                // fields from source names left the real
30221                                                                                // fields with no upstream (codex-696 F4).
30222                                                                                tableColumn = selectIntoRecordColumn(tableModel, null, k);
30223                                                                        } else {
30224                                                                                tableColumn = modelFactory.createInsertTableColumn(tableModel,
30225                                                                                                columns.get(k));
30226                                                                                // Star-link names are synthesized without
30227                                                                                // tokens; the '*' span is the honest
30228                                                                                // definition site for the expanded column.
30229                                                                                fillSyntheticColumnPosition(tableColumn, resultColumn);
30230                                                                        }
30231
30232                                                                        if (DlineageUtil.isTempTable(tableModel, option.getVendor()) && sqlenv != null
30233                                                                                        && tableModel.getDatabase() != null && tableModel.getSchema() != null) {
30234                                                                                TSQLSchema schema = tableMetadataEnvironment(tableModel).getSQLSchema(
30235                                                                                                tableModel.getDatabase() + "." + tableModel.getSchema(), true);
30236                                                                                if (schema != null) {
30237                                                                                        TSQLTable tempTable = schema.createTable(DlineageUtil.getSimpleTableName(tableModel.getName()));
30238                                                                                        if (tableColumn.isStarColumn()) {
30239                                                                                                addUnresolvedStar(tempTable);
30240                                                                                        } else {
30241                                                                                                tempTable.addColumn(tableColumn.getName());
30242                                                                                        }
30243                                                                                }
30244                                                                        }
30245        
30246                                                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
30247                                                                        relation.setEffectType(EffectType.insert);
30248                                                                        relation.setTarget(new TableColumnRelationshipElement(tableColumn));
30249                                                                        relation.addSource(new ResultColumnRelationshipElement(resultColumn));
30250                                                                        relation.setProcess(process);
30251                                                                }
30252                                                                if (resultColumn.isShowStar()) {
30253                                                                        TableColumn tableColumn = modelFactory.createInsertTableColumn(tableModel,
30254                                                                                        column.getFieldAttr());
30255                                                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
30256                                                                        relation.setEffectType(EffectType.insert);
30257                                                                        relation.setTarget(new TableColumnRelationshipElement(tableColumn));
30258                                                                        relation.addSource(new ResultColumnRelationshipElement(resultColumn));
30259                                                                        relation.setProcess(process);
30260                                                                }
30261                                                        } else {
30262                                                                TObjectName columnObject = column.getFieldAttr();
30263                                                                if (column.getAliasClause() != null) {
30264                                                                        columnObject = column.getAliasClause().getAliasName();
30265                                                                }
30266                                                                if (columnObject != null) {
30267                                                                        TableColumn tableColumn = modelFactory.createInsertTableColumn(tableModel,
30268                                                                                        columnObject);
30269                                                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
30270                                                                        relation.setEffectType(EffectType.insert);
30271                                                                        relation.setTarget(new TableColumnRelationshipElement(tableColumn));
30272                                                                        relation.addSource(new ResultColumnRelationshipElement(resultColumn));
30273                                                                        relation.setProcess(process);
30274                                                                } else if (!SQLUtil.isEmpty(column.getColumnAlias())) {
30275                                                                        TableColumn tableColumn = modelFactory.createInsertTableColumn(tableModel,
30276                                                                                        column.getAliasClause().getAliasName());
30277                                                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
30278                                                                        relation.setEffectType(EffectType.insert);
30279                                                                        relation.setTarget(new TableColumnRelationshipElement(tableColumn));
30280                                                                        relation.addSource(new ResultColumnRelationshipElement(resultColumn));
30281                                                                        relation.setProcess(process);
30282                                                                }
30283                                                        }
30284                                                }
30285                                        } else {
30286                                                ResultColumn resultColumn = null;
30287
30288                                                if (queryModel instanceof QueryTable) {
30289                                                        resultColumn = (ResultColumn) modelManager.getModel(column);
30290                                                } else if (queryModel instanceof ResultSet) {
30291                                                        resultColumn = (ResultColumn) modelManager.getModel(column);
30292                                                } else {
30293                                                        continue;
30294                                                }
30295
30296                                                if (resultColumn == null && column.getAliasClause() != null) {
30297                                                        resultColumn = (ResultColumn) modelManager.getModel(column.getAliasClause().getAliasName());
30298                                                }
30299
30300                                                if (resultColumn != null) {
30301                                                        TObjectName columnObject = column.getFieldAttr();
30302                                                        if (column.getAliasClause() != null) {
30303                                                                columnObject = column.getAliasClause().getAliasName();
30304                                                        }
30305                                                        TableColumn tableColumn = null;
30306                                                        if (columnObject != null) {
30307                                                                if (tableModel.isVariable()) {
30308                                                                        tableColumn = selectIntoRecordColumn(tableModel, variableColumn, i);
30309                                                                } 
30310                                                                else if (oracleIntoTableColumn != null) {
30311                                                                        tableColumn = oracleIntoTableColumn;
30312                                                                }
30313                                                                else {
30314                                                                        tableColumn = modelFactory.createInsertTableColumn(tableModel, columnObject);
30315                                                                        if (containStarColumn(queryModel)) {
30316                                                                                tableColumn.notBindStarLinkColumn(true);
30317                                                                        }
30318                                                                }
30319                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
30320                                                                relation.setEffectType(EffectType.insert);
30321                                                                relation.setTarget(new TableColumnRelationshipElement(tableColumn));
30322                                                                relation.addSource(new ResultColumnRelationshipElement(resultColumn));
30323                                                                relation.setProcess(process);
30324                                                        } else if (!SQLUtil.isEmpty(column.getColumnAlias())) {
30325                                                                if (tableModel.isVariable()) {
30326                                                                        tableColumn = selectIntoRecordColumn(tableModel, variableColumn, i);
30327                                                                } else {
30328                                                                        tableColumn = modelFactory.createInsertTableColumn(tableModel,
30329                                                                                        column.getAliasClause().getAliasName());
30330                                                                }
30331                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
30332                                                                relation.setEffectType(EffectType.insert);
30333                                                                relation.setTarget(new TableColumnRelationshipElement(tableColumn));
30334                                                                relation.addSource(new ResultColumnRelationshipElement(resultColumn));
30335                                                                relation.setProcess(process);
30336                                                        } else {
30337                                                                if (tableModel.isVariable()) {
30338                                                                        tableColumn = selectIntoRecordColumn(tableModel, variableColumn, i);
30339                                                                }
30340                                                                if (tableColumn != null) {
30341                                                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
30342                                                                        relation.setEffectType(EffectType.insert);
30343                                                                        relation.setTarget(new TableColumnRelationshipElement(tableColumn));
30344                                                                        relation.addSource(new ResultColumnRelationshipElement(resultColumn));
30345                                                                        relation.setProcess(process);
30346                                                                }
30347                                                        }
30348                                                        if (written != null) {
30349                                                                if (tableColumn != null) {
30350                                                                        written.add(tableColumn);
30351                                                                } else {
30352                                                                        written = null;
30353                                                                }
30354                                                        }
30355                                                } else {
30356                                                        written = null;
30357                                                }
30358                                        }
30359                                }
30360                                tableModel.setDetermined(isDetermined);
30361                                recordSelectIntoTarget(stmt, tableModel, tableName, isDetermined, written);
30362                        }
30363                }
30364        }
30365
30366        private boolean isUnPivotedTable(TPivotedTable pivotedTable) {
30367                if (pivotedTable.getPivotClauseList() != null && pivotedTable.getPivotClauseList().size() > 0) {
30368                        return pivotedTable.getPivotClauseList().getElement(0).getType() == TPivotClause.unpivot;
30369                } else {
30370                        TPivotClause pivotClause = pivotedTable.getPivotClause();
30371                        return pivotClause.getType() == TPivotClause.unpivot;
30372                }
30373        }
30374
30375        private void analyzeUnPivotedTable(TSelectSqlStatement stmt, TPivotedTable pivotedTable) {
30376                List<Object> tables = new ArrayList<Object>();
30377                Set<Object> pivotedColumns = new HashSet<Object>();
30378                TTable fromTable = pivotedTable.getTableSource();
30379                Object table = modelManager.getModel(fromTable);
30380                List<TPivotClause> pivotClauses = new ArrayList<TPivotClause>();
30381                if (pivotedTable.getPivotClauseList() != null && pivotedTable.getPivotClauseList().size() > 0) {
30382                        for (int i = 0; i < pivotedTable.getPivotClauseList().size(); i++) {
30383                                pivotClauses.add(pivotedTable.getPivotClauseList().getElement(i));
30384                        }
30385                } else {
30386                        TPivotClause pivotClause = pivotedTable.getPivotClause();
30387                        pivotClauses.add(pivotClause);
30388                }
30389
30390                for (int y = 0; y < pivotClauses.size(); y++) {
30391                        TPivotClause pivotClause = pivotClauses.get(y);
30392                        PivotedTable pivotTable = modelFactory.createPivotdTable(pivotClause);
30393                        pivotTable.setUnpivoted(true);
30394
30395                        if (pivotClause.getValueColumnList() != null) {
30396                                for (int j = 0; j < pivotClause.getValueColumnList().size(); j++) {
30397                                        modelFactory.createResultColumn(pivotTable, pivotClause.getValueColumnList().getObjectName(j));
30398                                }
30399                        }
30400                        if (pivotClause.getPivotColumnList() != null) {
30401                                for (int j = 0; j < pivotClause.getPivotColumnList().size(); j++) {
30402                                        modelFactory.createResultColumn(pivotTable, pivotClause.getPivotColumnList().getObjectName(j));
30403                                }
30404                        }
30405                        if (pivotClause.getUnpivotInClause()!=null && pivotClause.getUnpivotInClause().getItems() != null) {
30406                                for (int j = 0; j < pivotClause.getUnpivotInClause().getItems().size(); j++) {
30407                                        TObjectName columnName = pivotClause.getUnpivotInClause().getItems().getElement(j).getColumn();
30408                                        if (columnName != null) {
30409                                                if (table instanceof QueryTable) {
30410                                                        for (ResultColumn tableColumn : ((QueryTable) table).getColumns()) {
30411                                                                if (DlineageUtil.columnMatches(tableColumn, columnName)) {
30412                                                                        for (ResultColumn resultColumn : pivotTable.getColumns()) {
30413                                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
30414                                                                                relation.setEffectType(EffectType.select);
30415                                                                                relation.setTarget(new ResultColumnRelationshipElement(resultColumn));
30416                                                                                relation.addSource(new ResultColumnRelationshipElement(tableColumn));
30417                                                                                pivotedColumns.add(tableColumn);
30418                                                                        }
30419                                                                        break;
30420                                                                }
30421                                                        }
30422                                                } else if (table instanceof Table) {
30423                                                        for (TableColumn tableColumn : ((Table) table).getColumns()) {
30424                                                                if (DlineageUtil.columnMatches(tableColumn, columnName)) {
30425                                                                        for (ResultColumn resultColumn : pivotTable.getColumns()) {
30426                                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
30427                                                                                relation.setEffectType(EffectType.select);
30428                                                                                relation.setTarget(new ResultColumnRelationshipElement(resultColumn));
30429                                                                                relation.addSource(new TableColumnRelationshipElement(tableColumn));
30430                                                                                pivotedColumns.add(tableColumn);
30431                                                                        }
30432                                                                        break;
30433                                                                }
30434                                                        }
30435                                                }
30436                                        } else {
30437                                                TObjectNameList columnNames = pivotClause.getUnpivotInClause().getItems().getElement(j)
30438                                                                .getColumnList();
30439                                                for (TObjectName columnName1 : columnNames) {
30440                                                        if (table instanceof QueryTable) {
30441                                                                for (ResultColumn tableColumn : ((QueryTable) table).getColumns()) {
30442                                                                        if (DlineageUtil.columnMatches(tableColumn, columnName1)) {
30443                                                                                for (ResultColumn resultColumn : pivotTable.getColumns()) {
30444                                                                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
30445                                                                                        relation.setEffectType(EffectType.select);
30446                                                                                        relation.setTarget(new ResultColumnRelationshipElement(resultColumn));
30447                                                                                        relation.addSource(new ResultColumnRelationshipElement(tableColumn));
30448                                                                                        pivotedColumns.add(tableColumn);
30449                                                                                }
30450                                                                                break;
30451                                                                        }
30452                                                                }
30453                                                        } else if (table instanceof Table) {
30454                                                                for (TableColumn tableColumn : ((Table) table).getColumns()) {
30455                                                                        if (DlineageUtil.columnMatches(tableColumn, columnName1)) {
30456                                                                                for (ResultColumn resultColumn : pivotTable.getColumns()) {
30457                                                                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
30458                                                                                        relation.setEffectType(EffectType.select);
30459                                                                                        relation.setTarget(new ResultColumnRelationshipElement(resultColumn));
30460                                                                                        relation.addSource(new TableColumnRelationshipElement(tableColumn));
30461                                                                                        pivotedColumns.add(tableColumn);
30462                                                                                }
30463                                                                                break;
30464                                                                        }
30465                                                                }
30466                                                        }
30467                                                }
30468                                        }
30469                                }
30470                        }
30471                        tables.add(pivotTable);
30472                        tables.add(table);
30473                }
30474
30475                ResultSet resultSet = modelFactory.createResultSet(stmt,
30476                                isTopResultSet(stmt) && isShowTopSelectResultSet());
30477                TResultColumnList columnList = stmt.getResultColumnList();
30478                for (int i = 0; i < columnList.size(); i++) {
30479                        TResultColumn column = columnList.getResultColumn(i);
30480                        ResultColumn resultColumn = modelFactory.createAndBindingSelectSetResultColumn(resultSet, column, i);
30481                        if (resultColumn.getColumnObject() instanceof TResultColumn) {
30482                                TResultColumn columnObject = (TResultColumn) resultColumn.getColumnObject();
30483                                if (columnObject.getFieldAttr() != null) {
30484                                        if (DlineageUtil.isStarName((columnObject.getFieldAttr()))) {
30485                                                resultColumn.setShowStar(false);
30486                                                int index = 0;
30487                                                for (int k = 0; k < tables.size(); k++) {
30488                                                        Object tableItem = tables.get(k);
30489                                                        if (tableItem instanceof ResultSet) {
30490                                                                for (int x = 0; x < ((ResultSet) tableItem).getColumns().size(); x++) {
30491                                                                        ResultColumn tableColumn = ((ResultSet) tableItem).getColumns().get(x);
30492                                                                        if (pivotedColumns.contains(tableColumn)) {
30493                                                                                continue;
30494                                                                        }
30495                                                                        if (tableColumn.getColumnObject() instanceof TObjectName) {
30496                                                                                resultColumn.bindStarLinkColumn((TObjectName) tableColumn.getColumnObject());
30497                                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
30498                                                                                relation.setEffectType(EffectType.select);
30499                                                                                relation.setTarget(new ResultColumnRelationshipElement(resultColumn, (TObjectName) tableColumn.getColumnObject()));
30500                                                                                relation.addSource(new ResultColumnRelationshipElement(tableColumn));
30501                                                                        } else if (tableColumn.getColumnObject() instanceof TResultColumn) {
30502                                                                                if (((TResultColumn) tableColumn.getColumnObject()).getFieldAttr() != null) {
30503                                                                                        if (tableColumn.hasStarLinkColumn()) {
30504                                                                                                for (int z = 0; z < tableColumn.getStarLinkColumnList().size(); z++) {
30505                                                                                                        ResultColumn resultColumn1 = modelFactory.createResultColumn(
30506                                                                                                                        (ResultSet) tableItem,
30507                                                                                                                        tableColumn.getStarLinkColumnList().get(z));
30508                                                                                                        DataFlowRelationship relation = modelFactory
30509                                                                                                                        .createDataFlowRelation();
30510                                                                                                        relation.setEffectType(EffectType.select);
30511                                                                                                        relation.setTarget(
30512                                                                                                                        new ResultColumnRelationshipElement(resultColumn));
30513                                                                                                        relation.addSource(
30514                                                                                                                        new ResultColumnRelationshipElement(resultColumn1));
30515                                                                                                        tableColumn.removeStarLinkOf(tableColumn.getStarLinkColumnList().get(z));
30516                                                                                                        z--;
30517                                                                                                }
30518                                                                                        } else {
30519                                                                                                resultColumn.bindStarLinkColumn(
30520                                                                                                                ((TResultColumn) tableColumn.getColumnObject()).getFieldAttr());
30521                                                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
30522                                                                                                relation.setEffectType(EffectType.select);
30523                                                                                                relation.setTarget(new ResultColumnRelationshipElement(resultColumn, ((TResultColumn) tableColumn.getColumnObject()).getFieldAttr()));
30524                                                                                                relation.addSource(new ResultColumnRelationshipElement(tableColumn));
30525                                                                                        }
30526                                                                                } else if (((TResultColumn) tableColumn.getColumnObject()).getExpr() != null) {
30527                                                                                        TExpression expr = ((TResultColumn) tableColumn.getColumnObject())
30528                                                                                                        .getExpr();
30529                                                                                        if (expr.getExpressionType() == EExpressionType.simple_constant_t) {
30530                                                                                                TObjectName columnName = Column.nameOfText(expr.toString());
30531                                                                                                resultColumn.bindStarLinkColumn(columnName);
30532                                                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
30533                                                                                                relation.setEffectType(EffectType.select);
30534                                                                                                relation.setTarget(new ResultColumnRelationshipElement(resultColumn, columnName));
30535                                                                                                relation.addSource(new ResultColumnRelationshipElement(tableColumn));
30536                                                                                        }
30537                                                                                }
30538                                                                        }
30539                                                                }
30540                                                        } else if (tableItem instanceof Table) {
30541                                                                for (TableColumn tableColumn : ((Table) tableItem).getColumns()) {
30542                                                                        if (pivotedColumns.contains(tableColumn)) {
30543                                                                                continue;
30544                                                                        }
30545                                                                        resultColumn.bindStarLinkColumn((TObjectName) tableColumn.getColumnObject(), index);
30546                                                                        index++;
30547                                                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
30548                                                                        relation.setEffectType(EffectType.select);
30549                                                                        relation.setTarget(new ResultColumnRelationshipElement(resultColumn, (TObjectName) tableColumn.getColumnObject()));
30550                                                                        relation.addSource(new TableColumnRelationshipElement(tableColumn));
30551                                                                }
30552                                                        }
30553                                                }
30554                                        } else {
30555                                                for (int k = 0; k < tables.size(); k++) {
30556                                                        Object tableItem = tables.get(k);
30557                                                        if (tableItem instanceof ResultSet) {
30558                                                                for (ResultColumn tableColumn : ((ResultSet) tableItem).getColumns()) {
30559                                                                        if (DlineageUtil.columnMatches(tableColumn, columnObject.getFieldAttr())) {
30560                                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
30561                                                                                relation.setEffectType(EffectType.select);
30562                                                                                relation.setTarget(new ResultColumnRelationshipElement(resultColumn));
30563                                                                                relation.addSource(new ResultColumnRelationshipElement(tableColumn));
30564                                                                                break;
30565                                                                        }
30566                                                                }
30567                                                        } else if (tableItem instanceof Table) {
30568                                                                for (TableColumn tableColumn : ((Table) tableItem).getColumns()) {
30569                                                                        if (DlineageUtil.columnMatches(tableColumn, columnObject.getFieldAttr())) {
30570                                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
30571                                                                                relation.setEffectType(EffectType.select);
30572                                                                                relation.setTarget(new ResultColumnRelationshipElement(resultColumn));
30573                                                                                relation.addSource(new TableColumnRelationshipElement(tableColumn));
30574                                                                                break;
30575                                                                        }
30576                                                                }
30577                                                        }
30578                                                }
30579                                        }
30580                                } else if (columnObject.getExpr() != null) {
30581                                        analyzeResultColumn(column, EffectType.select);
30582                                }
30583                        }
30584                }
30585        }
30586
30587        private void analyzePivotedTable(TSelectSqlStatement stmt, TPivotedTable pivotedTable) {
30588                List<Object> tables = new ArrayList<Object>();
30589                Set<Object> pivotedColumns = new HashSet<Object>();
30590                TTable fromTable = pivotedTable.getTableSource();
30591                Object table = modelManager.getModel(fromTable);
30592                if(table == null && fromTable.getSubquery()!=null) {
30593                        table = modelFactory.createQueryTable(fromTable);
30594                        TSelectSqlStatement subquery = fromTable.getSubquery();
30595                        analyzeSelectStmt(subquery);
30596                }
30597                List<TPivotClause> pivotClauses = new ArrayList<TPivotClause>();
30598                if (pivotedTable.getPivotClauseList() != null && pivotedTable.getPivotClauseList().size() > 0) {
30599                        for (int i = 0; i < pivotedTable.getPivotClauseList().size(); i++) {
30600                                pivotClauses.add(pivotedTable.getPivotClauseList().getElement(i));
30601                        }
30602                } else {
30603                        TPivotClause pivotClause = pivotedTable.getPivotClause();
30604                        pivotClauses.add(pivotClause);
30605                }
30606
30607                for (int y = 0; y < pivotClauses.size(); y++) {
30608                        TPivotClause pivotClause = pivotClauses.get(y);
30609                        List<TFunctionCall> functionCalls = new ArrayList<TFunctionCall>();
30610                        if (pivotClause.getAggregation_function() != null || pivotClause.getAggregation_function_list() != null) {
30611                                if (pivotClause.getAggregation_function() != null) {
30612                                        functionCalls.add((TFunctionCall) pivotClause.getAggregation_function());
30613                                } else if (pivotClause.getAggregation_function_list() != null) {
30614                                        for (int i = 0; i < pivotClause.getAggregation_function_list().size(); i++) {
30615                                                functionCalls.add((TFunctionCall) pivotClause.getAggregation_function_list().getResultColumn(i)
30616                                                                .getExpr().getFunctionCall());
30617                                        }
30618                                }
30619
30620                                if (functionCalls.isEmpty()) {
30621                                        return;
30622                                }
30623
30624                                if (pivotClause.getPivotColumnList() == null) {
30625                                        return;
30626                                }
30627
30628                                if (pivotClause.getPivotInClause() == null) {
30629                                        return;
30630                                }
30631
30632                                for (int x = 0; x < functionCalls.size(); x++) {
30633                                        TFunctionCall functionCall = functionCalls.get(x);
30634                                        Function function = modelFactory.createFunction(functionCall);
30635                                        ResultColumn column = modelFactory.createFunctionResultColumn(function,
30636                                                        ((TFunctionCall) functionCall).getFunctionName());
30637
30638                                        List<TExpression> expressions = new ArrayList<TExpression>();
30639                                        getFunctionExpressions(expressions, new ArrayList<TExpression>(), functionCall);
30640
30641                                        for (int j = 0; j < expressions.size(); j++) {
30642                                                columnsInExpr visitor = new columnsInExpr();
30643                                                expressions.get(j).inOrderTraverse(visitor);
30644                                                List<TObjectName> objectNames = visitor.getObjectNames();
30645                                                if (objectNames == null) {
30646                                                        continue;
30647                                                }
30648                                                for (TObjectName columnName : objectNames) {
30649                                                        // STRUCT tree: COUNT(customer.name) reads the field node customer.name, and the column
30650                                                        // customer is consumed by the aggregate, so it is no grouping column (Mantis 4829)
30651                                                        String fieldBase = nestedProfile().buildsDeclaredTree()
30652                                                                        && columnName.getFieldPathRead() != null && columnName.getFieldPathBaseToken() != null
30653                                                                        ? columnName.getFieldPathBaseToken().toString() : null;
30654                                                        if (fieldBase != null && table instanceof Table) {
30655                                                                TableColumn node = modelFactory.createTableColumn((Table) table, columnName, false);
30656                                                                if (node != null) {
30657                                                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
30658                                                                        relation.setEffectType(EffectType.select);
30659                                                                        relation.setTarget(new ResultColumnRelationshipElement(column));
30660                                                                        relation.addSource(new TableColumnRelationshipElement(node));
30661                                                                        pivotedColumns.add(node);
30662                                                                        if (node.getDeclaredColumn() != null) {
30663                                                                                pivotedColumns.add(node.getDeclaredColumn());
30664                                                                        }
30665                                                                        for (TableColumn tableColumn : ((Table) table).getColumns()) {
30666                                                                                if (DlineageUtil.columnMatches(tableColumn, fieldBase)) {
30667                                                                                        pivotedColumns.add(tableColumn);
30668                                                                                }
30669                                                                        }
30670                                                                        continue;
30671                                                                }
30672                                                        }
30673                                                        if (table instanceof QueryTable) {
30674                                                                for (int i = 0; i < ((QueryTable) table).getColumns().size(); i++) {
30675                                                                        boolean find = false;
30676                                                                        ResultColumn tableColumn = ((QueryTable) table).getColumns().get(i);
30677                                                                        if (fieldBase != null && !tableColumn.hasStarLinkColumn()
30678                                                                                        && DlineageUtil.columnMatches(tableColumn, fieldBase)) {
30679                                                                                // a field read of a CTE or derived column reads that column (Mantis 4829)
30680                                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
30681                                                                                relation.setEffectType(EffectType.select);
30682                                                                                relation.setTarget(new ResultColumnRelationshipElement(column));
30683                                                                                relation.addSource(new ResultColumnRelationshipElement(tableColumn));
30684                                                                                pivotedColumns.add(tableColumn);
30685                                                                                break;
30686                                                                        }
30687                                                                        if (tableColumn.hasStarLinkColumn()) {
30688                                                                                for (int k = 0; k < tableColumn.getStarLinkColumnList().size(); k++) {
30689                                                                                        TObjectName objectName = tableColumn.getStarLinkColumnList().get(k);
30690                                                                                        if (DlineageUtil.sameColumnName(columnName, objectName)) {
30691                                                                                                ResultColumn resultColumn = modelFactory
30692                                                                                                                .createResultColumn((QueryTable) table, objectName);
30693                                                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
30694                                                                                                relation.setEffectType(EffectType.select);
30695                                                                                                relation.setTarget(new ResultColumnRelationshipElement(column));
30696                                                                                                relation.addSource(new ResultColumnRelationshipElement(resultColumn));
30697                                                                                                pivotedColumns.add(resultColumn);
30698                                                                                                tableColumn.removeStarLinkOf(objectName);
30699                                                                                                find = true;
30700                                                                                                break;
30701                                                                                        }
30702                                                                                }
30703                                                                        } else if (DlineageUtil.columnMatches(tableColumn, columnName)) {
30704                                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
30705                                                                                relation.setEffectType(EffectType.select);
30706                                                                                relation.setTarget(new ResultColumnRelationshipElement(column));
30707                                                                                relation.addSource(new ResultColumnRelationshipElement(tableColumn));
30708                                                                                pivotedColumns.add(tableColumn);
30709                                                                                find = true;
30710                                                                                break;
30711                                                                        } 
30712                                                                        
30713                                                                        if (!find && tableColumn.isStarColumn()) {
30714                                                                                QueryTable queryTable = (QueryTable)table;
30715                                                                                ResultColumn resultColumn = modelFactory.createResultColumn(queryTable, columnName);
30716                                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
30717                                                                                relation.setEffectType(EffectType.select);
30718                                                                                relation.setTarget(new ResultColumnRelationshipElement(column));
30719                                                                                relation.addSource(new ResultColumnRelationshipElement(resultColumn));
30720                                                                                tableColumn.bindStarLinkColumn(columnName);
30721                                                                        }
30722                                                                }
30723                                                        } else if (table instanceof Table) {
30724                                                                for (TableColumn tableColumn : ((Table) table).getColumns()) {
30725                                                                        if (DlineageUtil.columnMatches(tableColumn, columnName)) {
30726                                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
30727                                                                                relation.setEffectType(EffectType.select);
30728                                                                                relation.setTarget(new ResultColumnRelationshipElement(column));
30729                                                                                relation.addSource(new TableColumnRelationshipElement(tableColumn));
30730                                                                                pivotedColumns.add(tableColumn);
30731                                                                                break;
30732                                                                        }
30733                                                                }
30734                                                        }
30735                                                }
30736                                        }
30737
30738                                        PivotedTable pivotTable = modelFactory.createPivotdTable(pivotClause);
30739                                        pivotTable.setUnpivoted(false);
30740
30741                                        if (pivotClause.getPivotInClause().getItems() != null) {
30742                                                for (int j = 0; j < pivotClause.getPivotInClause().getItems().size(); j++) {
30743                                                        ResultColumn resultColumn = null;
30744                                                        if (pivotClause.getAggregation_function_list() != null
30745                                                                        && pivotClause.getAggregation_function_list().size() > 1) {
30746                                                                TResultColumn functionColumn = pivotClause.getAggregation_function_list()
30747                                                                                .getResultColumn(x);
30748                                                                TObjectName tableColumn = new TObjectName();
30749                                                                if (option.getVendor() == EDbVendor.dbvbigquery) {
30750                                                                        tableColumn.setString(getResultColumnString(functionColumn) + "_"
30751                                                                                        + SQLUtil.trimColumnStringQuote(getResultColumnString(
30752                                                                                                        pivotClause.getPivotInClause().getItems().getResultColumn(j))));
30753                                                                }
30754                                                                else {
30755                                                                        tableColumn.setString(SQLUtil
30756                                                                                        .trimColumnStringQuote(getResultColumnString(
30757                                                                                                        pivotClause.getPivotInClause().getItems().getResultColumn(j)))
30758                                                                                        + "_" + getResultColumnString(functionColumn));
30759                                                                }
30760                                                                resultColumn = modelFactory.createResultColumn(pivotTable, tableColumn);
30761                                                        } else {
30762                                                                resultColumn = modelFactory.createSelectSetResultColumn(pivotTable,
30763                                                                                pivotClause.getPivotInClause().getItems().getResultColumn(j), j);
30764                                                        }
30765                                                        {
30766                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
30767                                                                relation.setEffectType(EffectType.select);
30768                                                                relation.setTarget(new ResultColumnRelationshipElement(resultColumn));
30769                                                                relation.addSource(new ResultColumnRelationshipElement(column));
30770                                                        }
30771                                                        {
30772                                                                for (TObjectName columnName : pivotClause.getPivotColumnList()) {
30773                                                                        if (table instanceof QueryTable) {
30774                                                                                for (int i = 0; i < ((QueryTable) table).getColumns().size(); i++) {
30775                                                                                        ResultColumn tableColumn = ((QueryTable) table).getColumns().get(i);
30776                                                                                        if (tableColumn.hasStarLinkColumn()) {
30777                                                                                                for (int k = 0; k < tableColumn.getStarLinkColumnList().size(); k++) {
30778                                                                                                        TObjectName objectName = tableColumn.getStarLinkColumnList().get(k);
30779                                                                                                        if (DlineageUtil.sameColumnName(columnName, objectName)) {
30780                                                                                                                ResultColumn resultColumn1 = modelFactory
30781                                                                                                                                .createResultColumn((QueryTable) table, objectName);
30782                                                                                                                DataFlowRelationship relation = modelFactory
30783                                                                                                                                .createDataFlowRelation();
30784                                                                                                                relation.setEffectType(EffectType.select);
30785                                                                                                                relation.setTarget(new ResultColumnRelationshipElement(column));
30786                                                                                                                relation.addSource(
30787                                                                                                                                new ResultColumnRelationshipElement(resultColumn1));
30788                                                                                                                pivotedColumns.add(resultColumn1);
30789                                                                                                                tableColumn.removeStarLinkOf(objectName);
30790                                                                                                                break;
30791                                                                                                        }
30792                                                                                                }
30793                                                                                        } else if (DlineageUtil.columnMatches(tableColumn, columnName)) {
30794                                                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
30795                                                                                                relation.setEffectType(EffectType.select);
30796                                                                                                relation.setTarget(new ResultColumnRelationshipElement(resultColumn));
30797                                                                                                relation.addSource(new ResultColumnRelationshipElement(tableColumn));
30798                                                                                                pivotedColumns.add(tableColumn);
30799                                                                                                break;
30800                                                                                        }
30801                                                                                }
30802                                                                        } else if (table instanceof Table) {
30803                                                                                for (TableColumn tableColumn : ((Table) table).getColumns()) {
30804                                                                                        if (DlineageUtil.columnMatches(tableColumn, columnName)) {
30805                                                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
30806                                                                                                relation.setEffectType(EffectType.select);
30807                                                                                                relation.setTarget(new ResultColumnRelationshipElement(resultColumn));
30808                                                                                                relation.addSource(new TableColumnRelationshipElement(tableColumn));
30809                                                                                                pivotedColumns.add(tableColumn);
30810                                                                                                break;
30811                                                                                        }
30812                                                                                }
30813                                                                        }
30814                                                                }
30815                                                        }
30816                                                }
30817                                        } else if (pivotClause.getPivotInClause().getSubQuery() != null) {
30818                                                TSelectSqlStatement subquery = pivotClause.getPivotInClause().getSubQuery();
30819                                                analyzeSelectStmt(subquery);
30820                                                ResultSet selectSetResultSetModel = (ResultSet) modelManager.getModel(subquery);
30821                                                for (int j = 0; j < subquery.getResultColumnList().size(); j++) {
30822                                                        ResultColumn resultColumn = modelFactory.createSelectSetResultColumn(pivotTable,
30823                                                                        subquery.getResultColumnList().getResultColumn(j), j);
30824                                                        {
30825                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
30826                                                                relation.setEffectType(EffectType.select);
30827                                                                relation.setTarget(new ResultColumnRelationshipElement(resultColumn));
30828                                                                relation.addSource(new ResultColumnRelationshipElement(column));
30829                                                                relation.addSource(new ResultColumnRelationshipElement(
30830                                                                                selectSetResultSetModel.getColumns().get(j)));
30831                                                        }
30832                                                        {
30833                                                                for (TObjectName columnName : pivotClause.getPivotColumnList()) {
30834                                                                        if (table instanceof QueryTable) {
30835                                                                                for (ResultColumn tableColumn : ((QueryTable) table).getColumns()) {
30836                                                                                        if (DlineageUtil.columnMatches(tableColumn, columnName)) {
30837                                                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
30838                                                                                                relation.setEffectType(EffectType.select);
30839                                                                                                relation.setTarget(new ResultColumnRelationshipElement(resultColumn));
30840                                                                                                relation.addSource(new ResultColumnRelationshipElement(tableColumn));
30841                                                                                                pivotedColumns.add(tableColumn);
30842                                                                                                break;
30843                                                                                        }
30844                                                                                }
30845                                                                        } else if (table instanceof Table) {
30846                                                                                for (TableColumn tableColumn : ((Table) table).getColumns()) {
30847                                                                                        if (DlineageUtil.columnMatches(tableColumn, columnName)) {
30848                                                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
30849                                                                                                relation.setEffectType(EffectType.select);
30850                                                                                                relation.setTarget(new ResultColumnRelationshipElement(resultColumn));
30851                                                                                                relation.addSource(new TableColumnRelationshipElement(tableColumn));
30852                                                                                                pivotedColumns.add(tableColumn);
30853                                                                                                break;
30854                                                                                        }
30855                                                                                }
30856                                                                        }
30857                                                                }
30858                                                        }
30859                                                }
30860                                        }
30861                                        tables.add(pivotTable);
30862                                        tables.add(table);
30863                                }
30864                        }
30865                }
30866
30867                TPivotClause pivotClause = pivotClauses.get(0);
30868                boolean hasAlias = pivotClause.getAliasClause() != null && pivotClause.getAliasClause().getColumns() != null
30869                                && pivotClause.getAliasClause().getColumns().size() > 0;
30870                if (hasAlias) {
30871                        Alias alias = modelFactory.createAlias(pivotClause.getAliasClause());
30872                        List<TObjectName> aliasColumns = new ArrayList<TObjectName>();
30873                        int index = 0;
30874                        for (int k = 0; k < tables.size(); k++) {
30875                                Object tableItem = tables.get(k);
30876                                if (tableItem instanceof ResultSet) {
30877                                        for (ResultColumn tableColumn : ((ResultSet) tableItem).getColumns()) {
30878                                                if (pivotedColumns.contains(tableColumn)) {
30879                                                        continue;
30880                                                }
30881                                                if (tableColumn.getColumnObject() instanceof TObjectName) {
30882                                                        aliasColumns.add((TObjectName) tableColumn.getColumnObject());
30883                                                } else if (tableColumn.getColumnObject() instanceof TResultColumn) {
30884                                                        if (((TResultColumn) tableColumn.getColumnObject()).getFieldAttr() != null) {
30885                                                                aliasColumns.add(((TResultColumn) tableColumn.getColumnObject()).getFieldAttr());
30886                                                        } else {
30887                                                                TExpression expr = ((TResultColumn) tableColumn.getColumnObject()).getExpr();
30888                                                                if (expr.getExpressionType() == EExpressionType.simple_constant_t) {
30889                                                                        TObjectName columnName = new TObjectName();
30890                                                                        columnName.setString(expr.toString());
30891                                                                        aliasColumns.add(columnName);
30892                                                                }
30893                                                        }
30894                                                }
30895                                        }
30896                                } else if (tableItem instanceof Table) {
30897                                        for (TableColumn tableColumn : ((Table) tableItem).getColumns()) {
30898                                                if (pivotedColumns.contains(tableColumn)) {
30899                                                        continue;
30900                                                }
30901                                                aliasColumns.add(index, (TObjectName) tableColumn.getColumnObject());
30902                                                index++;
30903                                        }
30904                                }
30905                        }
30906
30907                        IndexedLinkedHashMap<String, ResultColumn> aliasColumnMap = new IndexedLinkedHashMap<String, ResultColumn>();
30908                        int diffCount = pivotClause.getAliasClause().getColumns().size() -  aliasColumns.size();
30909                        for (int k = 0; k < pivotClause.getAliasClause().getColumns().size(); k++) {
30910                                if (pivotClause.getAliasClause().getColumns().size() > aliasColumns.size()) {
30911                                        if (k < diffCount) {
30912                                                continue;
30913                                        }
30914                                        ResultColumn resultColumn = modelFactory.createResultColumn(alias,
30915                                                        pivotClause.getAliasClause().getColumns().getObjectName(k));
30916                                        if ((k - diffCount) < aliasColumns.size()) {
30917                                                aliasColumnMap.put(aliasColumns.get(k - diffCount).toString(), resultColumn);
30918                                        }
30919                                } else {
30920                                        ResultColumn resultColumn = modelFactory.createResultColumn(alias,
30921                                                        pivotClause.getAliasClause().getColumns().getObjectName(k));
30922                                        if (k < aliasColumns.size()) {
30923                                                aliasColumnMap.put(aliasColumns.get(k).toString(), resultColumn);
30924                                        }
30925                                }
30926                        }
30927
30928                        for (int k = 0; k < tables.size(); k++) {
30929                                Object tableItem = tables.get(k);
30930                                if (tableItem instanceof ResultSet) {
30931                                        int resultColumnSize = ((ResultSet) tableItem).getColumns().size();
30932                                        for (int x = 0; x < resultColumnSize; x++) {
30933                                                ResultColumn tableColumn = ((ResultSet) tableItem).getColumns().get(x);
30934                                                if (pivotedColumns.contains(tableColumn)) {
30935                                                        continue;
30936                                                }
30937                                                if (tableColumn.getColumnObject() instanceof TObjectName) {
30938                                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
30939                                                        relation.setEffectType(EffectType.select);
30940                                                        relation.setTarget(new ResultColumnRelationshipElement(
30941                                                                        aliasColumnMap.get(tableColumn.getColumnObject().toString())));
30942                                                        relation.addSource(new ResultColumnRelationshipElement(tableColumn));
30943                                                } else if (tableColumn.getColumnObject() instanceof TResultColumn) {
30944                                                        if (((TResultColumn) tableColumn.getColumnObject()).getFieldAttr() != null) {
30945                                                                ResultColumn targetColumn = aliasColumnMap.get(
30946                                                                                ((TResultColumn) tableColumn.getColumnObject()).getFieldAttr().toString());
30947                                                                if(targetColumn == null) {
30948                                                                        continue;
30949                                                                }
30950                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
30951                                                                relation.setEffectType(EffectType.select);
30952                                                                relation.setTarget(new ResultColumnRelationshipElement(targetColumn));
30953                                                                relation.addSource(new ResultColumnRelationshipElement(tableColumn));
30954                                                        } else if (((TResultColumn) tableColumn.getColumnObject()).getExpr() != null) {
30955                                                                TExpression expr = ((TResultColumn) tableColumn.getColumnObject()).getExpr();
30956                                                                if (expr.getExpressionType() == EExpressionType.simple_constant_t) {
30957                                                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
30958                                                                        relation.setEffectType(EffectType.select);
30959                                                                        ResultColumn targetColumn = (ResultColumn) aliasColumnMap
30960                                                                                        .getValueAtIndex(aliasColumnMap.size() - resultColumnSize + x);
30961                                                                        relation.setTarget(new ResultColumnRelationshipElement(targetColumn));
30962                                                                        relation.addSource(new ResultColumnRelationshipElement(tableColumn));
30963                                                                }
30964                                                        }
30965                                                }
30966                                        }
30967                                } else if (tableItem instanceof Table) {
30968                                        for (TableColumn tableColumn : ((Table) tableItem).getColumns()) {
30969                                                if (pivotedColumns.contains(tableColumn)) {
30970                                                        continue;
30971                                                }
30972                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
30973                                                relation.setEffectType(EffectType.select);
30974                                                relation.setTarget(new ResultColumnRelationshipElement(
30975                                                                aliasColumnMap.get(tableColumn.getColumnObject().toString())));
30976                                                relation.addSource(new TableColumnRelationshipElement(tableColumn));
30977                                        }
30978                                }
30979                        }
30980
30981                        ResultSet resultSet = modelFactory.createResultSet(stmt,
30982                                        isTopResultSet(stmt) && isShowTopSelectResultSet());
30983                        TResultColumnList columnList = stmt.getResultColumnList();
30984                        for (int i = 0; i < columnList.size(); i++) {
30985                                TResultColumn column = columnList.getResultColumn(i);
30986                                ResultColumn resultColumn = modelFactory.createAndBindingSelectSetResultColumn(resultSet, column, i);
30987                                if (resultColumn.getColumnObject() instanceof TResultColumn) {
30988                                        TResultColumn columnObject = (TResultColumn) resultColumn.getColumnObject();
30989                                        if (columnObject.getFieldAttr() != null) {
30990                                                if (DlineageUtil.isStarName((columnObject.getFieldAttr()))) {
30991                                                        resultColumn.setShowStar(false);
30992                                                        for (ResultColumn tableColumn : ((ResultSet) alias).getColumns()) {
30993                                                                resultColumn.bindStarLinkColumn((TObjectName) tableColumn.getColumnObject());
30994                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
30995                                                                relation.setEffectType(EffectType.select);
30996                                                                relation.setTarget(new ResultColumnRelationshipElement(resultColumn, (TObjectName) tableColumn.getColumnObject()));
30997                                                                relation.addSource(new ResultColumnRelationshipElement(tableColumn));
30998                                                        }
30999                                                } else {
31000                                                        for (ResultColumn tableColumn : ((ResultSet) alias).getColumns()) {
31001                                                                if (DlineageUtil.columnMatches(tableColumn, columnObject.getFieldAttr())) {
31002                                                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
31003                                                                        relation.setEffectType(EffectType.select);
31004                                                                        relation.setTarget(new ResultColumnRelationshipElement(resultColumn));
31005                                                                        relation.addSource(new ResultColumnRelationshipElement(tableColumn));
31006                                                                        break;
31007                                                                }
31008                                                        }
31009                                                }
31010                                        } else if (columnObject.getExpr() != null && columnObject.getExpr()
31011                                                        .getExpressionType() == EExpressionType.sqlserver_proprietary_column_alias_t) {
31012                                                for (ResultColumn tableColumn : ((ResultSet) alias).getColumns()) {
31013                                                        if (DlineageUtil.columnMatches(tableColumn, columnObject.getExpr().getRightOperand().getObjectOperand())) {
31014                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
31015                                                                relation.setEffectType(EffectType.select);
31016                                                                relation.setTarget(new ResultColumnRelationshipElement(resultColumn));
31017                                                                relation.addSource(new ResultColumnRelationshipElement(tableColumn));
31018                                                                break;
31019                                                        }
31020                                                }
31021                                        } else if (columnObject.getExpr() != null && columnObject.getExpr()
31022                                                        .getExpressionType() == EExpressionType.function_t) {
31023                                                Function function = (Function) createFunction(columnObject.getExpr().getFunctionCall());
31024                                                for (ResultColumn arg : function.getColumns()) {
31025                                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
31026                                                        relation.setEffectType(EffectType.select);
31027                                                        relation.setTarget(new ResultColumnRelationshipElement(resultColumn));
31028                                                        relation.addSource(new ResultColumnRelationshipElement(arg));
31029                                                }
31030                                        }
31031                                }
31032                        }
31033                } else {
31034                        ResultSet resultSet = modelFactory.createResultSet(stmt, isTopResultSet(stmt));
31035                        TResultColumnList columnList = stmt.getResultColumnList();
31036                        for (int i = 0; i < columnList.size(); i++) {
31037                                TResultColumn column = columnList.getResultColumn(i);
31038                                ResultColumn resultColumn = modelFactory.createAndBindingSelectSetResultColumn(resultSet, column, i);
31039                                if (resultColumn.getColumnObject() instanceof TResultColumn) {
31040                                        boolean fromFunction = false;
31041                                        TResultColumn columnObject = (TResultColumn) resultColumn.getColumnObject();
31042                                        TObjectName resultColumnFieldAttr = columnObject.getFieldAttr();
31043                                        List<TObjectName> resultColumnNames = new ArrayList<TObjectName>();
31044                                        if (resultColumnFieldAttr != null) {
31045                                                resultColumnNames.add(resultColumnFieldAttr);
31046                                        } else if (columnObject.getExpr() != null
31047                                                        && column.getExpr().getExpressionType() == EExpressionType.function_t) {
31048                                                extractFunctionObjectNames(column.getExpr().getFunctionCall(), resultColumnNames);
31049                                                fromFunction = true;
31050                                        }
31051                                        
31052                                        if (!resultColumnNames.isEmpty()) {
31053                                                for (TObjectName resultColumnName : resultColumnNames) {
31054                                                        if (DlineageUtil.isStarName((resultColumnName))) {
31055                                                                resultColumn.setShowStar(false);
31056                                                                int index = 0;
31057                                                                for (int k = 0; k < tables.size(); k++) {
31058                                                                        Object tableItem = tables.get(k);
31059                                                                        if (tableItem instanceof ResultSet && !(tableItem instanceof QueryTable)) {
31060                                                                                for (int x = 0; x < ((ResultSet) tableItem).getColumns().size(); x++) {
31061                                                                                        ResultColumn tableColumn = ((ResultSet) tableItem).getColumns().get(x);
31062                                                                                        if (pivotedColumns.contains(tableColumn)) {
31063                                                                                                continue;
31064                                                                                        }
31065                                                                                        if (tableColumn.getColumnObject() instanceof TObjectName) {
31066                                                                                                resultColumn.bindStarLinkColumn(
31067                                                                                                                (TObjectName) tableColumn.getColumnObject());
31068                                                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
31069                                                                                                relation.setEffectType(EffectType.select);
31070                                                                                                relation.setTarget(new ResultColumnRelationshipElement(resultColumn, (TObjectName) tableColumn.getColumnObject()));
31071                                                                                                relation.addSource(new ResultColumnRelationshipElement(tableColumn));
31072                                                                                        } else if (tableColumn.getColumnObject() instanceof TResultColumn) {
31073                                                                                                if (((TResultColumn) tableColumn.getColumnObject())
31074                                                                                                                .getFieldAttr() != null) {
31075                                                                                                        if (tableColumn.hasStarLinkColumn()) {
31076                                                                                                                for (int z = 0; z < tableColumn.getStarLinkColumnList()
31077                                                                                                                                .size(); z++) {
31078                                                                                                                        ResultColumn resultColumn1 = modelFactory
31079                                                                                                                                        .createResultColumn((ResultSet) tableItem,
31080                                                                                                                                                        tableColumn.getStarLinkColumnList().get(z));
31081                                                                                                                        DataFlowRelationship relation = modelFactory
31082                                                                                                                                        .createDataFlowRelation();
31083                                                                                                                        relation.setEffectType(EffectType.select);
31084                                                                                                                        relation.setTarget(
31085                                                                                                                                        new ResultColumnRelationshipElement(resultColumn));
31086                                                                                                                        relation.addSource(
31087                                                                                                                                        new ResultColumnRelationshipElement(resultColumn1));
31088                                                                                                                        tableColumn.removeStarLinkOf(tableColumn.getStarLinkColumnList().get(z));
31089                                                                                                                        z--;
31090                                                                                                                }
31091                                                                                                        } else {
31092                                                                                                                resultColumn.bindStarLinkColumn(
31093                                                                                                                                ((TResultColumn) tableColumn.getColumnObject())
31094                                                                                                                                                .getFieldAttr());
31095                                                                                                                DataFlowRelationship relation = modelFactory
31096                                                                                                                                .createDataFlowRelation();
31097                                                                                                                relation.setEffectType(EffectType.select);
31098                                                                                                                relation.setTarget(
31099                                                                                                                                new ResultColumnRelationshipElement(resultColumn, ((TResultColumn) tableColumn.getColumnObject())
31100                                                                                                                                                .getFieldAttr()));
31101                                                                                                                relation.addSource(
31102                                                                                                                                new ResultColumnRelationshipElement(tableColumn));
31103                                                                                                        }
31104                                                                                                } else if (((TResultColumn) tableColumn.getColumnObject())
31105                                                                                                                .getExpr() != null) {
31106                                                                                                        TExpression expr = ((TResultColumn) tableColumn.getColumnObject())
31107                                                                                                                        .getExpr();
31108                                                                                                        if (expr.getExpressionType() == EExpressionType.simple_constant_t) {
31109                                                                                                                TObjectName columnName = Column.nameOfText(expr.toString());
31110                                                                                                                resultColumn.bindStarLinkColumn(columnName);
31111                                                                                                                DataFlowRelationship relation = modelFactory
31112                                                                                                                                .createDataFlowRelation();
31113                                                                                                                relation.setEffectType(EffectType.select);
31114                                                                                                                relation.setTarget(
31115                                                                                                                                new ResultColumnRelationshipElement(resultColumn, columnName));
31116                                                                                                                relation.addSource(
31117                                                                                                                                new ResultColumnRelationshipElement(tableColumn));
31118                                                                                                        }
31119                                                                                                }
31120                                                                                        }
31121                                                                                }
31122                                                                        } else if (tableItem instanceof Table) {
31123                                                                                for (TableColumn tableColumn : ((Table) tableItem).getColumns()) {
31124                                                                                        if (pivotedColumns.contains(tableColumn)) {
31125                                                                                                continue;
31126                                                                                        }
31127                                                                                        resultColumn.bindStarLinkColumn((TObjectName) tableColumn.getColumnObject(),
31128                                                                                                        index);
31129                                                                                        index++;
31130                                                                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
31131                                                                                        relation.setEffectType(EffectType.select);
31132                                                                                        relation.setTarget(new ResultColumnRelationshipElement(resultColumn, (TObjectName) tableColumn.getColumnObject()));
31133                                                                                        relation.addSource(new TableColumnRelationshipElement(tableColumn));
31134                                                                                }
31135                                                                        } else if (tableItem instanceof QueryTable) {
31136                                                                                for (ResultColumn tableColumn : ((QueryTable) tableItem).getColumns()) {
31137                                                                                        if (pivotedColumns.contains(tableColumn)) {
31138                                                                                                continue;
31139                                                                                        }
31140                                                                                        TObjectName column1 = copyColumnName(tableColumn, tableColumn.getName());
31141                                                                                        resultColumn.bindStarLinkColumn(column1, index);
31142                                                                                        index++;
31143                                                                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
31144                                                                                        relation.setEffectType(EffectType.select);
31145                                                                                        relation.setTarget(new ResultColumnRelationshipElement(resultColumn, column1));
31146                                                                                        relation.addSource(new ResultColumnRelationshipElement(tableColumn), false);
31147                                                                                }
31148                                                                        }
31149                                                                }
31150                                                        } else {
31151                                                                ResultColumn pivotedTableColumn = getPivotedTableColumn(pivotedTable, resultColumnName);
31152                                                                        if (pivotedTableColumn != null) {
31153                                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
31154                                                                                relation.setEffectType(EffectType.select);
31155                                                                                relation.setTarget(new ResultColumnRelationshipElement(resultColumn));
31156                                                                                relation.addSource(new ResultColumnRelationshipElement(pivotedTableColumn));
31157                                                                        } else {
31158                                                                                for (int k = 0; k < tables.size(); k++) {
31159                                                                                        Object tableItem = tables.get(k);
31160                                                                                        if (tableItem instanceof ResultSet) {
31161                                                                                                for (ResultColumn tableColumn : ((ResultSet) tableItem).getColumns()) {
31162                                                                                                        if (DlineageUtil.columnMatches(tableColumn, resultColumnName)) {
31163                                                                                                                if (fromFunction) {
31164                                                                                                                        Function function = (Function)createPivotedFunction(column.getExpr().getFunctionCall(), tableColumn);
31165                                                                                                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
31166                                                                                                                        relation.setEffectType(EffectType.select);
31167                                                                                                                        relation.setTarget(new ResultColumnRelationshipElement(resultColumn));
31168                                                                                                                        if (function.getColumns() != null && !function.getColumns().isEmpty()) {
31169                                                                                                                                for (ResultColumn functionColumn : function.getColumns()) {
31170                                                                                                                                        relation.addSource(new ResultColumnRelationshipElement(functionColumn));
31171                                                                                                                                }
31172                                                                                                                        }
31173                                                                                                                } else {
31174                                                                                                                        DataFlowRelationship relation = modelFactory
31175                                                                                                                                        .createDataFlowRelation();
31176                                                                                                                        relation.setEffectType(EffectType.select);
31177                                                                                                                        relation.setTarget(
31178                                                                                                                                        new ResultColumnRelationshipElement(resultColumn));
31179                                                                                                                        relation.addSource(
31180                                                                                                                                        new ResultColumnRelationshipElement(tableColumn));
31181                                                                                                                }
31182                                                                                                                break;
31183                                                                                                        }
31184                                                                                                }
31185                                                                                        } else if (tableItem instanceof Table) {
31186                                                                                                for (TableColumn tableColumn : ((Table) tableItem).getColumns()) {
31187                                                                                                        if (DlineageUtil.columnMatches(tableColumn, resultColumnName)) {
31188                                                                                                                DataFlowRelationship relation = modelFactory
31189                                                                                                                                .createDataFlowRelation();
31190                                                                                                                relation.setEffectType(EffectType.select);
31191                                                                                                                relation.setTarget(
31192                                                                                                                                new ResultColumnRelationshipElement(resultColumn));
31193                                                                                                                relation.addSource(
31194                                                                                                                                new TableColumnRelationshipElement(tableColumn));
31195                                                                                                                break;
31196                                                                                                        }
31197                                                                                                }
31198                                                                                        }
31199                                                                                }
31200                                                                        }
31201                                                                }
31202                                                }
31203                                        } else if (columnObject.getExpr() != null && columnObject.getExpr()
31204                                                        .getExpressionType() == EExpressionType.sqlserver_proprietary_column_alias_t) {
31205                                                for (int k = 0; k < tables.size(); k++) {
31206                                                        Object tableItem = tables.get(k);
31207                                                        if (tableItem instanceof ResultSet) {
31208                                                                for (ResultColumn tableColumn : ((ResultSet) tableItem).getColumns()) {
31209                                                                        if (columnObject.getExpr().getRightOperand().getObjectOperand() != null
31210                                                                                        && DlineageUtil.columnMatches(tableColumn, columnObject.getExpr().getRightOperand().getObjectOperand())) {
31211                                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
31212                                                                                relation.setEffectType(EffectType.select);
31213                                                                                relation.setTarget(new ResultColumnRelationshipElement(resultColumn));
31214                                                                                relation.addSource(new ResultColumnRelationshipElement(tableColumn));
31215                                                                                break;
31216                                                                        } else if (columnObject.getExpr().getRightOperand()
31217                                                                                        .getExpressionType() == EExpressionType.function_t) {
31218                                                                                List<TExpression> expressions = new ArrayList<TExpression>();
31219                                                                                getFunctionExpressions(expressions, new ArrayList<TExpression>(),
31220                                                                                                columnObject.getExpr().getRightOperand().getFunctionCall());
31221                                                                                for (int j = 0; j < expressions.size(); j++) {
31222                                                                                        columnsInExpr visitor = new columnsInExpr();
31223                                                                                        expressions.get(j).inOrderTraverse(visitor);
31224                                                                                        List<TObjectName> objectNames = visitor.getObjectNames();
31225                                                                                        if (objectNames == null) {
31226                                                                                                continue;
31227                                                                                        }
31228                                                                                        for (TObjectName columnName : objectNames) {
31229                                                                                                if (DlineageUtil.columnMatches(tableColumn, columnName)) {
31230                                                                                                        DataFlowRelationship relation = modelFactory
31231                                                                                                                        .createDataFlowRelation();
31232                                                                                                        relation.setEffectType(EffectType.select);
31233                                                                                                        relation.setTarget(
31234                                                                                                                        new ResultColumnRelationshipElement(resultColumn));
31235                                                                                                        relation.addSource(
31236                                                                                                                        new ResultColumnRelationshipElement(tableColumn));
31237                                                                                                        break;
31238                                                                                                }
31239                                                                                        }
31240                                                                                }
31241                                                                        }
31242                                                                }
31243                                                        } else if (tableItem instanceof Table) {
31244                                                                for (TableColumn tableColumn : ((Table) tableItem).getColumns()) {
31245                                                                        if (DlineageUtil.columnMatches(tableColumn, columnObject.getExpr().getRightOperand().getObjectOperand())) {
31246                                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
31247                                                                                relation.setEffectType(EffectType.select);
31248                                                                                relation.setTarget(new ResultColumnRelationshipElement(resultColumn));
31249                                                                                relation.addSource(new TableColumnRelationshipElement(tableColumn));
31250                                                                                break;
31251                                                                        }
31252                                                                }
31253                                                        }
31254                                                }
31255                                        }
31256                                }
31257                        }
31258                }
31259
31260                analyzeSelectIntoClause(stmt);
31261        }
31262
31263        private Function createPivotedFunction(TFunctionCall functionCall, ResultColumn sourceColumn) {
31264                Function function = modelFactory.createFunction((TFunctionCall) functionCall);
31265                ResultColumn column = modelFactory.createFunctionResultColumn(function,
31266                                ((TFunctionCall) functionCall).getFunctionName());
31267                if ("COUNT".equalsIgnoreCase(builtInFunctionName((TFunctionCall) functionCall))) { // non-identifier-compare: built-in function name
31268                        // @see https://e.gitee.com/gudusoft/issues/list?issue=I40NUP
31269                        // COUNT特殊处理,不和参数关联
31270                        if (option.isShowCountTableColumn()) {
31271                                analyzePivotedFunctionArgumentsDataFlowRelation(column, functionCall, sourceColumn);
31272                        }
31273                } else {
31274                        analyzePivotedFunctionArgumentsDataFlowRelation(column, functionCall, sourceColumn);
31275                        Set<Object> functionTableModelObjs = modelManager.getFunctionResults(functionAliasKey(function));
31276                        if (functionTableModelObjs!=null && functionTableModelObjs.iterator().next() instanceof ResultSet) {
31277                                ResultSet functionTableModel = (ResultSet) functionTableModelObjs.iterator().next();
31278                                if (functionTableModel.getColumns() != null) {
31279                                        for (int j = 0; j < functionTableModel.getColumns().size(); j++) {
31280                                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
31281                                                        relation.setEffectType(EffectType.select);
31282                                                        relation.setTarget(new ResultColumnRelationshipElement(column));
31283                                                        relation.addSource(new ResultColumnRelationshipElement(
31284                                                                        functionTableModel.getColumns().get(j)));
31285                                        }
31286                                }
31287                        }
31288                }
31289                return function;
31290        }
31291
31292        private void analyzePivotedFunctionArgumentsDataFlowRelation(ResultColumn column, TFunctionCall functionCall,
31293                        ResultColumn sourceColumn) {
31294                List<TExpression> directExpressions = new ArrayList<TExpression>();
31295                List<TExpression> indirectExpressions = new ArrayList<TExpression>();
31296
31297                getFunctionExpressions(directExpressions, indirectExpressions, functionCall);
31298
31299                for (int j = 0; j < directExpressions.size(); j++) {
31300                        columnsInExpr visitor = new columnsInExpr();
31301                        directExpressions.get(j).inOrderTraverse(visitor);
31302
31303                        List<TObjectName> objectNames = visitor.getObjectNames();
31304                        List<TParseTreeNode> constants = visitor.getConstants();
31305
31306                        if (objectNames != null) {
31307                                for (TObjectName name : objectNames) {
31308                                        if (DlineageUtil.compareColumnIdentifier(name.toString(), sourceColumn.getName())) {
31309                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
31310                                                relation.setEffectType(EffectType.select);
31311                                                relation.setTarget(new ResultColumnRelationshipElement(column));
31312                                                relation.addSource(new ResultColumnRelationshipElement(sourceColumn));
31313                                        }
31314                                }
31315                        }
31316                        if (constants != null) {
31317                                for (TParseTreeNode name : constants) {
31318                                        if (name.toString().equals(sourceColumn.getName())) {
31319                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
31320                                                relation.setEffectType(EffectType.select);
31321                                                relation.setTarget(new ResultColumnRelationshipElement(column));
31322                                                relation.addSource(new ResultColumnRelationshipElement(sourceColumn));
31323                                        }
31324                                }
31325                        }
31326                }
31327        }
31328        
31329        private void extractFunctionObjectNames(TFunctionCall functionCall, List<TObjectName> resultColumnNames) {
31330                List<TExpression> directExpressions = new ArrayList<TExpression>();
31331                List<TExpression> indirectExpressions = new ArrayList<TExpression>();
31332
31333                getFunctionExpressions(directExpressions, indirectExpressions, functionCall);
31334
31335                for (int j = 0; j < directExpressions.size(); j++) {
31336                        columnsInExpr visitor = new columnsInExpr();
31337                        directExpressions.get(j).inOrderTraverse(visitor);
31338
31339                        List<TObjectName> objectNames = visitor.getObjectNames();
31340                        List<TParseTreeNode> functions = visitor.getFunctions();
31341                        List<TParseTreeNode> constants = visitor.getConstants(); 
31342
31343                        if (objectNames != null) {
31344                                resultColumnNames.addAll(objectNames);
31345                        }
31346                        
31347                        if (constants != null) {
31348                                for(TParseTreeNode item: constants) {
31349                                        if(item instanceof TConstant && ((TConstant) item).getLiteralType().getText().equals(ELiteralType.string_et.getText())) {
31350                                                TObjectName object = new TObjectName();
31351                                                object.setString(item.toString());
31352                                                resultColumnNames.add(object);
31353                                        }
31354                                }
31355                        }
31356
31357                        if (functions != null && !functions.isEmpty()) {
31358                                for (TParseTreeNode function : functions) {
31359                                        if (function instanceof TFunctionCall) {
31360                                                extractFunctionObjectNames((TFunctionCall) function, resultColumnNames);
31361                                        }
31362                                }
31363                        }
31364                }
31365        }
31366
31367        private String getResultColumnString(TResultColumn resultColumn) {
31368                if (resultColumn.getAliasClause() != null) {
31369                        return resultColumn.getAliasClause().toString();
31370                }
31371                return resultColumn.toString();
31372        }
31373
31374        /**
31375         * STRUCT tree: {@code UNNEST(items) AS i} reads the elements of {@code items}, so the unnest table's column
31376         * {@code i} comes from the array element node {@code items[*]}, and a read of a field of it ({@code i.item_name})
31377         * binds to {@code items[*].item_name} ({@link ModelFactory#createTableColumn}). The array may itself be a field,
31378         * {@code UNNEST(customer.orders)}.
31379         *
31380         * @return whether the array was found in a table of the analysis
31381         */
31382        private boolean linkUnnestElement(Table unnestTable, TTable table, TObjectName arrayColumn) {
31383                if (arrayColumn.getSourceTable() == null) {
31384                        return false;
31385                }
31386                TTable owner = arrayColumn.getSourceTable();
31387                if (owner.getCTE() != null || owner.isCTEName() || owner.getSubquery() != null) {
31388                        // a column of a CTE or a derived table comes from its result set, which the unnest links as before
31389                        return false;
31390                }
31391                Object model = modelManager.getModel(owner);
31392                if (model == null && owner.getTableType() == ETableSource.objectname) {
31393                        // without DDL the table is modelled on first use, which may come after its UNNEST
31394                        model = modelFactory.createTable(owner);
31395                }
31396                if (!(model instanceof Table)) {
31397                        return false;
31398                }
31399                TableColumn element = createArrayElement((Table) model, arrayColumn);
31400                if (element == null) {
31401                        return false;
31402                }
31403                TObjectName elementName = arrayColumn;
31404                if (table.getAliasClause() != null && table.getAliasClause().getAliasName() != null) {
31405                        elementName = table.getAliasClause().getAliasName();
31406                }
31407                TableColumn unnestColumn = modelFactory.createTableColumn(unnestTable, elementName, true);
31408                if (unnestColumn == null) {
31409                        return false;
31410                }
31411                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
31412                relation.setEffectType(EffectType.select);
31413                relation.setTarget(new TableColumnRelationshipElement(unnestColumn));
31414                relation.addSource(new TableColumnRelationshipElement(element));
31415                unnestColumn.setSourceColumn(element);
31416                unnestColumn.setStruct(true);
31417                return true;
31418        }
31419
31420        /**
31421         * STRUCT tree: the node of the elements of the array {@code arrayColumn} of {@code sourceTable}: {@code items[*]}
31422         * below the column {@code items}, or {@code payload.items[*]} below the field node {@code payload.items}. Null
31423         * when the table has no such column.
31424         */
31425        private TableColumn createArrayElement(Table sourceTable, TObjectName arrayColumn) {
31426                TableColumn array = modelFactory.createTableColumn(sourceTable, arrayColumn, false);
31427                if (array == null) {
31428                        return null;
31429                }
31430                if (!array.getDeclaredFields().isEmpty() && array.getDeclaredElement() == null) {
31431                        // the DDL declares a STRUCT, not an array: UNNEST of it reads its fields
31432                        return array;
31433                }
31434                List<gudusoft.gsqlparser.common.structured.StructuredPathSegment> segments =
31435                                new ArrayList<gudusoft.gsqlparser.common.structured.StructuredPathSegment>();
31436                if (array.isFieldPathNode()) {
31437                        segments.addAll(array.getFieldPath().getSegments());
31438                }
31439                segments.add(gudusoft.gsqlparser.common.structured.StructuredPathSegment.arrayElement());
31440                return modelFactory.createFieldPath(array.getDeclaredColumn(), segments, arrayColumn);
31441        }
31442
31443        private void analyzeBigQueryUnnest(TSelectSqlStatement stmt, TTable table) {
31444                Table unnestTable = modelFactory.createTableFromCreateDDL(table, false, getTempTableName(table));
31445                unnestTable.setSubType(SubType.unnest);
31446                TUnnestClause clause = table.getUnnestClause();
31447                TExpression arrayExpr = clause.getArrayExpr();
31448                if (arrayExpr == null){
31449                        if (clause.getColumns() != null) {
31450                                for (TObjectName column : clause.getColumns()) {
31451                                        // STRUCT tree: the unnest column is the array element; reads of its fields bind to the element's
31452                                        // field nodes, so no column per field (BigQuery's derived list is the fields the query reads)
31453                                        if (nestedProfile().buildsDeclaredTree() && linkUnnestElement(unnestTable, table, column)) {
31454                                                unnestTable.setCreateTable(true);
31455                                                continue;
31456                                        }
31457                                        if (clause.getDerivedColumnList() != null) {
31458                                                unnestTable.setCreateTable(true);
31459                                                for (int i = 0; i < clause.getDerivedColumnList().size(); i++) {
31460                                                        TObjectName columnName = new TObjectName();
31461                                                        columnName.setString(column.getColumnNameOnly() + "."
31462                                                                        + clause.getDerivedColumnList().getObjectName(i).getColumnNameOnly());
31463                                                        TableColumn tableColumn = modelFactory.createTableColumn(unnestTable, columnName, true);
31464                                                        List<TObjectName> columns = new ArrayList<TObjectName>();
31465                                                        columns.add(column);
31466                                                        analyzeDataFlowRelation(tableColumn, columns, EffectType.select, null);
31467                                                }
31468                                        }
31469                                        else {
31470                                                unnestTable.setCreateTable(true);
31471                                                boolean find = false;
31472                                                if (column.getSourceTable() != null && modelManager.getModel(column.getSourceTable()) instanceof Table) {
31473                                                        Table sourceTable = (Table)modelManager.getModel(column.getSourceTable());
31474                                                        if(sourceTable!=null) {
31475                                                                for(TableColumn tableColumn: sourceTable.getColumns()) {
31476                                                                        if(tableColumn.isStruct()) {
31477                                                                                List<String> names = SQLUtil.parseNames(tableColumn.getName());
31478                                                                                if (SQLUtil.sameName(option.getVendor(), ESQLDataObjectType.dotColumn, names.get(0), column.getColumnNameOnly())) {
31479                                                                                        TObjectName columnName = new TObjectName();
31480                                                                                        columnName.setString(tableColumn.getName());
31481                                                                                        TableColumn unnestTableColumn = modelFactory.createTableColumn(unnestTable,
31482                                                                                                        columnName, true);
31483                                                                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
31484                                                                                        relation.setEffectType(EffectType.select);
31485                                                                                        relation.setTarget(new TableColumnRelationshipElement(unnestTableColumn));
31486                                                                                        relation.addSource(new TableColumnRelationshipElement(tableColumn));
31487                                                                                        find = true;
31488                                                                                }
31489                                                                        }
31490                                                                }
31491                                                        }
31492                                                }
31493                                                if (!find) {
31494                                                        TObjectName colName = column;
31495                                                        if (table.getAliasClause() != null && table.getAliasClause().getAliasName() != null) {
31496                                                                colName = table.getAliasClause().getAliasName();
31497                                                        }
31498                                                        TableColumn tableColumn = modelFactory.createTableColumn(unnestTable, colName, true);
31499                                                        // UNNEST(s.items) of a derived table analyzed later in the FROM clause: its result set must
31500                                                        // exist for the element to link to it (Mantis 4835)
31501                                                        if (column.getSourceTable() != null && column.getSourceTable().getSubquery() != null
31502                                                                        && modelManager.getModel(column.getSourceTable()) == null) {
31503                                                                analyzeTableSubquery(column.getSourceTable());
31504                                                        }
31505                                                        List<TObjectName> columns = new ArrayList<TObjectName>();
31506                                                        columns.add(column);
31507                                                        analyzeDataFlowRelation(tableColumn, columns, EffectType.select, null);
31508                                                }
31509                                        }
31510                                }
31511                        }
31512                        return;
31513                }
31514                List<TExpression> expressions = new ArrayList<TExpression>();
31515                TExpressionList values = arrayExpr.getExprList();
31516                if (values == null) {
31517                        expressions.add(arrayExpr);
31518                }
31519                else {
31520                        for(TExpression value: values) {
31521                                expressions.add(value);
31522                        }
31523                }
31524
31525                for (TExpression value : expressions) {
31526                        unnestTable.setCreateTable(true);
31527                        if (value.getExpressionType() == EExpressionType.simple_object_name_t
31528                                        && nestedProfile().buildsDeclaredTree() && value.getObjectOperand() != null
31529                                        && linkUnnestElement(unnestTable, table, value.getObjectOperand())) {
31530                                continue;
31531                        }
31532                        if (value.getExpressionType() == EExpressionType.simple_object_name_t) {
31533                                if (table.getAliasClause() != null) {
31534                                        TableColumn tableColumn = modelFactory.createTableColumn(unnestTable,
31535                                                        table.getAliasClause().getAliasName(), true);
31536                                        columnsInExpr visitor = new columnsInExpr();
31537                                        value.inOrderTraverse(visitor);
31538                                        List<TObjectName> columns = visitor.getObjectNames();
31539                                        analyzeDataFlowRelation(tableColumn, columns, EffectType.select, null);
31540                                } else {
31541                                        TResultColumnList resultColumnList = stmt.getResultColumnList();
31542                                        for (int i = 0; i < resultColumnList.size(); i++) {
31543                                                TObjectName firstColumn = new TObjectName();
31544                                                firstColumn.setString(resultColumnList.getResultColumn(0).getColumnNameOnly());
31545                                                TableColumn tableColumn = modelFactory.createTableColumn(unnestTable, firstColumn, true);
31546                                                columnsInExpr visitor = new columnsInExpr();
31547                                                value.inOrderTraverse(visitor);
31548                                                List<TObjectName> columns = visitor.getObjectNames();
31549                                                analyzeDataFlowRelation(tableColumn, columns, EffectType.select, null);
31550                                        }
31551                                }
31552                        } else if (value.getExpressionType() == EExpressionType.function_t) {
31553                                if (table.getAliasClause() != null) {
31554                                        TableColumn tableColumn = modelFactory.createTableColumn(unnestTable,
31555                                                        table.getAliasClause().getAliasName(), true);
31556                                        columnsInExpr visitor = new columnsInExpr();
31557                                        value.inOrderTraverse(visitor);
31558                                        List<TParseTreeNode> functions = visitor.getFunctions();
31559                                        analyzeFunctionDataFlowRelation(tableColumn, functions, EffectType.select, null);
31560                                } else {
31561                                        TResultColumnList resultColumnList = stmt.getResultColumnList();
31562                                        for (int i = 0; i < resultColumnList.size(); i++) {
31563                                                TObjectName firstColumn = new TObjectName();
31564                                                firstColumn.setString(resultColumnList.getResultColumn(0).getColumnNameOnly());
31565                                                TableColumn tableColumn = modelFactory.createTableColumn(unnestTable, firstColumn, true);
31566                                                columnsInExpr visitor = new columnsInExpr();
31567                                                value.inOrderTraverse(visitor);
31568                                                List<TObjectName> columns = visitor.getObjectNames();
31569                                                analyzeDataFlowRelation(tableColumn, columns, EffectType.select, null);
31570                                        }
31571                                }
31572                        } else if (value.getExpressionType() == EExpressionType.simple_constant_t) {
31573                                if (table.getAliasClause() != null) {
31574                                        TableColumn tableColumn = modelFactory.createTableColumn(unnestTable,
31575                                                        table.getAliasClause().getAliasName(), true);
31576                                        columnsInExpr visitor = new columnsInExpr();
31577                                        value.inOrderTraverse(visitor);
31578                                        List<TParseTreeNode> constants = visitor.getConstants();
31579                                        analyzeConstantDataFlowRelation(tableColumn, constants, EffectType.select, null);
31580                                } else {
31581                                        TObjectName firstColumn = new TObjectName();
31582                                        firstColumn.setString("f0_");
31583                                        TableColumn tableColumn = modelFactory.createTableColumn(unnestTable, firstColumn, true);
31584                                        columnsInExpr visitor = new columnsInExpr();
31585                                        value.inOrderTraverse(visitor);
31586                                        List<TParseTreeNode> constants = visitor.getConstants();
31587                                        analyzeConstantDataFlowRelation(tableColumn, constants, EffectType.select, null);
31588                                }
31589                        } else if (value.getExpressionType() == EExpressionType.list_t) {
31590                                if (arrayExpr.getTypeName() != null && arrayExpr.getTypeName().getColumnDefList() != null) {
31591                                        for (int i = 0; i < arrayExpr.getTypeName().getColumnDefList().size(); i++) {
31592                                                TColumnDefinition column = arrayExpr.getTypeName().getColumnDefList().getColumn(i);
31593                                                if (column != null && column.getColumnName() != null) {
31594                                                        TableColumn tableColumn = modelFactory.createTableColumn(unnestTable,
31595                                                                        column.getColumnName(), true);
31596                                                        columnsInExpr visitor = new columnsInExpr();
31597                                                        value.getExprList().getExpression(i).inOrderTraverse(visitor);
31598                                                        List<TParseTreeNode> constants = visitor.getConstants();
31599                                                        analyzeConstantDataFlowRelation(tableColumn, constants, EffectType.select, null);
31600                                                }
31601                                        }
31602                                } else {
31603                                        for (int i = 0; i < value.getExprList().size(); i++) {
31604                                                TObjectName firstColumn = new TObjectName();
31605                                                firstColumn.setString("f" + i + "_");
31606                                                TableColumn tableColumn = modelFactory.createTableColumn(unnestTable, firstColumn, true);
31607                                                columnsInExpr visitor = new columnsInExpr();
31608                                                value.getExprList().getExpression(i).inOrderTraverse(visitor);
31609                                                List<TParseTreeNode> constants = visitor.getConstants();
31610                                                analyzeConstantDataFlowRelation(tableColumn, constants, EffectType.select, null);
31611                                        }
31612                                }
31613                        } else if (value.getExpressionType() == EExpressionType.subquery_t) {
31614                                analyzeSelectStmt(value.getSubQuery());
31615                                ResultSet resultSet = (ResultSet) modelManager.getModel(value.getSubQuery());
31616                                if (resultSet != null) {
31617                                        for (int i = 0; i < resultSet.getColumns().size(); i++) {
31618                                                TObjectName columnName =  new TObjectName();
31619                                                if(resultSet.getColumns().get(i).getAlias()!=null) {
31620                                                        columnName.setString(resultSet.getColumns().get(i).getAlias());
31621                                                }
31622                                                else {
31623                                                        ResultColumn subqueryColumn = resultSet.getColumns().get(i);
31624                                                        String segment = DlineageUtil.columnSegmentOf(subqueryColumn.getName());
31625                                                        // a column the subquery names takes that name as written, as an alias does above; an
31626                                                        // anonymous expression (ARRAY_AGG(t.x)) has no name, and keeps the display it always had
31627                                                        columnName.setString(subqueryColumn.getIdentifierKey() != null ? segment
31628                                                                        : LegacyDlineageWireNamesV1.columnName(segment));
31629                                                }
31630                                                TableColumn tableColumn = modelFactory.createTableColumn(unnestTable, columnName, true);
31631                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
31632                                                relation.setEffectType(EffectType.select);
31633                                                relation.setTarget(new TableColumnRelationshipElement(tableColumn));
31634                                                relation.addSource(
31635                                                                new ResultColumnRelationshipElement(resultSet.getColumns().get(i)));
31636                                        }
31637                                }
31638                        }
31639                }
31640                if (unnestTable.getColumns().isEmpty() && table.getAliasClause() != null
31641                                && table.getAliasClause().getAliasName() != null) {
31642                        // an array none of whose items made a column (an empty array, UNNEST(ARRAY<INT64>[]) AS x): no rows, but
31643                        // still the column x the query selects; without it the select list lost x (plan 09 slice 11)
31644                        unnestTable.setCreateTable(true);
31645                        modelFactory.createTableColumn(unnestTable, table.getAliasClause().getAliasName(), true);
31646                }
31647        }
31648
31649        /**
31650         * Trino and Athena {@code UNNEST(a, b) [WITH ORDINALITY] AS t(x, y[, n])}: links each alias column of the unnest
31651         * table to the array it reads, found through the resolver's binding of the argument (a column of a physical
31652         * table, of a CTE or of a derived table), so the owner need not be the first FROM item. An alias reads the
31653         * argument at its position when the counts agree, every alias reads the one argument's element when there is a
31654         * single argument (an ARRAY(ROW) expands into one alias per field), and with several arguments and a different
31655         * number of aliases no edge is published, because the mapping needs the element types. The ordinality alias
31656         * reads every array whole: its value depends on their cardinalities, not on any element. Nothing is linked for an
31657         * expression argument, {@code UNNEST(sequence(1, n))}, whose value is not an array column (Mantis 4826).
31658         */
31659        private void analyzeTrinoUnnest(TTable table) {
31660                TUnnestClause clause = table.getUnnestClause();
31661                Table unnestTable = modelFactory.createTable(table);
31662                if (unnestTable == null) {
31663                        return;
31664                }
31665                unnestTable.setSubType(SubType.unnest);
31666                TObjectNameList arguments = clause.getColumns();
31667                TObjectNameList aliases = table.getAliasClause() != null ? table.getAliasClause().getColumns() : null;
31668                if (arguments == null || arguments.size() == 0 || aliases == null || aliases.size() == 0) {
31669                        return;
31670                }
31671                int argCount = arguments.size();
31672                int aliasCount = aliases.size();
31673                boolean ordinality = clause.isWithOrdinality();
31674                int valueAliases = ordinality ? aliasCount - 1 : aliasCount;
31675                for (int i = 0; i < aliasCount; i++) {
31676                        TableColumn targetColumn = modelFactory.createTableColumn(unnestTable, aliases.getObjectName(i), true);
31677                        if (targetColumn == null) {
31678                                continue;
31679                        }
31680                        List<Object> sources = new ArrayList<Object>();
31681                        if (ordinality && i == aliasCount - 1) {
31682                                for (int a = 0; a < argCount; a++) {
31683                                        Object source = unnestArgumentSource(arguments.getObjectName(a), false);
31684                                        if (source != null) {
31685                                                sources.add(source);
31686                                        }
31687                                }
31688                        } else {
31689                                int argIndex = argCount == valueAliases ? i : argCount == 1 ? 0 : -1;
31690                                Object source = argIndex >= 0 ? unnestArgumentSource(arguments.getObjectName(argIndex), true) : null;
31691                                if (source != null) {
31692                                        sources.add(source);
31693                                }
31694                        }
31695                        for (Object source : sources) {
31696                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
31697                                relation.setEffectType(EffectType.select);
31698                                relation.setTarget(new TableColumnRelationshipElement(targetColumn));
31699                                if (source instanceof TableColumn) {
31700                                        relation.addSource(new TableColumnRelationshipElement((TableColumn) source));
31701                                } else {
31702                                        relation.addSource(new ResultColumnRelationshipElement((ResultColumn) source));
31703                                }
31704                        }
31705                }
31706        }
31707
31708        /** The position of {@code column} in the derived column list of {@code table}, {@code d(a, xs)}; -1 when absent. */
31709        private int derivedColumnPosition(TTable table, TObjectName column) {
31710                if (table.getAliasClause() == null || table.getAliasClause().getColumns() == null) {
31711                        return -1;
31712                }
31713                TObjectNameList columns = table.getAliasClause().getColumns();
31714                for (int i = 0; i < columns.size(); i++) {
31715                        if (DlineageUtil.sameColumnName(columns.getObjectName(i), column)) {
31716                                return i;
31717                        }
31718                }
31719                return -1;
31720        }
31721
31722        /**
31723         * The model an UNNEST argument reads, through the resolver's binding of the column: for a CTE or derived-table
31724         * owner its result column; for a physical table the element node of the array ({@code items[*]}) when
31725         * {@code element}, else the array column itself. Null when the argument is unbound.
31726         */
31727        private Object unnestArgumentSource(TObjectName argument, boolean element) {
31728                TTable owner = argument.getSourceTable();
31729                if (owner == null) {
31730                        return null;
31731                }
31732                if (owner.getCTE() != null || owner.isCTEName() || owner.getSubquery() != null) {
31733                        if (owner.getSubquery() != null && modelManager.getModel(owner) == null) {
31734                                // a derived table analyzed later in the FROM clause: its result set must exist to link to it
31735                                analyzeTableSubquery(owner);
31736                        }
31737                        Object model = modelManager.getModel(owner);
31738                        if (!(model instanceof ResultSet) && owner.getSubquery() != null) {
31739                                model = modelManager.getModel(owner.getSubquery());
31740                        }
31741                        if (model instanceof ResultSet) {
31742                                return matchResultColumn(((ResultSet) model).getColumns(), argument);
31743                        }
31744                        return null;
31745                }
31746                Object model = modelManager.getModel(owner);
31747                if (model == null && owner.getTableType() == ETableSource.objectname) {
31748                        model = modelFactory.createTable(owner);
31749                }
31750                if (!(model instanceof Table)) {
31751                        return null;
31752                }
31753                if (element && nestedProfile().buildsDeclaredTree()) {
31754                        TableColumn elementNode = createArrayElement((Table) model, argument);
31755                        if (elementNode != null) {
31756                                return elementNode;
31757                        }
31758                }
31759                return modelFactory.createTableColumn((Table) model, argument, true);
31760        }
31761
31762        private void analyzePrestoUnnest(TSelectSqlStatement stmt, TTable table) {
31763                List<Table> tables = new ArrayList<Table>();
31764                TTable targetTable = stmt.getTables().getTable(0);
31765                SqllrObservations decided = SqllrObservations.current();
31766                if (decided != null) {
31767                        decided.decision(SqllrObservations.DECISION_UNNEST_FIRST_TABLE, null, targetTable,
31768                                        "the array of an UNNEST read from the first FROM item");
31769                }
31770                Table stmtTable = null;
31771                Object sourceTable = null;
31772                if (targetTable.getSubquery() == null && (targetTable.isCTEName() || targetTable.getCTE() != null)) {
31773                        // a CTE reference: the result set of its body is the source of the unnested array, and no physical table
31774                        // is modelled under the CTE's name (Mantis 4826)
31775                        Object cteModel = modelManager.getModel(targetTable);
31776                        TSelectSqlStatement body = targetTable.getCTE() != null ? targetTable.getCTE().getSubquery() : null;
31777                        if (!(cteModel instanceof ResultSet) && body != null) {
31778                                if (!stmtStack.contains(body) && modelManager.getModel(body) == null
31779                                                && (body.getResultColumnList() == null || modelManager.getModel(body.getResultColumnList()) == null)) {
31780                                        analyzeSelectStmt(body);
31781                                }
31782                                cteModel = body.isCombinedQuery() || body.getResultColumnList() == null ? modelManager.getModel(body)
31783                                                : modelManager.getModel(body.getResultColumnList());
31784                        }
31785                        if (cteModel instanceof ResultSet) {
31786                                sourceTable = cteModel;
31787                        }
31788                } else if (targetTable.getSubquery() == null || targetTable.getSubquery().getValueClause() != null) {
31789                        // a physical table, or a VALUES list whose rows the unnest reads by the derived column list; a SELECT
31790                        // derived table is read through its result set and no physical table is modelled under its alias
31791                        stmtTable = modelFactory.createTable(targetTable);
31792                }
31793                if (targetTable.getSubquery() != null) {
31794                        analyzeSelectStmt(targetTable.getSubquery());
31795                        if (targetTable.getSubquery().isCombinedQuery()) {
31796                                ResultSet sourceResultSet = (ResultSet) modelManager.getModel(targetTable.getSubquery());
31797                                sourceTable = sourceResultSet;
31798                        } else if (targetTable.getSubquery().getResultColumnList() != null) {
31799                                ResultSet sourceResultSet = (ResultSet) modelManager
31800                                                .getModel(targetTable.getSubquery().getResultColumnList());
31801                                sourceTable = sourceResultSet;
31802                        } else if (targetTable.getSubquery().getValueClause() != null) {
31803                                List<TResultColumnList> rowList = targetTable.getSubquery().getValueClause().getRows();
31804                                if (rowList != null && rowList.size() > 0) {
31805                                        Table valuesTable = modelFactory.createTableByName("Values-Table", true);
31806                                        int columnCount = rowList.get(0).size();
31807                                        for (int j = 1; j <= columnCount; j++) {
31808                                                TObjectName columnName = new TObjectName();
31809                                                TResultColumn columnObject = rowList.get(0).getResultColumn(j - 1);
31810                                                if (columnObject.getExpr().getExpressionType() == EExpressionType.typecast_t) {
31811                                                        columnName.setString(columnObject.getExpr().getLeftOperand().toString());
31812                                                } else {
31813                                                        columnName.setString(columnObject.getExpr().toString());
31814                                                }
31815                                                modelFactory.createTableColumn(valuesTable, columnName, true);
31816                                        }
31817                                        valuesTable.setCreateTable(true);
31818                                        valuesTable.setSubType(SubType.values_table);
31819                                        sourceTable = valuesTable;
31820                                }
31821                        }
31822                }
31823                if (stmtTable != null) {
31824                        tables.add(stmtTable);
31825                }
31826                Table unnestTable = modelFactory.createTable(table);
31827                unnestTable.setSubType(SubType.unnest);
31828                tables.add(unnestTable);
31829                if (table.getAliasClause() != null && table.getAliasClause().getColumns() != null
31830                                && table.getUnnestClause().getColumns() != null) {
31831                        TObjectNameList arguments = table.getUnnestClause().getColumns();
31832                        TObjectNameList aliases = table.getAliasClause().getColumns();
31833                        int argCount = arguments.size();
31834                        int aliasCount = aliases.size();
31835                        // UNNEST(a, b) WITH ORDINALITY AS t(x, y, n): the last alias is the 1-based position (Mantis 4826)
31836                        boolean ordinality = table.getUnnestClause().isWithOrdinality();
31837                        int valueAliases = ordinality ? aliasCount - 1 : aliasCount;
31838                        for (int i = 0; i < aliasCount; i++) {
31839                                boolean ordinal = ordinality && i == aliasCount - 1;
31840                                // Which argument an alias reads: by position when the counts agree; a single argument gives every
31841                                // alias (Trino expands an ARRAY(ROW) argument into one alias per field); with several arguments and a
31842                                // different number of aliases the mapping needs the element types, so no edge is published.
31843                                int argIndex = -1;
31844                                if (!ordinal) {
31845                                        if (argCount == valueAliases) {
31846                                                argIndex = i;
31847                                        } else if (argCount == 1) {
31848                                                argIndex = 0;
31849                                        }
31850                                }
31851                                TableColumn sourceColumn = null;
31852                                ResultColumn sourceResultColumn = null;
31853                                List<TableColumn> ordinalSources = new ArrayList<TableColumn>();
31854                                if (ordinal) {
31855                                        // the 1-based position: it depends on every array's cardinality (the longest sets the row count)
31856                                        for (int a = 0; a < argCount && stmtTable != null; a++) {
31857                                                TableColumn array = modelFactory.createTableColumn(stmtTable, arguments.getObjectName(a), true);
31858                                                if (array != null) {
31859                                                        ordinalSources.add(array);
31860                                                }
31861                                        }
31862                                } else if (argIndex >= 0) {
31863                                        TObjectName argument = arguments.getObjectName(argIndex);
31864                                        if (sourceTable == null && stmtTable != null && nestedProfile().buildsDeclaredTree()) {
31865                                                // STRUCT tree: the column comes from the elements of the array it unnests, payload.items[*]
31866                                                sourceColumn = createArrayElement(stmtTable, argument);
31867                                        }
31868                                        if (stmtTable == null && sourceTable instanceof ResultSet) {
31869                                                // the array is a column of the CTE or derived table: the unnest column reads that result column
31870                                                sourceResultColumn = matchResultColumn(((ResultSet) sourceTable).getColumns(), argument);
31871                                        }
31872                                        if (sourceColumn == null && stmtTable != null) {
31873                                                sourceColumn = modelFactory.createTableColumn(stmtTable, argument, true);
31874                                                // the VALUES column the argument names: its position in the derived column list, d(xs)
31875                                                if (sourceTable instanceof Table) {
31876                                                        int position = derivedColumnPosition(targetTable, argument);
31877                                                        if (position >= 0 && position < ((Table) sourceTable).getColumns().size()) {
31878                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
31879                                                                relation.setEffectType(EffectType.select);
31880                                                                relation.setTarget(new TableColumnRelationshipElement(sourceColumn));
31881                                                                relation.addSource(
31882                                                                                new TableColumnRelationshipElement(((Table) sourceTable).getColumns().get(position)));
31883                                                        }
31884                                                } else if (sourceTable instanceof ResultSet) {
31885                                                        ResultColumn candidate = matchResultColumn(((ResultSet) sourceTable).getColumns(), argument);
31886                                                        if (candidate != null) {
31887                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
31888                                                                relation.setEffectType(EffectType.select);
31889                                                                relation.setTarget(new TableColumnRelationshipElement(sourceColumn));
31890                                                                relation.addSource(new ResultColumnRelationshipElement(candidate));
31891                                                        }
31892                                                }
31893                                        }
31894                                }
31895                                TableColumn targetColumn = modelFactory.createTableColumn(unnestTable, aliases.getObjectName(i), true);
31896                                if (sourceColumn != null) {
31897                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
31898                                        relation.setEffectType(EffectType.select);
31899                                        relation.setTarget(new TableColumnRelationshipElement(targetColumn));
31900                                        relation.addSource(new TableColumnRelationshipElement(sourceColumn));
31901                                } else if (sourceResultColumn != null) {
31902                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
31903                                        relation.setEffectType(EffectType.select);
31904                                        relation.setTarget(new TableColumnRelationshipElement(targetColumn));
31905                                        relation.addSource(new ResultColumnRelationshipElement(sourceResultColumn));
31906                                }
31907                                for (TableColumn array : ordinalSources) {
31908                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
31909                                        relation.setEffectType(EffectType.select);
31910                                        relation.setTarget(new TableColumnRelationshipElement(targetColumn));
31911                                        relation.addSource(new TableColumnRelationshipElement(array));
31912                                }
31913                        }
31914                }
31915
31916                ResultSet resultSet = modelFactory.createResultSet(stmt,
31917                                isTopResultSet(stmt) && isShowTopSelectResultSet());
31918                TResultColumnList columnList = stmt.getResultColumnList();
31919                for (int i = 0; i < columnList.size(); i++) {
31920                        TResultColumn column = columnList.getResultColumn(i);
31921                        ResultColumn resultColumn = modelFactory.createSelectSetResultColumn(resultSet, column, i);
31922                        if (resultColumn.getColumnObject() instanceof TResultColumn) {
31923                                TResultColumn columnObject = (TResultColumn) resultColumn.getColumnObject();
31924                                if (columnObject.getFieldAttr() != null) {
31925                                        if (DlineageUtil.isStarName((columnObject.getFieldAttr()))) {
31926                                                for (int k = 0; k < tables.size(); k++) {
31927                                                        Table tableItem = tables.get(k);
31928                                                        for (TableColumn tableColumn : tableItem.getColumns()) {
31929                                                                resultColumn.bindStarLinkColumn(tableColumn.getColumnObject());
31930                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
31931                                                                relation.setEffectType(EffectType.select);
31932                                                                relation.setTarget(new ResultColumnRelationshipElement(resultColumn, tableColumn.getColumnObject()));
31933                                                                relation.addSource(new TableColumnRelationshipElement(tableColumn));
31934                                                        }
31935                                                }
31936                                        } else {
31937                                                boolean match = false;
31938                                                TObjectName read = columnObject.getFieldAttr();
31939                                                // a field of an unnest column, sites.hostname with UNNEST(items) t(sites): the read is of the
31940                                                // column sites, named by the base token of the field path (Mantis 4826)
31941                                                String readColumn = read.getFieldPathRead() != null && read.getFieldPathBaseToken() != null
31942                                                                ? read.getFieldPathBaseToken().toString() : null;
31943                                                for (int k = 0; k < tables.size(); k++) {
31944                                                        Table tableItem = tables.get(k);
31945                                                        for (TableColumn tableColumn : tableItem.getColumns()) {
31946                                                                boolean matches = readColumn != null ? DlineageUtil.columnMatches(tableColumn, readColumn)
31947                                                                                : DlineageUtil.columnMatches(tableColumn, read);
31948                                                                if (matches) {
31949                                                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
31950                                                                        relation.setEffectType(EffectType.select);
31951                                                                        relation.setTarget(new ResultColumnRelationshipElement(resultColumn));
31952                                                                        relation.addSource(new TableColumnRelationshipElement(tableColumn));
31953                                                                        match = true;
31954                                                                }
31955                                                        }
31956                                                }
31957                                                if (!match && stmtTable == null && sourceTable instanceof ResultSet) {
31958                                                        // a column of the CTE the unnest reads from (Mantis 4826)
31959                                                        ResultColumn cteColumn = matchResultColumn(((ResultSet) sourceTable).getColumns(), read);
31960                                                        if (cteColumn != null) {
31961                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
31962                                                                relation.setEffectType(EffectType.select);
31963                                                                relation.setTarget(new ResultColumnRelationshipElement(resultColumn));
31964                                                                relation.addSource(new ResultColumnRelationshipElement(cteColumn));
31965                                                                match = true;
31966                                                        }
31967                                                }
31968                                                if (!match && stmtTable != null) {
31969                                                        TableColumn tableColumn = modelFactory.createTableColumn(stmtTable, read, false);
31970                                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
31971                                                        relation.setEffectType(EffectType.select);
31972                                                        relation.setTarget(new ResultColumnRelationshipElement(resultColumn));
31973                                                        relation.addSource(new TableColumnRelationshipElement(tableColumn));
31974                                                }
31975                                        }
31976                                }
31977                        }
31978                }
31979        }
31980
31981        /**
31982         * Plan section 4.3: a Hive-family {@code LATERAL VIEW f(args) lv AS c1, c2} is one more relation of its SELECT.
31983         * <ul>
31984         *   <li>its table is the view's own ({@link ModelFactory#createArrayRelationTable}), never a table of the script
31985         *       that happens to have the alias's name (Mantis 4890);</li>
31986         *   <li>the function is a node of its own, fed by its arguments, which TSQLResolver2 resolved against the
31987         *       relations to the view's left, an earlier LATERAL VIEW included (Mantis 4888, 4889);</li>
31988         *   <li>each column alias is a column of the view, fed by the function;</li>
31989         *   <li>for a function proven to produce the elements of its argument (role ELEMENT, {@link #lateralViewElementRole}),
31990         *       the element column is linked to the argument's element node ({@code items[*]}), so a read of it or of a
31991         *       field of it reads that node ({@code ModelFactory.createUnnestElementRead}).</li>
31992         * </ul>
31993         * The select list, the WHERE clause, joins and the rest are then analyzed by the general flow, the view being a
31994         * relation like any other (Mantis 4887).
31995         */
31996        private void analyzeLateralViewRelation(TTable table) {
31997                TLateralView view = table.getLateralView();
31998                TFunctionCall udtf = view == null ? null : view.getUdtf();
31999                String name = table.getAliasClause() != null && table.getAliasClause().getAliasName() != null
32000                                ? table.getAliasClause().getAliasName().toString()
32001                                : (udtf != null ? udtf.toString() : table.getName());
32002                Table shared = view == null ? null : lateralViewRelations.get(view);
32003                if (shared != null) {
32004                        // the FROM clause of a Hive multi-insert serves every INSERT: each body holds its own copy of the view's
32005                        // table, all of one TLateralView, and they are one relation (Codex review of phase 3)
32006                        if (modelManager.getBoundModel(table) == null) {
32007                                modelManager.bindModel(table, shared);
32008                        }
32009                        return;
32010                }
32011                Table relation = modelManager.getBoundModel(table) instanceof Table ? (Table) modelManager.getBoundModel(table)
32012                                : modelFactory.createArrayRelationTable(table, arrayRelationName(name));
32013                if (view != null) {
32014                        lateralViewRelations.put(view, relation);
32015                }
32016                if (udtf == null) {
32017                        return;
32018                }
32019                Function function = modelFactory.createFunction(udtf);
32020                ResultColumn functionColumn = modelFactory.createFunctionResultColumn(function, udtf.getFunctionName());
32021                for (int i = 0; udtf.getArgs() != null && i < udtf.getArgs().size(); i++) {
32022                        TExpression argument = udtf.getArgs().getExpression(i);
32023                        if (argument == null) {
32024                                continue;
32025                        }
32026                        columnsInExpr visitor = new columnsInExpr();
32027                        argument.inOrderTraverse(visitor);
32028                        List<TParseTreeNode> functions = visitor.getFunctions();
32029                        if (functions != null && !functions.isEmpty()) {
32030                                analyzeFunctionDataFlowRelation(functionColumn, functions, EffectType.function);
32031                        }
32032                        List<TSelectSqlStatement> subqueries = visitor.getSubquerys();
32033                        if (subqueries != null && !subqueries.isEmpty()) {
32034                                analyzeSubqueryDataFlowRelation(functionColumn, subqueries, EffectType.select);
32035                        }
32036                        List<TObjectName> unresolved = new ArrayList<TObjectName>();
32037                        List<RelationshipElement<?>> inputs = new ArrayList<RelationshipElement<?>>();
32038                        for (TObjectName input : visitor.getObjectNames()) {
32039                                if (!linkLateralViewInput(input, inputs)) {
32040                                        unresolved.add(input);
32041                                }
32042                        }
32043                        if (!inputs.isEmpty()) {
32044                                DataFlowRelationship inputRelation = modelFactory.createDataFlowRelation();
32045                                inputRelation.setEffectType(EffectType.select);
32046                                inputRelation.setTarget(new ResultColumnRelationshipElement(functionColumn));
32047                                for (RelationshipElement<?> input : inputs) {
32048                                        inputRelation.addSource(input);
32049                                }
32050                                addRelationRowsImpacts(inputRelation, EffectType.select);
32051                        }
32052                        analyzeDataFlowRelation(functionColumn, unresolved, EffectType.select, functions);
32053                        analyzeConstantDataFlowRelation(functionColumn, visitor.getConstants(), EffectType.select, functions);
32054                }
32055                int element = lateralViewElementRole(view, udtf);
32056                for (int j = 0; view.getColumnAliasList() != null && j < view.getColumnAliasList().size(); j++) {
32057                        TableColumn column = modelFactory.createTableColumn(relation, view.getColumnAliasList().getObjectName(j),
32058                                        true);
32059                        if (column == null) {
32060                                continue;
32061                        }
32062                        DataFlowRelationship relationship = modelFactory.createDataFlowRelation();
32063                        relationship.setEffectType(EffectType.select);
32064                        relationship.setTarget(new TableColumnRelationshipElement(column));
32065                        relationship.addSource(new ResultColumnRelationshipElement(functionColumn));
32066                        if (j == element) {
32067                                linkLateralViewElement(column, udtf.getArgs().getExpression(0));
32068                        }
32069                }
32070        }
32071
32072        /**
32073         * Plan section 4.3: adds to {@code inputs} the source of {@code input}, a column the arguments of a LATERAL VIEW's
32074         * function read, by the relation TSQLResolver2 resolved it to among the relations to the view's left: a table, an
32075         * earlier view, a CTE or a derived table. An input the resolver found in none of them reads nothing: the
32076         * statement's other relations are to the right, and matching one of them by name would publish an edge the SQL
32077         * does not have (Codex review of phase 2).
32078         *
32079         * @return false when the resolver gave no verdict on {@code input} (a variable, or no resolution), for the general
32080         *         path; true when the input is handled, linked or refused
32081         */
32082        private boolean linkLateralViewInput(TObjectName input, List<RelationshipElement<?>> inputs) {
32083                if (input.getDbObjectType() == EDbObjectType.variable || input.getResolution() == null) {
32084                        return false;
32085                }
32086                TTable source = input.getSourceTable();
32087                if (source == null) {
32088                        return true;
32089                }
32090                Object model = modelManager.getModel(source);
32091                if (model instanceof Table) {
32092                        TableColumn column = modelFactory.createTableColumn((Table) model, input, false);
32093                        if (column != null) {
32094                                inputs.add(new TableColumnRelationshipElement(column, input.getLocation(), input));
32095                        }
32096                        return true;
32097                }
32098                if (!(model instanceof ResultSet)) {
32099                        return false;
32100                }
32101                List<ResultColumn> columns = ((ResultSet) model).getColumns();
32102                ResultColumn column = structFieldReadSource(structFieldReadBaseColumn(input, columns), input);
32103                for (int k = 0; column == null && k < columns.size(); k++) {
32104                        if (!columns.get(k).isStarColumn() && referencesColumn(input, columns.get(k))) {
32105                                column = columns.get(k);
32106                        }
32107                }
32108                for (int k = 0; column == null && k < columns.size(); k++) {
32109                        if (columns.get(k).isStarColumn()) {
32110                                column = columns.get(k);
32111                                if (!containsStarColumn(column, input)) {
32112                                        column.bindStarLinkColumn(input);
32113                                }
32114                        }
32115                }
32116                if (column != null) {
32117                        inputs.add(new ResultColumnRelationshipElement(column, input));
32118                }
32119                return true;
32120        }
32121
32122        /** The relation of each LATERAL VIEW modelled, so the copies of a multi-insert's view share one. */
32123        private final Map<TLateralView, Table> lateralViewRelations = new IdentityHashMap<TLateralView, Table>();
32124
32125        /** The parser of the unit being analyzed ({@link #recordStatementOrdinals}). */
32126        private TGSqlParser unitParser;
32127
32128        /** The identity keys of the tables the unit's statements name, made on first use. */
32129        private Set<Object> unitTableNames;
32130
32131        /**
32132         * The identity keys of every table a statement of the unit names, a later statement's included: a LATERAL VIEW's
32133         * relation must not publish the name of a table the script reads after it (Codex review of phase 2).
32134         */
32135        private Set<Object> unitTableNames() {
32136                if (unitTableNames == null) {
32137                        final Set<Object> names = new HashSet<Object>();
32138                        for (int i = 0; unitParser != null && i < unitParser.getSqlstatements().size(); i++) {
32139                                unitParser.getSqlstatements().get(i).acceptChildren(new TParseTreeVisitor() {
32140                                        @Override
32141                                        public void preVisit(TTable table) {
32142                                                if (table.getTableType() == ETableSource.objectname && !table.isCTEName()
32143                                                                && table.getTableName() != null) {
32144                                                        names.add(DlineageUtil.getTableIdentityKey(table.getTableName().toString()));
32145                                                }
32146                                        }
32147                                });
32148                        }
32149                        unitTableNames = names;
32150                }
32151                return unitTableNames;
32152        }
32153
32154        /** The published names of the relations LATERAL VIEWs added, by identity key. */
32155        private final Set<Object> arrayRelationNames = new HashSet<Object>();
32156
32157        /**
32158         * The name a LATERAL VIEW's relation publishes: its alias, numbered ({@code tmp1-1}, {@code tmp1-2}) when a table
32159         * of the script or an earlier view already has that name, so two relations never share one (Mantis 4845, 4890).
32160         */
32161        private String arrayRelationName(String alias) {
32162                String name = alias;
32163                for (int k = 1; modelManager.getTableByName(DlineageUtil.getTableIdentityKey(name)) != null
32164                                || arrayRelationNames.contains(DlineageUtil.getTableIdentityKey(name))
32165                                || unitTableNames().contains(DlineageUtil.getTableIdentityKey(name)); k++) {
32166                        name = alias + "-" + k;
32167                }
32168                arrayRelationNames.add(DlineageUtil.getTableIdentityKey(name));
32169                return name;
32170        }
32171
32172        /**
32173         * Plan section 4.3, role proof: which column alias of a LATERAL VIEW is the element of the function's argument,
32174         * or -1. Only an unqualified {@code explode} or {@code explode_outer} of one argument with one alias proves it (the
32175         * rule of Mantis 4845); any other function ({@code posexplode}, {@code inline}, a UDTF), a qualified one
32176         * ({@code my_schema.explode}), another arity, or an argument the DDL declares as a MAP keeps every alias fed by the
32177         * whole argument through the function.
32178         */
32179        private int lateralViewElementRole(TLateralView view, TFunctionCall udtf) {
32180                if (!nestedProfile().buildsDeclaredTree() || udtf.getFunctionName() == null
32181                                || udtf.getFunctionName().getSchemaToken() != null || udtf.getFunctionName().getDatabaseToken() != null
32182                                || udtf.getArgs() == null || udtf.getArgs().size() != 1 || view.getColumnAliasList() == null) {
32183                        return -1;
32184                }
32185                String function = udtf.getFunctionName().toString();
32186                int aliases = view.getColumnAliasList().size();
32187                if (("explode".equalsIgnoreCase(function) || "explode_outer".equalsIgnoreCase(function)) && aliases == 1) { // non-identifier-compare: built-in function name
32188                        return 0;
32189                }
32190                return -1;
32191        }
32192
32193        /**
32194         * Links {@code column}, the element alias of a LATERAL VIEW, to the element node of the function's argument
32195         * ({@code items[*]} of {@code events.items}, or {@code items[*].subs[*]} when the argument is a field of an earlier
32196         * view's element). The argument must be one column the resolver placed, of a table or of another view; the
32197         * column of a CTE or a derived table has no element node, and a declared MAP has no element.
32198         */
32199        private void linkLateralViewElement(TableColumn column, TExpression argument) {
32200                if (argument == null || argument.getExpressionType() != EExpressionType.simple_object_name_t
32201                                || argument.getObjectOperand() == null || argument.getObjectOperand().getSourceTable() == null) {
32202                        return;
32203                }
32204                TObjectName array = argument.getObjectOperand();
32205                Object owner = modelManager.getModel(array.getSourceTable());
32206                if (!(owner instanceof Table)) {
32207                        return;
32208                }
32209                TableColumn arrayNode = modelFactory.createTableColumn((Table) owner, array, false);
32210                if (arrayNode == null || arrayNode.getDeclaredKind() == gudusoft.gsqlparser.common.structured.DeclaredTypeNode.Kind.MAP) {
32211                        return;
32212                }
32213                TableColumn element = createArrayElement((Table) owner, array);
32214                if (element == null || !element.isFieldPathNode() || element.getFieldPath().getSegments().get(
32215                                element.getFieldPath().getSegments().size() - 1).getKind()
32216                                != gudusoft.gsqlparser.common.structured.StructuredPathSegmentKind.ARRAY_ELEMENT) {
32217                        return;
32218                }
32219                // not a STRUCT field of its source: the simple output's struct test would drop lv2.sub <- items[*].subs
32220                column.setSourceColumn(element);
32221        }
32222
32223        private boolean isFromFunction(TObjectName object) {
32224
32225                Stack<TParseTreeNode> nodes = object.getStartToken().getNodesStartFromThisToken();
32226                if (nodes != null) {
32227                        for (int i = 0; i < nodes.size(); i++) {
32228                                if (nodes.get(i) instanceof TFunctionCall) {
32229                                        return true;
32230                                }
32231                        }
32232                }
32233                return false;
32234        }
32235
32236        private TResultColumnList getResultColumnList(TSelectSqlStatement stmt) {
32237                // Iterative DFS (left-first) to find the first non-combined query's result column list.
32238                // Avoids StackOverflow with deeply nested UNION trees.
32239                Deque<TSelectSqlStatement> stack = new ArrayDeque<>();
32240                stack.push(stmt);
32241                while (!stack.isEmpty()) {
32242                        TSelectSqlStatement current = stack.pop();
32243                        if (current.isCombinedQuery()) {
32244                                // Push right first so left is processed first (stack is LIFO)
32245                                if (current.getRightStmt() != null) stack.push(current.getRightStmt());
32246                                if (current.getLeftStmt() != null) stack.push(current.getLeftStmt());
32247                        } else {
32248                                if (current.getResultColumnList() != null) {
32249                                        return current.getResultColumnList();
32250                                }
32251                        }
32252                }
32253                return null;
32254        }
32255
32256        private void createPseudoImpactRelation(TCustomSqlStatement stmt, ResultSet resultSetModel, EffectType effectType) {
32257                if (stmt.getTables() != null) {
32258                        for (int i = 0; i < stmt.getTables().size(); i++) {
32259                                TTable table = stmt.getTables().getTable(i);
32260                                if (modelManager.getModel(table) instanceof ResultSet) {
32261                                        ResultSet tableModel = (ResultSet) modelManager.getModel(table);
32262                                        if (tableModel != resultSetModel && !tableModel.getRelationRows().getHoldRelations().isEmpty()) {
32263                                                ImpactRelationship impactRelation = modelFactory.createImpactRelation();
32264                                                impactRelation.setEffectType(effectType);
32265                                                impactRelation.addSource(new RelationRowsRelationshipElement<ResultSetRelationRows>(
32266                                                                tableModel.getRelationRows()));
32267                                                impactRelation.setTarget(new RelationRowsRelationshipElement<ResultSetRelationRows>(
32268                                                                resultSetModel.getRelationRows()));
32269                                        }
32270                                } else if (modelManager.getModel(table) instanceof Table) {
32271                                        Table tableModel = (Table) modelManager.getModel(table);
32272                                        if (!tableModel.getRelationRows().getHoldRelations().isEmpty()) {
32273                                                ImpactRelationship impactRelation = modelFactory.createImpactRelation();
32274                                                impactRelation.setEffectType(effectType);
32275                                                impactRelation.addSource(
32276                                                                new RelationRowsRelationshipElement<TableRelationRows>(tableModel.getRelationRows()));
32277                                                impactRelation.setTarget(new RelationRowsRelationshipElement<ResultSetRelationRows>(
32278                                                                resultSetModel.getRelationRows()));
32279                                        }
32280                                }
32281                        }
32282                }
32283        }
32284
32285        private void analyzeFunctionDataFlowRelation(Object gspObject, List<TParseTreeNode> functions,
32286                        EffectType effectType) {
32287                for (int i = 0; i < functions.size(); i++) {
32288                        TParseTreeNode functionCall = functions.get(i);
32289                        if (functionCall instanceof TFunctionCall) {
32290                                Procedure procedure = modelManager.getProcedure(callAliasKey((TFunctionCall) functionCall));
32291                                if (gudusoft.gsqlparser.dlineage.routine.RoutineShadow.enabled()) shadowCallee("functionRelation3", ((TFunctionCall) functionCall).getFunctionName(), procedure,
32292                                                new Integer[] {gudusoft.gsqlparser.dlineage.routine.RoutineShadow.countAliasOf((TFunctionCall) functionCall)}, gudusoft.gsqlparser.catalog.runtime.CatalogObjectKind.FUNCTION);
32293                                if (procedure != null) {
32294                                        String procedureParent = getProcedureParentName(stmtStack.peek());
32295                                        if (procedureParent != null) {
32296                                                Procedure caller = getEnclosingProcedure(stmtStack.peek());
32297                                                if (caller != null) {
32298                                                        CallRelationship callRelation = modelFactory.createCallRelation();
32299                                                        callRelation.setCallObject(functionCall);
32300                                                        callRelation.setTarget(new ProcedureRelationshipElement(caller));
32301                                                        callRelation.addSource(new ProcedureRelationshipElement(procedure));
32302                                                        if (isBuiltInFunctionName(((TFunctionCall)functionCall).getFunctionName())||isKeyword(((TFunctionCall)functionCall).getFunctionName())) {
32303                                                                callRelation.setBuiltIn(true);
32304                                                        }
32305                                                }
32306                                        }
32307                                        if (procedure.getProcedureObject() instanceof TCreateFunctionStmt) {
32308                                                TCreateFunctionStmt createFunction = (TCreateFunctionStmt)procedure.getProcedureObject();
32309                                                TTypeName dataType = createFunction.getReturnDataType();
32310                                                if (dataType!=null && dataType.getTypeOfList() != null && dataType.getTypeOfList().getColumnDefList() != null) {
32311                                                        Object modelObject = modelManager.getModel(gspObject);
32312                                                        if(modelObject instanceof ResultColumn) {
32313                                                                ResultColumn resultColumn = (ResultColumn)modelObject;
32314                                                                ResultSet resultSet = resultColumn.getResultSet();
32315                                                                for (int j = 0; j < dataType.getTypeOfList().getColumnDefList().size(); j++) {
32316                                                                        TObjectName columnName = new TObjectName();
32317                                                                        if( dataType.getDataType() == EDataType.array_t) {
32318//                                                                              columnName.setString(resultColumn.getName() + ".array."
32319//                                                                                              + dataType.getTypeOfList().getColumnDefList().getColumn(j)
32320//                                                                                                              .getColumnName().getColumnNameOnly());
32321                                                                                columnName.setString(resultColumn.getName() + "."
32322                                                                                                + dataType.getTypeOfList().getColumnDefList().getColumn(j)
32323                                                                                                .getColumnName().getColumnNameOnly());
32324                                                                        }
32325                                                                        else {
32326                                                                                columnName.setString(resultColumn.getName() + "."
32327                                                                                                + dataType.getTypeOfList().getColumnDefList().getColumn(j)
32328                                                                                                                .getColumnName().getColumnNameOnly());
32329                                                                        }
32330                                                                        ResultColumn sturctColumn = modelFactory.createResultColumn(resultSet, columnName,
32331                                                                                        true);
32332                                                                        sturctColumn.setStruct(true);
32333                                                                        Function sourceFunction = (Function)createFunction(functionCall);
32334                                                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
32335                                                                        relation.setEffectType(effectType);
32336                                                                        relation.setTarget(new ResultColumnRelationshipElement(sturctColumn));
32337                                                                        if (sourceFunction.getColumns() != null && !sourceFunction.getColumns().isEmpty()) {
32338                                                                                for (ResultColumn column : sourceFunction.getColumns()) {
32339                                                                                        relation.addSource(new ResultColumnRelationshipElement(column));
32340                                                                                }
32341                                                                        }
32342                                                                }
32343                                                                resultSet.getColumns().remove(resultColumn);
32344                                                        }
32345                                                        return;
32346                                                }
32347                                        }
32348                                }
32349                        }
32350                        
32351                        if(gspObject instanceof TResultColumn) {
32352                                TResultColumn resultColumn = (TResultColumn)gspObject;
32353                                if(resultColumn.getAliasClause()!=null && resultColumn.getAliasClause().getColumns()!=null) {
32354                                        for(TObjectName columnName: resultColumn.getAliasClause().getColumns()) {
32355                                                analyzeFunctionDataFlowRelation(columnName, Arrays.asList(functionCall), effectType, null);
32356                                        }
32357                                        return;
32358                                }
32359                        }
32360                        analyzeFunctionDataFlowRelation(gspObject, Arrays.asList(functionCall), effectType, null);
32361                }
32362        }
32363
32364        private void analyzeFunctionDataFlowRelation(Object gspObject, List<TParseTreeNode> functions,
32365                        EffectType effectType, Process process) {
32366
32367                Object modelObject = modelManager.getModel(gspObject);
32368                if (modelObject == null) {
32369                        if (gspObject instanceof ResultColumn || gspObject instanceof TableColumn) {
32370                                modelObject = gspObject;
32371                        }
32372                }
32373
32374                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
32375                relation.setEffectType(effectType);
32376                relation.setProcess(process);
32377
32378                if (modelObject instanceof ResultColumn) {
32379                        relation.setTarget(new ResultColumnRelationshipElement((ResultColumn) modelObject));
32380
32381                } else if (modelObject instanceof TableColumn) {
32382                        relation.setTarget(new TableColumnRelationshipElement((TableColumn) modelObject));
32383
32384                } else {
32385                        throw new UnsupportedOperationException();
32386                }
32387
32388                for (int i = 0; i < functions.size(); i++) {
32389                        TParseTreeNode functionCall = functions.get(i);
32390
32391                        if (functionCall instanceof TFunctionCall) {
32392                                TFunctionCall call = (TFunctionCall) functionCall;
32393                                Procedure callee = modelManager.getProcedure(callAliasKey(call));
32394                                if (callee == null && analyzeProcedureDefinition(callAliasKey(call))) {
32395                                        callee = modelManager.getProcedure(callAliasKey(call));
32396                                }
32397                                if (gudusoft.gsqlparser.dlineage.routine.RoutineShadow.enabled()) shadowCallee("functionRelation4", call.getFunctionName(), callee, new Integer[] {gudusoft.gsqlparser.dlineage.routine.RoutineShadow.countAliasOf(call)}, gudusoft.gsqlparser.catalog.runtime.CatalogObjectKind.FUNCTION);
32398
32399                                if (callee != null) {
32400                                        String procedureParent = getProcedureParentName(stmtStack.peek());
32401                                        if (procedureParent != null) {
32402                                                Procedure caller = getEnclosingProcedure(stmtStack.peek());
32403                                                if (caller != null) {
32404                                                        CallRelationship callRelation = modelFactory.createCallRelation();
32405                                                        callRelation.setCallObject(functionCall);
32406                                                        callRelation.setTarget(new ProcedureRelationshipElement(caller));
32407                                                        callRelation.addSource(new ProcedureRelationshipElement(callee));
32408                                                        if (isBuiltInFunctionName(call.getFunctionName()) || isKeyword(call.getFunctionName())) {
32409                                                                callRelation.setBuiltIn(true);
32410                                                        }
32411                                                }
32412                                        }
32413                                        if (callee.getArguments() != null) {
32414                                                for (int j = 0; j < callee.getArguments().size(); j++) {
32415                                                        Argument argument = callee.getArguments().get(j);
32416                                                        Variable variable = resolveFormalVariable(callee, argument);
32417                                                        if(variable!=null) {
32418                                                                if (argument.getMode() == EParameterMode.out || argument.getMode() == EParameterMode.output) {
32419                                                                        Transform transform = new Transform();
32420                                                                        transform.setType(Transform.FUNCTION);
32421                                                                        transform.setCode(call);
32422                                                                        compositeBindingColumn(variable).setTransform(transform);
32423                                                                }
32424                                                                Process callProcess = modelFactory.createProcess(call);
32425                                                                variable.addProcess(callProcess);
32426                                                                analyzeFunctionArgumentsDataFlowRelation(compositeBindingColumn(variable), call, j, callProcess);
32427                                                        }
32428                                                }
32429                                        }
32430                                        Set<Object> functionTableModelObjs = modelManager.getFunctionResults(nameAliasKey(call.getFunctionName(), null));
32431                                        if (functionTableModelObjs != null) {
32432                                                modelManager.bindModel(call, functionTableModelObjs.iterator().next());
32433                                                for (Object functionTableModelObj : functionTableModelObjs) {
32434                                                        if (functionTableModelObj instanceof ResultSet) {
32435                                                                ResultSet resultSet = (ResultSet) functionTableModelObj;
32436                                                                for (ResultColumn column : resultSet.getColumns()) {
32437                                                                        relation.addSource(new ResultColumnRelationshipElement(column));
32438                                                                }
32439                                                        }
32440                                                }
32441                                        }
32442                                        continue;
32443                                }
32444                        }
32445
32446
32447                        Object functionModel = createFunction(functionCall);
32448                        if (functionModel instanceof Function) {
32449                                Function sourceFunction = (Function)functionModel;
32450                                if (sourceFunction.getColumns() != null && !sourceFunction.getColumns().isEmpty()) {
32451                                        for (ResultColumn column : sourceFunction.getColumns()) {
32452                                                relation.addSource(new ResultColumnRelationshipElement(column));
32453                                        }
32454                                }
32455                                else if (functionCall instanceof TFunctionCall) {
32456                                        relation.addSource(new ResultColumnRelationshipElement((FunctionResultColumn) modelManager
32457                                                        .getModel(((TFunctionCall) functionCall).getFunctionName())));
32458                                } else if (functionCall instanceof TCaseExpression) {
32459                                        relation.addSource(new ResultColumnRelationshipElement((FunctionResultColumn) modelManager
32460                                                        .getModel(((TCaseExpression) functionCall).getWhenClauseItemList())));
32461                                }
32462                                
32463                                if (sourceFunction != null && !sourceFunction.getRelationRows().getHoldRelations().isEmpty()) {
32464                                        boolean find = false;
32465                                        if (modelObject instanceof ResultColumn) {
32466                                                ResultSetRelationRows targetRelationRows = ((ResultColumn) modelObject).getResultSet().getRelationRows();
32467                                                if(targetRelationRows.hasRelation()) {
32468                                                        for(Relationship relationship: targetRelationRows.getHoldRelations()) {
32469                                                                if(relationship.getSources().contains(sourceFunction.getRelationRows())) {
32470                                                                        find = true;
32471                                                                        break;
32472                                                                }
32473                                                        }
32474                                                }
32475                                        }
32476                                        else if (modelObject instanceof TableColumn) {
32477                                                TableRelationRows targetRelationRows = ((TableColumn) modelObject).getTable().getRelationRows();
32478                                                if(targetRelationRows.hasRelation()) {
32479                                                        for(Relationship relationship: targetRelationRows.getHoldRelations()) {
32480                                                                if(relationship.getSources().contains(sourceFunction.getRelationRows())) {
32481                                                                        find = true;
32482                                                                        break;
32483                                                                }
32484                                                        }
32485                                                }
32486                                        }
32487                                        
32488                                        if (!find) {
32489                                                ImpactRelationship impactRelation = modelFactory.createImpactRelation();
32490                                                impactRelation.setEffectType(EffectType.select);
32491                                                impactRelation.addSource(new RelationRowsRelationshipElement<ResultSetRelationRows>(
32492                                                                sourceFunction.getRelationRows()));
32493                                                if (modelObject instanceof ResultColumn) {
32494                                                        impactRelation.setTarget(new RelationRowsRelationshipElement<ResultSetRelationRows>(
32495                                                                        ((ResultColumn) modelObject).getResultSet().getRelationRows()));
32496                                                } else if (modelObject instanceof TableColumn) {
32497                                                        impactRelation.setTarget(new RelationRowsRelationshipElement<TableRelationRows>(
32498                                                                        ((TableColumn) modelObject).getTable().getRelationRows()));
32499                                                }
32500                                        }
32501                                }
32502                        } else if (functionModel instanceof Table) {
32503                                TFunctionCall call = (TFunctionCall) functionCall;
32504                                String functionName = call.getFunctionName().toString();
32505                                boolean flag = false;
32506                                if (functionName.indexOf(".") != -1) {
32507                                        String columnName = functionName.substring(functionName.indexOf(".") + 1);
32508                                        for (TableColumn tableColumn : ((Table) functionModel).getColumns()) {
32509                                                if (DlineageUtil.sameName(ESQLDataObjectType.dotColumn, DlineageUtil.columnSegmentOf(tableColumn.getName()), columnName)) {
32510                                                        TableColumnRelationshipElement element = new TableColumnRelationshipElement(tableColumn);
32511                                                        relation.addSource(element);
32512                                                        flag = true;
32513                                                        break;
32514                                                }
32515                                        }
32516                                }
32517
32518                                if (!flag) {
32519                                        TableColumn tableColumn = modelFactory.createTableColumn((Table) functionModel,
32520                                                        ((TFunctionCall) functionCall));
32521                                        TableColumnRelationshipElement element = new TableColumnRelationshipElement(tableColumn);
32522                                        relation.addSource(element);
32523                                }
32524                        }
32525                }
32526
32527        }
32528
32529        private void analyzeSubqueryDataFlowRelation(Object gspObject, List<TSelectSqlStatement> subquerys,
32530                        EffectType effectType) {
32531                analyzeSubqueryDataFlowRelation(gspObject, subquerys, effectType, null);
32532        }
32533
32534        private void analyzeSubqueryDataFlowRelation(Object gspObject, List<TSelectSqlStatement> subquerys,
32535                        EffectType effectType, Process process) {
32536
32537                Object modelObject = modelManager.getModel(gspObject);
32538                if (modelObject == null) {
32539                        if (gspObject instanceof ResultColumn || gspObject instanceof TableColumn) {
32540                                modelObject = gspObject;
32541                        }
32542                }
32543
32544                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
32545                relation.setEffectType(effectType);
32546                relation.setProcess(process);
32547
32548                if (modelObject instanceof ResultColumn) {
32549                        relation.setTarget(new ResultColumnRelationshipElement((ResultColumn) modelObject));
32550
32551                } else if (modelObject instanceof TableColumn) {
32552                        relation.setTarget(new TableColumnRelationshipElement((TableColumn) modelObject));
32553
32554                } else {
32555                        throw new UnsupportedOperationException();
32556                }
32557
32558                for (int i = 0; i < subquerys.size(); i++) {
32559                        TSelectSqlStatement subquery = subquerys.get(i);
32560                        ResultSet resultSetModel = (ResultSet) modelManager.getModel(subquery);
32561                        if (resultSetModel != null && resultSetModel.getColumns() != null) {
32562                                for (ResultColumn column : resultSetModel.getColumns()) {
32563                                        relation.addSource(new ResultColumnRelationshipElement(column));
32564                                }
32565                        }
32566                        
32567                        if (resultSetModel != null && !resultSetModel.getRelationRows().getHoldRelations().isEmpty()) {
32568                                boolean find = false;
32569                                if (modelObject instanceof ResultColumn) {
32570                                        ResultSetRelationRows targetRelationRows = ((ResultColumn) modelObject).getResultSet().getRelationRows();
32571                                        if(targetRelationRows.hasRelation()) {
32572                                                for(Relationship relationship: targetRelationRows.getHoldRelations()) {
32573                                                        if(relationship.getSources().contains(resultSetModel.getRelationRows())) {
32574                                                                find = true;
32575                                                                break;
32576                                                        }
32577                                                }
32578                                        }
32579                                }
32580                                else if (modelObject instanceof TableColumn) {
32581                                        TableRelationRows targetRelationRows = ((TableColumn) modelObject).getTable().getRelationRows();
32582                                        if(targetRelationRows.hasRelation()) {
32583                                                for(Relationship relationship: targetRelationRows.getHoldRelations()) {
32584                                                        if(relationship.getSources().contains(resultSetModel.getRelationRows())) {
32585                                                                find = true;
32586                                                                break;
32587                                                        }
32588                                                }
32589                                        }
32590                                }
32591                                
32592                                if (!find) {
32593                                        ImpactRelationship impactRelation = modelFactory.createImpactRelation();
32594                                        impactRelation.setEffectType(EffectType.select);
32595                                        impactRelation.addSource(new RelationRowsRelationshipElement<ResultSetRelationRows>(
32596                                                        resultSetModel.getRelationRows()));
32597                                        if (modelObject instanceof ResultColumn) {
32598                                                impactRelation.setTarget(new RelationRowsRelationshipElement<ResultSetRelationRows>(
32599                                                                ((ResultColumn) modelObject).getResultSet().getRelationRows()));
32600                                        } else if (modelObject instanceof TableColumn) {
32601                                                impactRelation.setTarget(new RelationRowsRelationshipElement<TableRelationRows>(
32602                                                                ((TableColumn) modelObject).getTable().getRelationRows()));
32603                                        }
32604                                }
32605                        }
32606                }
32607
32608        }
32609
32610        /**
32611         * Hive, Spark SQL and Databricks {@code named_struct('a', x, 'b', y)}: a STRUCT whose fields are named by the string
32612         * literals before their values, as BigQuery {@code STRUCT(x AS a, y AS b)} (Mantis 4847). False in any other
32613         * dialect, where a function of that name is a routine of the user's, for a qualified name ({@code db.named_struct},
32614         * a user function too), and for a call whose names are not all non-empty string literals.
32615         */
32616        private boolean isNamedStruct(TFunctionCall call) {
32617                EDbVendor vendor = option.getVendor();
32618                if (call == null || call.getFunctionName() == null
32619                                || !gudusoft.gsqlparser.nodes.structured.NestedAccessProfile.forVendor(vendor).hasNamedStructConstructor()
32620                                || !gudusoft.gsqlparser.nodes.structured.NestedAccessProfile.forVendor(vendor).buildsDeclaredTree()
32621                                || call.getFunctionName().getSchemaToken() != null || call.getFunctionName().getDatabaseToken() != null
32622                                || call.getArgs() == null || call.getArgs().size() < 2 || call.getArgs().size() % 2 != 0
32623                                || !"named_struct".equalsIgnoreCase(call.getFunctionName().toString())) { // non-identifier-compare: built-in function name
32624                        return false;
32625                }
32626                for (int i = 0; i < call.getArgs().size(); i += 2) {
32627                        TExpression name = call.getArgs().getExpression(i);
32628                        if (name == null || name.getExpressionType() != EExpressionType.simple_constant_t
32629                                        || name.getConstantOperand() == null
32630                                        || name.getConstantOperand().getLiteralType() != ELiteralType.etString
32631                                        || name.getConstantOperand().getValue() == null || name.getConstantOperand().getValue().isEmpty()) {
32632                                return false;
32633                        }
32634                }
32635                return true;
32636        }
32637
32638        /**
32639         * Plan section 4.4, the constructor registry: the fields of the STRUCT {@code call} builds, each {name, value} in
32640         * source order, the name null for a field the vendor leaves unnamed; null when {@code call} builds no STRUCT GSP
32641         * knows. A Hive family {@code named_struct('a', x)} names its fields by the literals, a STRUCT constructor
32642         * ({@code STRUCT(x AS a)}) by the alias or the column a field reads, and a Hive family {@code struct(x, y)} by the
32643         * vendor's rule ({@link #positionalStructFieldName}).
32644         */
32645        private List<Object[]> constructorFields(TFunctionCall call) {
32646                if (call == null) {
32647                        return null;
32648                }
32649                List<Object[]> fields = new ArrayList<Object[]>();
32650                if (isNamedStruct(call)) {
32651                        for (int i = 0; i + 1 < call.getArgs().size(); i += 2) {
32652                                fields.add(new Object[]{call.getArgs().getExpression(i).getConstantOperand().getValue(),
32653                                                call.getArgs().getExpression(i + 1)});
32654                        }
32655                        return fields;
32656                }
32657                if (call.getFunctionType() == EFunctionType.struct_t && call.getFieldValues() != null) {
32658                        for (int i = 0; i < call.getFieldValues().size(); i++) {
32659                                TResultColumn field = call.getFieldValues().getResultColumn(i);
32660                                String name = null;
32661                                TObjectName typed = call.getFieldDefs() != null && i < call.getFieldDefs().size()
32662                                                ? call.getFieldDefs().getColumn(i).getColumnName() : null;
32663                                if (typed != null) {
32664                                        // a typed constructor, STRUCT<a STRING>(s.x): the type names the field
32665                                        name = DlineageUtil.decodeColumnIdentifier(typed.getColumnNameOnly());
32666                                } else if (field.getAliasClause() != null && field.getAliasClause().getAliasName() != null) {
32667                                        name = DlineageUtil.decodeAliasIdentifier(field.getAliasClause().getAliasName().toString());
32668                                } else if (gudusoft.gsqlparser.nodes.structured.NestedAccessProfile.forVendor(option.getVendor())
32669                                                .hasNamedStructConstructor()) {
32670                                        name = positionalStructFieldName(field.getExpr(), i);
32671                                } else if (field.getFieldAttr() != null && field.getFieldAttr().getFieldPathRead() == null) {
32672                                        name = DlineageUtil.decodeColumnIdentifier(field.getFieldAttr().getColumnNameOnly());
32673                                }
32674                                fields.add(new Object[]{name == null || name.isEmpty() ? null : name, field.getExpr()});
32675                        }
32676                        return fields;
32677                }
32678                List<String> rowFields = rowCastFieldNames(call);
32679                if (rowFields != null) {
32680                        for (int i = 0; i < rowFields.size(); i++) {
32681                                fields.add(new Object[]{rowFields.get(i), call.getExpr1().getExprList().getExpression(i)});
32682                        }
32683                        return fields;
32684                }
32685                if (isPositionalStruct(call)) {
32686                        for (int i = 0; i < call.getArgs().size(); i++) {
32687                                fields.add(new Object[]{positionalStructFieldName(call.getArgs().getExpression(i), i),
32688                                                call.getArgs().getExpression(i)});
32689                        }
32690                        return fields;
32691                }
32692                return null;
32693        }
32694
32695        /**
32696         * Whether {@code call} is the Hive family {@code struct(x, y, ..)} function, unqualified, which builds a STRUCT
32697         * whose field names the vendor generates ({@link #positionalStructFieldName}). Databricks parses it as a STRUCT
32698         * constructor with no field list.
32699         */
32700        private boolean isPositionalStruct(TFunctionCall call) {
32701                EDbVendor vendor = option.getVendor();
32702                return call != null && call.getFunctionName() != null
32703                                && (call.getFunctionType() != EFunctionType.struct_t || call.getFieldValues() == null)
32704                                && gudusoft.gsqlparser.nodes.structured.NestedAccessProfile.forVendor(vendor).hasNamedStructConstructor()
32705                                && gudusoft.gsqlparser.nodes.structured.NestedAccessProfile.forVendor(vendor).buildsDeclaredTree()
32706                                && call.getFunctionName().getSchemaToken() == null && call.getFunctionName().getDatabaseToken() == null
32707                                && call.getArgs() != null && call.getArgs().size() > 0
32708                                && "struct".equalsIgnoreCase(call.getFunctionName().toString()); // non-identifier-compare: built-in function name
32709        }
32710
32711        /**
32712         * The name of field {@code index} of a {@link #isPositionalStruct struct(..)}: Hive's GenericUDFStruct names every
32713         * field {@code col1}, {@code col2}, ..; Spark SQL and Databricks name a field that reads a column after that column
32714         * (its last field for a field read), and any other field {@code colN} as Hive does.
32715         */
32716        private String positionalStructFieldName(TExpression value, int index) {
32717                EDbVendor vendor = option.getVendor();
32718                if ((vendor == EDbVendor.dbvsparksql || vendor == EDbVendor.dbvdatabricks) && value != null
32719                                && value.getExpressionType() == EExpressionType.simple_object_name_t && value.getObjectOperand() != null
32720                                && !DlineageUtil.isStarName(value.getObjectOperand())) {
32721                        String name = DlineageUtil.decodeColumnIdentifier(DlineageUtil.fieldLeafText(value.getObjectOperand()));
32722                        if (name != null && !name.isEmpty()) {
32723                                return name;
32724                        }
32725                }
32726                return "col" + (index + 1);
32727        }
32728
32729        /** Whether a field of the STRUCT {@code call} builds is itself built by a constructor (Mantis 4892). */
32730        private boolean hasNestedConstructor(TFunctionCall call) {
32731                List<Object[]> fields = constructorFields(call);
32732                if (fields == null) {
32733                        return false;
32734                }
32735                for (Object[] field : fields) {
32736                        TExpression value = (TExpression) field[1];
32737                        if (value != null && value.getExpressionType() == EExpressionType.function_t
32738                                        && constructorFields(value.getFunctionCall()) != null) {
32739                                return true;
32740                        }
32741                }
32742                return false;
32743        }
32744
32745        /**
32746         * The leaves of the STRUCT {@code call} builds, each as {path below the struct, value}, in source order: a field
32747         * whose value is a constructor contributes its own leaves below its name (Mantis 4892). The recursion follows the
32748         * nesting of the constructors written, not an expression chain.
32749         *
32750         * @return whether every field has a name: an unnamed one has no leaf
32751         */
32752        private boolean constructorLeaves(TFunctionCall call, List<String> prefix, List<Object[]> leaves) {
32753                boolean complete = true;
32754                for (Object[] field : constructorFields(call)) {
32755                        TExpression value = (TExpression) field[1];
32756                        if (field[0] == null || value == null) {
32757                                complete = false;
32758                                continue;
32759                        }
32760                        List<String> path = new ArrayList<String>(prefix);
32761                        path.add((String) field[0]);
32762                        TFunctionCall nested = value.getExpressionType() == EExpressionType.function_t ? value.getFunctionCall() : null;
32763                        if (nested != null && constructorFields(nested) != null) {
32764                                complete &= constructorLeaves(nested, path, leaves);
32765                        } else {
32766                                leaves.add(new Object[]{path, value});
32767                        }
32768                }
32769                return complete;
32770        }
32771
32772        /** The function node of a {@link #isNamedStruct named_struct}: a column per field, fed by its value (Mantis 4847). */
32773        private Function createNamedStructFunction(TFunctionCall call) {
32774                Function function = modelFactory.createFunction(call);
32775                for (int i = 0; i + 1 < call.getArgs().size(); i += 2) {
32776                        TObjectName field = new TObjectName();
32777                        field.setString(call.getArgs().getExpression(i).getConstantOperand().getValue());
32778                        ResultColumn column = modelFactory.createFunctionResultColumn(function, field);
32779                        columnsInExpr visitor = new columnsInExpr();
32780                        call.getArgs().getExpression(i + 1).inOrderTraverse(visitor);
32781                        List<TParseTreeNode> functions = visitor.getFunctions();
32782                        if (functions != null && !functions.isEmpty()) {
32783                                analyzeFunctionDataFlowRelation(column, functions, EffectType.function);
32784                        }
32785                        List<TSelectSqlStatement> subquerys = visitor.getSubquerys();
32786                        if (subquerys != null && !subquerys.isEmpty()) {
32787                                analyzeSubqueryDataFlowRelation(column, subquerys, EffectType.function);
32788                        }
32789                        analyzeDataFlowRelation(column, visitor.getObjectNames(), EffectType.function, functions);
32790                        analyzeConstantDataFlowRelation(column, visitor.getConstants(), EffectType.function, functions);
32791                }
32792                return function;
32793        }
32794
32795    private Object createFunction(TParseTreeNode functionCall) {
32796        if (!processingFunctions.add(functionCall)) {
32797            return modelManager.getModel(functionCall);
32798        }
32799        try {
32800
32801            if (functionCall instanceof TFunctionCall) {
32802                TFunctionCall functionObj = (TFunctionCall) functionCall;
32803                // Generic structured-dataflow dispatch. When a vendor adapter (e.g.
32804                // Spark from_json+explode) returns a descriptor, model the function
32805                // as a structured generator: per-field result columns linked to
32806                // exact structured source paths such as nodes[*].key. When no
32807                // adapter matches, fall through to the existing function logic
32808                // unchanged.
32809                StructuredAdapterContext sctx = new StructuredAdapterContext(option.getVendor());
32810                StructuredDataflowDescriptor sdescriptor =
32811                        StructuredDataflowRegistry.defaultRegistry().describe(functionObj, sctx);
32812                if (sdescriptor != null) {
32813                    Function structuredFn = createStructuredDataflowFunction(sdescriptor);
32814                    if (structuredFn != null) {
32815                        return structuredFn;
32816                    }
32817                }
32818
32819                // Oracle basic statements own their call relationship and raw-name binding.
32820                // Re-entering the legacy lookup here can attach an unresolved/ambiguous
32821                // call to an unrelated same-named member (and emit it a second time).
32822                if (!isBuiltInFunctionName(functionObj.getFunctionName())
32823                        && !isOracleBasicCall(functionObj)) {
32824                    TCustomSqlStatement stmt = stmtStack.peek();
32825                    String procedureParent = getProcedureParentName(stmt);
32826                    if (procedureParent != null) {
32827                        Procedure procedureCallee = modelManager.getProcedure(callAliasKey(functionObj));
32828                        if (gudusoft.gsqlparser.dlineage.routine.RoutineShadow.enabled()) shadowCallee("createFunction", functionObj.getFunctionName(), procedureCallee, new Integer[] {gudusoft.gsqlparser.dlineage.routine.RoutineShadow.countAliasOf(functionObj)}, gudusoft.gsqlparser.catalog.runtime.CatalogObjectKind.FUNCTION);
32829                        if (procedureCallee != null) {
32830                            if (procedureParent != null) {
32831                                Procedure caller = getEnclosingProcedure(stmt);
32832                                if (caller != null) {
32833                                    CallRelationship callRelation = modelFactory.createCallRelation();
32834                                    callRelation.setCallObject(functionCall);
32835                                    callRelation.setTarget(new ProcedureRelationshipElement(caller));
32836                                    callRelation.addSource(new ProcedureRelationshipElement(procedureCallee));
32837                                    if (isBuiltInFunctionName(functionObj.getFunctionName()) || isKeyword(functionObj.getFunctionName())) {
32838                                        callRelation.setBuiltIn(true);
32839                                    }
32840                                }
32841                            }
32842                            if (procedureCallee.getArguments() != null) {
32843                                for (int j = 0; j < procedureCallee.getArguments().size(); j++) {
32844                                    Argument argument = procedureCallee.getArguments().get(j);
32845                                    Variable variable = modelFactory.createVariable(procedureCallee, argument.getName(), false);
32846                                    if (variable != null) {
32847                                        if (argument.getMode() == EParameterMode.out || argument.getMode() == EParameterMode.output) {
32848                                            Transform transform = new Transform();
32849                                            transform.setType(Transform.FUNCTION);
32850                                            transform.setCode(functionObj);
32851                                            compositeBindingColumn(variable).setTransform(transform);
32852                                        }
32853                                        Process process = modelFactory.createProcess(functionObj);
32854                                        variable.addProcess(process);
32855                                        analyzeFunctionArgumentsDataFlowRelation(compositeBindingColumn(variable), functionObj, j, process);
32856                                    }
32857                                }
32858                            }
32859                        } else {
32860                            TFunctionCall call = (TFunctionCall) functionCall;
32861                            String functionName = call.getFunctionName().toString();
32862                            List<String> functionNameParts = SQLUtil.parseNames(functionName, getOption().getVendor());
32863                            if (functionNameParts.size() == 2) {
32864                                // The record a rec.method() call names; else an object of that name. The name is
32865                                // split as a qualified name, so a delimited part may contain a dot.
32866                                String qualifier = functionNameParts.get(0);
32867                                Table functionTable = modelManager.visibleLocal(qualifier, LocalSymbolKey.Kind.VARIABLE);
32868                                if (functionTable == null) {
32869                                    functionTable = modelManager.getTableByName(DlineageUtil.getTableIdentityKey(qualifier));
32870                                }
32871                                if (functionTable != null) {
32872                                    String columnName = functionNameParts.get(1);
32873                                    for (TableColumn tableColumn : functionTable.getColumns()) {
32874                                        if (DlineageUtil.sameName(ESQLDataObjectType.dotColumn, DlineageUtil.columnSegmentOf(tableColumn.getName()), columnName)) {
32875                                            return functionTable;
32876                                        }
32877                                    }
32878                                }
32879                            }
32880                            Function function = modelFactory.createFunction(call);
32881                            if (procedureParent != null) {
32882                                Procedure caller = getEnclosingProcedure(stmt);
32883                                if (caller != null) {
32884                                    CallRelationship callRelation = modelFactory.createCallRelation();
32885                                    callRelation.setCallObject(functionCall);
32886                                    callRelation.setTarget(new ProcedureRelationshipElement(caller));
32887                                    callRelation.addSource(new FunctionRelationshipElement(function));
32888                                    if (isBuiltInFunctionName(functionObj.getFunctionName()) || isKeyword(functionObj.getFunctionName())) {
32889                                        callRelation.setBuiltIn(true);
32890                                    }
32891                                }
32892                            }
32893                        }
32894                    }
32895                } else if (isConstantFunction(functionObj.getFunctionName())
32896                        && (functionObj.getArgs() == null || functionObj.getArgs().size() == 0)) {
32897                    if (option.isShowConstantTable()) {
32898                        Table constantTable = modelFactory.createConstantsTable(stmtStack.peek());
32899                        modelFactory.createTableColumn(constantTable, functionObj);
32900                        return constantTable;
32901                    } else {
32902                        return null;
32903                    }
32904                }
32905
32906                if (functionObj.getFunctionType() == EFunctionType.struct_t) {
32907                    Function function = modelFactory.createFunction((TFunctionCall) functionCall);
32908                    if (functionObj instanceof TTableFunction) {
32909                        TTableFunction tableFunction = (TTableFunction) functionObj;
32910                        if (tableFunction.getFieldValues() != null) {
32911                            for (int i = 0; i < tableFunction.getFieldValues().size(); i++) {
32912                                TResultColumn resultColumn = tableFunction.getFieldValues().getResultColumn(i);
32913                                // a typed constructor, STRUCT<a STRING>(s.x), names its field by the type (owner
32914                                // ruling 2026-10-07)
32915                                TObjectName typedField = tableFunction.getFieldDefs() != null
32916                                        && i < tableFunction.getFieldDefs().size()
32917                                        ? tableFunction.getFieldDefs().getColumn(i).getColumnName() : null;
32918                                if (typedField != null || resultColumn.getAliasClause() != null) {
32919                                    ResultColumn column = modelFactory.createFunctionResultColumn(function,
32920                                            typedField != null ? typedField : resultColumn.getAliasClause().getAliasName());
32921                                    columnsInExpr visitor = new columnsInExpr();
32922                                    resultColumn.getExpr().inOrderTraverse(visitor);
32923                                    List<TObjectName> objectNames = visitor.getObjectNames();
32924                                    List<TParseTreeNode> functions = visitor.getFunctions();
32925                                    if (functions != null && !functions.isEmpty()) {
32926                                        analyzeFunctionDataFlowRelation(column, functions, EffectType.function);
32927                                    }
32928                                    List<TSelectSqlStatement> subquerys = visitor.getSubquerys();
32929                                    if (subquerys != null && !subquerys.isEmpty()) {
32930                                        analyzeSubqueryDataFlowRelation(column, subquerys, EffectType.function);
32931                                    }
32932                                    analyzeDataFlowRelation(column, objectNames, EffectType.function, functions);
32933                                    List<TParseTreeNode> constants = visitor.getConstants();
32934                                    analyzeConstantDataFlowRelation(column, constants, EffectType.function, functions);
32935                                } else if (resultColumn.getFieldAttr() != null) {
32936                                    ResultColumn column = modelFactory.createFunctionResultColumn(function,
32937                                            resultColumn.getFieldAttr());
32938                                    analyzeDataFlowRelation(column, Arrays.asList(resultColumn.getFieldAttr()), EffectType.function, null);
32939                                } else if (resultColumn.getExpr() != null) {
32940                                    if (resultColumn.getExpr().getFunctionCall() != null) {
32941                                        Function resultColumnFunction = (Function) createFunction(
32942                                                resultColumn.getExpr().getFunctionCall());
32943                                        String functionName = getResultSetName(function);
32944                                        for (int j = 0; j < resultColumnFunction.getColumns().size(); j++) {
32945                                            TObjectName columnName = new TObjectName();
32946                                            if (resultColumn.getAliasClause() != null) {
32947                                                columnName.setString(resultColumn.getAliasClause() + "." + getColumnNameOnly(
32948                                                        resultColumnFunction.getColumns().get(j).getName()));
32949                                            } else {
32950                                                columnName.setString(functionName + "." + getColumnNameOnly(
32951                                                        resultColumnFunction.getColumns().get(j).getName()));
32952                                            }
32953                                            ResultColumn functionResultColumn = modelFactory.createResultColumn(function,
32954                                                    columnName);
32955                                            DataFlowRelationship relationship = modelFactory.createDataFlowRelation();
32956                                            relationship.setTarget(new ResultColumnRelationshipElement(functionResultColumn));
32957                                            relationship.addSource(new ResultColumnRelationshipElement(
32958                                                    resultColumnFunction.getColumns().get(j)));
32959                                        }
32960                                    } else if (resultColumn.getExpr().getCaseExpression() != null) {
32961                                        function = modelFactory.createFunction(resultColumn.getExpr().getCaseExpression());
32962                                        ResultColumn column = modelFactory.createFunctionResultColumn(function,
32963                                                ((TCaseExpression) resultColumn.getExpr().getCaseExpression()).getWhenClauseItemList());
32964                                        analyzeFunctionArgumentsDataFlowRelation(column, functionCall);
32965                                    }
32966                                }
32967                            }
32968                            return function;
32969                        }
32970                    } else if (functionObj instanceof TFunctionCall) {
32971
32972                    }
32973                }
32974
32975                // BigQuery array_agg(table_alias) row-reference expansion
32976                if (functionObj.getFunctionType() == EFunctionType.array_agg_t
32977                        && option.getVendor() == EDbVendor.dbvbigquery
32978                        && functionObj.getArgs() != null
32979                        && functionObj.getArgs().size() == 1) {
32980
32981                    TExpression arg0 = functionObj.getArgs().getExpression(0);
32982                    if (arg0 != null
32983                            && arg0.getExpressionType() == EExpressionType.simple_object_name_t) {
32984
32985                        TObjectName on = arg0.getObjectOperand();
32986                        if (isArrayAggRowReference(on)) {
32987                            Function function = (Function) modelManager.getModel(functionCall);
32988                            if (function == null) {
32989                                function = modelFactory.createFunction((TFunctionCall) functionCall);
32990                            }
32991
32992                            // Only bind once (avoid duplicate processing when
32993                            // createFunction is called from analyzeFunctionDataFlowRelation)
32994                            if (function.getColumns() == null || function.getColumns().isEmpty()) {
32995                                TTable srcTable = on.getSourceTable();
32996                                String tableAlias = srcTable.getAliasName() != null
32997                                        ? srcTable.getAliasName().toString() : null;
32998
32999                                Table sourceTable = (Table) modelManager.getModel(srcTable);
33000                                sourceTable.removeColumn(tableAlias);
33001
33002                                // Collect inferred column names from ORDER BY (in function)
33003                                // and GROUP BY (in enclosing SELECT). Excludes the table alias.
33004                                List<TObjectName> inferredColumns = collectRowReferenceInferredColumns(
33005                                        functionObj, tableAlias);
33006
33007                                // Create a single * function result column carrying star-link
33008                                // metadata for the inferred columns and direct source edges
33009                                // for * and each inferred column.
33010                                createRowReferenceStarColumn(function, srcTable, inferredColumns);
33011                            }
33012
33013                            return function;
33014                        }
33015                    }
33016                }
33017
33018                if (functionObj.getFunctionType() == EFunctionType.array_t || functionObj.getFunctionType() == EFunctionType.array_agg_t) {
33019                    Function function = modelFactory.createFunction((TFunctionCall) functionCall);
33020                    if (functionObj.getArgs() != null) {
33021                        if (functionObj.getArgs().getExpression(0).getSubQuery() != null) {
33022                            TSelectSqlStatement stmt = functionObj.getArgs().getExpression(0).getSubQuery();
33023                            analyzeSelectStmt(stmt);
33024                            ResultSet resultset = (ResultSet) modelManager.getModel(stmt);
33025                            for (int i = 0; i < resultset.getColumns().size(); i++) {
33026                                ResultColumn sourceColumn = resultset.getColumns().get(i);
33027                                TObjectName columnName = new TObjectName();
33028                                columnName.setString(sourceColumn.getName());
33029                                ResultColumn resultColumn = modelFactory.createFunctionResultColumn(function,
33030                                        columnName);
33031                                DataFlowRelationship relationship = modelFactory.createDataFlowRelation();
33032                                relationship.setTarget(new ResultColumnRelationshipElement(resultColumn));
33033                                relationship.addSource(
33034                                        new ResultColumnRelationshipElement(sourceColumn));
33035                            }
33036                            return function;
33037                        } else if (functionObj.getArgs().getExpression(0).getExpressionType() == EExpressionType.function_t) {
33038                            Object functionTableModelObj = createFunction(functionObj.getArgs().getExpression(0).getFunctionCall());
33039                            if (functionTableModelObj instanceof ResultSet) {
33040                                ResultSet resultset = (ResultSet) functionTableModelObj;
33041                                for (int i = 0; i < resultset.getColumns().size(); i++) {
33042                                    ResultColumn sourceColumn = resultset.getColumns().get(i);
33043                                    TObjectName columnName = new TObjectName();
33044                                    columnName.setString(sourceColumn.getName());
33045                                    ResultColumn resultColumn = modelFactory.createFunctionResultColumn(function, columnName);
33046                                    DataFlowRelationship relationship = modelFactory.createDataFlowRelation();
33047                                    relationship.setTarget(new ResultColumnRelationshipElement(resultColumn));
33048                                    relationship.addSource(new ResultColumnRelationshipElement(sourceColumn));
33049                                }
33050                                return function;
33051                            }
33052                        }
33053                    }
33054                }
33055
33056                Function function = modelFactory.createFunction((TFunctionCall) functionCall);
33057                ResultColumn column = modelFactory.createFunctionResultColumn(function,
33058                        ((TFunctionCall) functionCall).getFunctionName());
33059                if ("COUNT".equalsIgnoreCase(builtInFunctionName((TFunctionCall) functionCall))) { // non-identifier-compare: built-in function name
33060                    // @see https://e.gitee.com/gudusoft/issues/list?issue=I40NUP
33061                    // COUNT特殊处理,不和参数关联
33062                    if (option.isShowCountTableColumn()) {
33063                        analyzeFunctionArgumentsDataFlowRelation(column, functionCall);
33064                    }
33065                } else {
33066                    boolean isCustomFunction = !isOracleBasicCall((TFunctionCall) functionCall)
33067                            && analyzeCustomFunctionCall((TFunctionCall) functionCall);
33068//                              if(!isCustomFunction) 
33069                    {
33070                        analyzeFunctionArgumentsDataFlowRelation(column, functionCall);
33071                    }
33072                    Set<Object> functionTableModelObjs = modelManager.getFunctionResults(functionAliasKey(function));
33073                    if (functionTableModelObjs != null) {
33074                        for (Object functionTableModelObj : functionTableModelObjs) {
33075                            if (functionTableModelObj instanceof ResultSet) {
33076                                ResultSet functionTableModel = (ResultSet) functionTableModelObj;
33077                                if (functionTableModel.getColumns() != null) {
33078                                    for (int j = 0; j < functionTableModel.getColumns().size(); j++) {
33079                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
33080                                        relation.setEffectType(EffectType.select);
33081                                        relation.setTarget(new ResultColumnRelationshipElement(column));
33082                                        relation.addSource(new ResultColumnRelationshipElement(
33083                                                functionTableModel.getColumns().get(j)));
33084                                    }
33085                                }
33086                            }
33087                        }
33088                    }
33089                }
33090                return function;
33091            } else if (functionCall instanceof TCaseExpression) {
33092                Function function = modelFactory.createFunction((TCaseExpression) functionCall);
33093                ResultColumn column = modelFactory.createFunctionResultColumn(function,
33094                        ((TCaseExpression) functionCall).getWhenClauseItemList());
33095                analyzeFunctionArgumentsDataFlowRelation(column, functionCall);
33096                return function;
33097            } else if (functionCall instanceof TObjectName) {
33098                Function function = modelFactory.createFunction((TObjectName) functionCall);
33099                TObjectName columnName = new TObjectName();
33100                columnName.setString(function.getFunctionName());
33101                ResultColumn column = modelFactory.createResultColumn(function,
33102                        columnName);
33103                analyzeFunctionArgumentsDataFlowRelation(column, functionCall);
33104                return function;
33105            }
33106            return null;
33107        } finally {
33108            processingFunctions.remove(functionCall);
33109        }
33110    }
33111
33112        /**
33113         * The legacy normal-name text of a function model's name.
33114         *
33115         * @deprecated function results are looked up by the function's name as written; nothing here calls this any more,
33116         *             and it stays for subclasses
33117         */
33118        @Deprecated
33119        protected String getIdentifiedFunctionName(Function function) {
33120                return LegacyDlineageWireNamesV1.functionName(function.getFunctionName());
33121        }
33122        
33123        private boolean isConstantFunction(TObjectName functionName) {
33124                boolean result = CONSTANT_BUILTIN_FUNCTIONS.contains(functionName.toString().toUpperCase(Locale.ROOT));
33125                if (result) {
33126                        return true;
33127                }
33128                return false;
33129        }
33130
33131        private void analyzeFunctionArgumentsDataFlowRelation(Object resultColumn, TParseTreeNode gspObject) {
33132                List<TExpression> directExpressions = new ArrayList<TExpression>();
33133                List<TExpression> indirectExpressions = new ArrayList<TExpression>();
33134                List<TExpression> conditionExpressions = new ArrayList<TExpression>();
33135                if (gspObject instanceof TFunctionCall) {
33136                        TFunctionCall functionCall = (TFunctionCall) gspObject;
33137                        getFunctionExpressions(directExpressions, indirectExpressions, functionCall);
33138                } else if (gspObject instanceof TCaseExpression) {
33139                        TCaseExpression expr = (TCaseExpression) gspObject;
33140                        TExpression inputExpr = expr.getInput_expr();
33141                        if (inputExpr != null) {
33142                                if(option.isShowCaseWhenAsDirect()){
33143                                        directExpressions.add(inputExpr);
33144                                }
33145                                else {
33146                                        conditionExpressions.add(inputExpr);
33147                                }
33148                        }
33149                        TExpression defaultExpr = expr.getElse_expr();
33150                        if (defaultExpr != null) {
33151                                directExpressions.add(defaultExpr);
33152                        }
33153                        TWhenClauseItemList list = expr.getWhenClauseItemList();
33154                        for (int i = 0; i < list.size(); i++) {
33155                                TWhenClauseItem element = list.getWhenClauseItem(i);
33156                                if(option.isShowCaseWhenAsDirect()){
33157                                        directExpressions.add(element.getComparison_expr());
33158                                }
33159                                else {
33160                                        conditionExpressions.add(element.getComparison_expr());
33161                                }
33162                                directExpressions.add(element.getReturn_expr());
33163                        }
33164                }
33165
33166                for (int j = 0; j < directExpressions.size(); j++) {
33167                        columnsInExpr visitor = new columnsInExpr();
33168                        directExpressions.get(j).inOrderTraverse(visitor);
33169
33170                        List<TObjectName> objectNames = visitor.getObjectNames();
33171                        List<TParseTreeNode> functions = visitor.getFunctions();
33172
33173                        if (functions != null && !functions.isEmpty()) {
33174                                analyzeFunctionDataFlowRelation(resultColumn, functions, EffectType.function);
33175                        }
33176
33177                        List<TSelectSqlStatement> subquerys = visitor.getSubquerys();
33178                        if (subquerys != null && !subquerys.isEmpty()) {
33179                                analyzeSubqueryDataFlowRelation(resultColumn, subquerys, EffectType.function);
33180                        }
33181
33182                        analyzeDataFlowRelation(resultColumn, objectNames, EffectType.function, functions);
33183
33184                        List<TParseTreeNode> constants = visitor.getConstants();
33185                        analyzeConstantDataFlowRelation(resultColumn, constants, EffectType.function, functions);
33186                }
33187
33188                conditionExpressions.addAll(indirectExpressions);
33189                for (int j = 0; j < conditionExpressions.size(); j++) {
33190                        analyzeFilterCondition(resultColumn, conditionExpressions.get(j), null, null, EffectType.function);
33191                }
33192        }
33193        
33194        private void analyzeFunctionArgumentsDataFlowRelation(Object resultColumn, TCallStatement callStatment, int argumentIndex, Process process) {
33195                List<TExpression> directExpressions = new ArrayList<TExpression>();
33196                List<TExpression> indirectExpressions = new ArrayList<TExpression>();
33197                List<TExpression> conditionExpressions = new ArrayList<TExpression>();
33198
33199                getFunctionExpressions(directExpressions, indirectExpressions, callStatment, argumentIndex);
33200
33201                for (int j = 0; j < directExpressions.size(); j++) {
33202                        columnsInExpr visitor = new columnsInExpr();
33203                        directExpressions.get(j).inOrderTraverse(visitor);
33204
33205                        List<TObjectName> objectNames = visitor.getObjectNames();
33206                        List<TParseTreeNode> functions = visitor.getFunctions();
33207
33208                        if (functions != null && !functions.isEmpty()) {
33209                                analyzeFunctionDataFlowRelation(resultColumn, functions, EffectType.function);
33210                        }
33211
33212                        List<TSelectSqlStatement> subquerys = visitor.getSubquerys();
33213                        if (subquerys != null && !subquerys.isEmpty()) {
33214                                analyzeSubqueryDataFlowRelation(resultColumn, subquerys, EffectType.function);
33215                        }
33216
33217                        DataFlowRelationship relation = analyzeDataFlowRelation(resultColumn, objectNames, EffectType.function, functions);
33218                        if (relation != null) {
33219                                relation.setProcess(process);
33220                        }
33221
33222                        List<TParseTreeNode> constants = visitor.getConstants();
33223                        analyzeConstantDataFlowRelation(resultColumn, constants, EffectType.function, functions);
33224                }
33225
33226                conditionExpressions.addAll(indirectExpressions);
33227                for (int j = 0; j < conditionExpressions.size(); j++) {
33228                        analyzeFilterCondition(resultColumn, conditionExpressions.get(j), null, null, EffectType.function);
33229                }
33230        }
33231
33232        private void analyzeFunctionArgumentsDataFlowRelation(Object resultColumn, TDb2CallStmt callStatment, int argumentIndex, Process process) {
33233                List<TExpression> directExpressions = new ArrayList<TExpression>();
33234                List<TExpression> indirectExpressions = new ArrayList<TExpression>();
33235                List<TExpression> conditionExpressions = new ArrayList<TExpression>();
33236
33237                getFunctionExpressions(directExpressions, indirectExpressions, callStatment, argumentIndex);
33238
33239                for (int j = 0; j < directExpressions.size(); j++) {
33240                        columnsInExpr visitor = new columnsInExpr();
33241                        directExpressions.get(j).inOrderTraverse(visitor);
33242
33243                        List<TObjectName> objectNames = visitor.getObjectNames();
33244                        List<TParseTreeNode> functions = visitor.getFunctions();
33245
33246                        if (functions != null && !functions.isEmpty()) {
33247                                analyzeFunctionDataFlowRelation(resultColumn, functions, EffectType.function);
33248                        }
33249
33250                        List<TSelectSqlStatement> subquerys = visitor.getSubquerys();
33251                        if (subquerys != null && !subquerys.isEmpty()) {
33252                                analyzeSubqueryDataFlowRelation(resultColumn, subquerys, EffectType.function);
33253                        }
33254
33255                        DataFlowRelationship relation = analyzeDataFlowRelation(resultColumn, objectNames, EffectType.function, functions);
33256                        if (relation != null) {
33257                                relation.setProcess(process);
33258                        }
33259
33260                        List<TParseTreeNode> constants = visitor.getConstants();
33261                        analyzeConstantDataFlowRelation(resultColumn, constants, EffectType.function, functions);
33262                }
33263
33264                conditionExpressions.addAll(indirectExpressions);
33265                for (int j = 0; j < conditionExpressions.size(); j++) {
33266                        analyzeFilterCondition(resultColumn, conditionExpressions.get(j), null, null, EffectType.function);
33267                }
33268        }
33269
33270        private void analyzeFunctionArgumentsDataFlowRelation(Object resultColumn, TFunctionCall functionCall, int argumentIndex, Process process) {
33271                List<TExpression> directExpressions = new ArrayList<TExpression>();
33272                List<TExpression> indirectExpressions = new ArrayList<TExpression>();
33273                List<TExpression> conditionExpressions = new ArrayList<TExpression>();
33274
33275                getFunctionExpressions(directExpressions, indirectExpressions, functionCall, argumentIndex);
33276
33277                for (int j = 0; j < directExpressions.size(); j++) {
33278                        columnsInExpr visitor = new columnsInExpr();
33279                        TExpression argument = directExpressions.get(j);
33280                        if (option.getVendor() == EDbVendor.dbvoracle
33281                                        && argument.getExpressionType() == EExpressionType.ref_arrow_t) {
33282                                // In formal => value, the left name labels a parameter; only value flows.
33283                                argument = argument.getRightOperand();
33284                        }
33285                        if (argument == null) continue;
33286                        argument.inOrderTraverse(visitor);
33287
33288                        List<TObjectName> objectNames = visitor.getObjectNames();
33289                        List<TParseTreeNode> functions = visitor.getFunctions();
33290
33291                        if (functions != null && !functions.isEmpty()) {
33292                                analyzeFunctionDataFlowRelation(resultColumn, functions, EffectType.function);
33293                        }
33294
33295                        List<TSelectSqlStatement> subquerys = visitor.getSubquerys();
33296                        if (subquerys != null && !subquerys.isEmpty()) {
33297                                analyzeSubqueryDataFlowRelation(resultColumn, subquerys, EffectType.function);
33298                        }
33299
33300                        DataFlowRelationship relation = analyzeDataFlowRelation(resultColumn, objectNames, EffectType.function, functions);
33301                        if (relation != null) {
33302                                relation.setProcess(process);
33303                        }
33304
33305                        List<TParseTreeNode> constants = visitor.getConstants();
33306                        analyzeConstantDataFlowRelation(resultColumn, constants, EffectType.function, functions);
33307                }
33308
33309                conditionExpressions.addAll(indirectExpressions);
33310                for (int j = 0; j < conditionExpressions.size(); j++) {
33311                        analyzeFilterCondition(resultColumn, conditionExpressions.get(j), null, null, EffectType.function);
33312                }
33313        }
33314
33315        private void analyzeFunctionArgumentsDataFlowRelation(Object resultColumn, TMssqlExecute functionCall, String argumentName, int argumentIndex, Process process) {
33316                List<TExpression> directExpressions = new ArrayList<TExpression>();
33317                List<TExpression> indirectExpressions = new ArrayList<TExpression>();
33318                List<TExpression> conditionExpressions = new ArrayList<TExpression>();
33319
33320                getFunctionExpressions(directExpressions, indirectExpressions, functionCall, argumentName, argumentIndex);
33321
33322                for (int j = 0; j < directExpressions.size(); j++) {
33323                        columnsInExpr visitor = new columnsInExpr();
33324                        directExpressions.get(j).inOrderTraverse(visitor);
33325
33326                        List<TObjectName> objectNames = visitor.getObjectNames();
33327                        List<TParseTreeNode> functions = visitor.getFunctions();
33328
33329                        if (functions != null && !functions.isEmpty()) {
33330                                analyzeFunctionDataFlowRelation(resultColumn, functions, EffectType.function);
33331                        }
33332
33333                        List<TSelectSqlStatement> subquerys = visitor.getSubquerys();
33334                        if (subquerys != null && !subquerys.isEmpty()) {
33335                                analyzeSubqueryDataFlowRelation(resultColumn, subquerys, EffectType.function);
33336                        }
33337
33338                        DataFlowRelationship relation = analyzeDataFlowRelation(resultColumn, objectNames, EffectType.function, functions);
33339                        if (relation != null) {
33340                                relation.setProcess(process);
33341                        }
33342
33343                        List<TParseTreeNode> constants = visitor.getConstants();
33344                        analyzeConstantDataFlowRelation(resultColumn, constants, EffectType.function, functions);
33345                }
33346
33347                conditionExpressions.addAll(indirectExpressions);
33348                for (int j = 0; j < conditionExpressions.size(); j++) {
33349                        analyzeFilterCondition(resultColumn, conditionExpressions.get(j), null, null, EffectType.function);
33350                }
33351        }
33352
33353
33354        private static void addResultColumnListExpressions(
33355                        TResultColumnList list, List<TExpression> target) {
33356                if (list == null) return;
33357                for (int k = 0; k < list.size(); k++) {
33358                        TExpression e = list.getResultColumn(k).getExpr();
33359                        if (e != null) target.add(e);
33360                }
33361        }
33362
33363        private static void addPassingClauseExpressions(
33364                        TFunctionCall fc, List<TExpression> target) {
33365                TResultColumnList list = null;
33366                if (fc.getXmlPassingClause() != null
33367                                && fc.getXmlPassingClause().getPassingList() != null) {
33368                        list = fc.getXmlPassingClause().getPassingList();
33369                } else if (fc.getPassingClause() != null
33370                                && fc.getPassingClause().getPassingList() != null) {
33371                        list = fc.getPassingClause().getPassingList();
33372                }
33373                addResultColumnListExpressions(list, target);
33374        }
33375
33376        private void collectOracleXmlFunctionExpressions(
33377                        TFunctionCall fc,
33378                        List<TExpression> direct,
33379                        List<TExpression> indirect) {
33380
33381                if (option.getVendor() != EDbVendor.dbvoracle) return;
33382
33383                // XMLELEMENT: value exprs + XMLATTRIBUTES exprs (direct)
33384                addResultColumnListExpressions(fc.getXMLElementValueExprList(), direct);
33385                if (fc.getXMLAttributesClause() != null) {
33386                        addResultColumnListExpressions(
33387                                        fc.getXMLAttributesClause().getValueExprList(), direct);
33388                }
33389
33390                // XMLFOREST (direct)
33391                addResultColumnListExpressions(fc.getXMLForestValueList(), direct);
33392
33393                // EXTRACT(XML) — AST-shape disambiguation (V3)
33394                // XML form populates getXMLType_Instance(); scalar EXTRACT(YEAR FROM d) does not.
33395                if (fc.getXMLType_Instance() != null) {
33396                        direct.add(fc.getXMLType_Instance());
33397                }
33398
33399                String name = fc.getFunctionName() == null
33400                                ? "" : fc.getFunctionName().toString().toUpperCase(Locale.ROOT);
33401
33402                // XMLEXISTS indirect (filter) from PASSING expressions
33403                if ("XMLEXISTS".equals(name)) {
33404                        addPassingClauseExpressions(fc, indirect);
33405                }
33406
33407                // XMLAGG / SYS_XMLAGG ORDER BY → indirect
33408                if (("XMLAGG".equals(name) || "SYS_XMLAGG".equals(name))
33409                                && fc.getSortClause() != null) {
33410                        TOrderByItemList orderByList = fc.getSortClause().getItems();
33411                        if (orderByList != null) {
33412                                for (int k = 0; k < orderByList.size(); k++) {
33413                                        TExpression e = orderByList.getOrderByItem(k).getSortKey();
33414                                        if (e != null) indirect.add(e);
33415                                }
33416                        }
33417                }
33418
33419                // DELIBERATELY NOT HARVESTED (metadata, not data):
33420                //   fc.getTypeExpression()       — target type for XMLCAST
33421                //   fc.getXMLElementNameExpr()   — element tag identifier
33422                //   XPath / XQuery literal strings that appear as function args
33423                //   datatype / format / style tokens
33424        }
33425
33426        private void collectArrayAggOrderByExpressions(
33427                        TFunctionCall fc,
33428                        List<TExpression> direct,
33429                        List<TExpression> indirect) {
33430                if (fc.getFunctionType() != EFunctionType.array_agg_t) return;
33431
33432                TOrderByItemList items = null;
33433                if (fc.getSortClause() != null) items = fc.getSortClause().getItems();
33434                if (items == null || items.size() == 0) {
33435                        items = fc.getOrderByList();
33436                }
33437                if (items == null) return;
33438
33439                // Resolve source tables for ORDER BY columns that the parser
33440                // doesn't link (they're inside a function call, outside the
33441                // resolver's normal scope)
33442                TTable fallbackTable = null;
33443                if (!stmtStack.isEmpty() && stmtStack.peek() instanceof TSelectSqlStatement) {
33444                        TTableList tables = ((TSelectSqlStatement) stmtStack.peek()).tables;
33445                        if (tables != null && tables.size() > 0) {
33446                                fallbackTable = tables.getTable(0);
33447                        }
33448                }
33449
33450                for (int k = 0; k < items.size(); k++) {
33451                        TExpression e = items.getOrderByItem(k).getSortKey();
33452                        if (e != null) {
33453                                if (fallbackTable != null
33454                                                && e.getExpressionType() == EExpressionType.simple_object_name_t
33455                                                && e.getObjectOperand() != null
33456                                                && e.getObjectOperand().getSourceTable() == null) {
33457                                        SqllrObservations decided = SqllrObservations.current();
33458                                        if (decided != null) {
33459                                                // recorded before the write-back below changes the name (plan 06, Section 3.3, #3)
33460                                                decided.decision(SqllrObservations.DECISION_ARRAY_AGG_ORDER_BY_FIRST_TABLE, e.getObjectOperand(),
33461                                                                fallbackTable, "an ARRAY_AGG ORDER BY key no resolver linked");
33462                                        }
33463                                        e.getObjectOperand().setSourceTable(fallbackTable);
33464                                }
33465                                direct.add(e);
33466                        }
33467                }
33468        }
33469
33470        /**
33471         * ORDER BY inside the argument list of an aggregate, as in LIST(x, ',' ORDER BY y) or
33472         * string_agg(x, ',' ORDER BY y). The keys decide the order of the values in the result, so each is
33473         * an indirect source of the function, like an OVER (ORDER BY ...) or WITHIN GROUP key. The two
33474         * collectors above have already taken the keys of Oracle XMLAGG (indirect) and BigQuery ARRAY_AGG
33475         * (direct); a key already collected is not added twice.
33476         */
33477        private void collectAggregateOrderByExpressions(
33478                        TFunctionCall fc,
33479                        List<TExpression> direct,
33480                        List<TExpression> indirect) {
33481                if (fc.getSortClause() == null) return;
33482                TOrderByItemList items = fc.getSortClause().getItems();
33483                for (int k = 0; k < items.size(); k++) {
33484                        TExpression e = items.getOrderByItem(k).getSortKey();
33485                        if (e != null && !containsNode(direct, e) && !containsNode(indirect, e)) {
33486                                indirect.add(e);
33487                        }
33488                }
33489        }
33490
33491        private static boolean containsNode(List<TExpression> list, TExpression e) {
33492                for (TExpression item : list) {
33493                        if (item == e) return true;
33494                }
33495                return false;
33496        }
33497
33498        private boolean isArrayAggRowReference(TObjectName on) {
33499                if (on == null) return false;
33500                TTable src = on.getSourceTable();
33501                if (src == null) return false;
33502
33503                // Raw equivalent of the old getColumnName extraction (toString fallback preserved).
33504                String raw = on.getColumnNameOnly();
33505                if (raw == null || raw.trim().isEmpty()) {
33506                        raw = on.toString() == null ? "" : on.toString().trim();
33507                } else {
33508                        raw = raw.trim();
33509                }
33510                if (raw.isEmpty()) return false;
33511
33512                // Check if identifier text matches the table alias or table name (canonical dotTable).
33513                boolean matchesAlias =
33514                        (src.getAliasName() != null
33515                                && SQLUtil.sameName(option.getVendor(), ESQLDataObjectType.dotTable, raw, src.getAliasName().toString()))
33516                        || (src.getTableName() != null
33517                                && SQLUtil.sameName(option.getVendor(), ESQLDataObjectType.dotTable, raw, src.getTableName().toString()));
33518                return matchesAlias;
33519        }
33520
33521        /**
33522         * Collects column names inferred from a row-reference array_agg:
33523         *  - ORDER BY keys inside the function call
33524         *  - GROUP BY keys in the enclosing SELECT
33525         * Excludes the table alias itself (so `original` in `array_agg(original)`
33526         * does not leak into the column list).
33527         */
33528        private List<TObjectName> collectRowReferenceInferredColumns(
33529                        TFunctionCall functionCall, String tableAlias) {
33530                List<TObjectName> columns = new ArrayList<TObjectName>();
33531
33532                TOrderByItemList orderItems = null;
33533                if (functionCall.getSortClause() != null) {
33534                        orderItems = functionCall.getSortClause().getItems();
33535                }
33536                if (orderItems == null || orderItems.size() == 0) {
33537                        orderItems = functionCall.getOrderByList();
33538                }
33539                if (orderItems != null) {
33540                        for (int k = 0; k < orderItems.size(); k++) {
33541                                TExpression sortKey = orderItems.getOrderByItem(k).getSortKey();
33542                                collectInferredColumn(sortKey, tableAlias, columns);
33543                        }
33544                }
33545
33546                if (!stmtStack.isEmpty() && stmtStack.peek() instanceof TSelectSqlStatement) {
33547                        TSelectSqlStatement select = (TSelectSqlStatement) stmtStack.peek();
33548                        if (select.getGroupByClause() != null) {
33549                                TGroupByItemList groupByList = select.getGroupByClause().getItems();
33550                                for (int k = 0; k < groupByList.size(); k++) {
33551                                        TExpression expr = groupByList.getGroupByItem(k).getExpr();
33552                                        collectInferredColumn(expr, tableAlias, columns);
33553                                }
33554                        }
33555                }
33556                return columns;
33557        }
33558
33559        private void collectInferredColumn(TExpression expr, String tableAlias,
33560                        List<TObjectName> columns) {
33561                if (expr == null) return;
33562                if (expr.getExpressionType() != EExpressionType.simple_object_name_t) return;
33563                if (expr.getObjectOperand() == null) return;
33564                String colName = expr.getObjectOperand().getColumnNameOnly();
33565                if (colName == null || colName.trim().isEmpty()) colName = expr.getObjectOperand().toString();
33566                if (colName == null || colName.isEmpty()) return;
33567                if (tableAlias != null && SQLUtil.sameName(option.getVendor(), ESQLDataObjectType.dotTable, tableAlias, colName)) return;
33568                if(!columns.contains(expr.getObjectOperand())) {
33569                        columns.add(expr.getObjectOperand());
33570                }
33571        }
33572
33573        /**
33574         * Creates a `*` FunctionResultColumn plus one FunctionResultColumn per
33575         * inferred column. Each column gets its own source-edge back to
33576         * the source table (source.* -> function.*, source.id -> function.id, etc).
33577         *
33578         * Downstream, the `*` column collects all three edges into the inner
33579         * `unique` ResultColumn, and post-processing (rewriteArrayAggRowReferenceTargets)
33580         * splits the collapsed CTAS relation into one per column.
33581         */
33582        private void createRowReferenceStarColumn(Function function, TTable src,
33583                        List<TObjectName> inferredColumns) {
33584                Object tableModel = modelManager.getModel(src);
33585
33586                // Create function.* and bind source.* -> function.*
33587                TObjectName starName = DlineageUtil.syntheticStarName();
33588                ResultColumn starColumn = modelFactory.createFunctionResultColumn(function, starName);
33589                starColumn.setStruct(true);
33590                starColumn.setShowStar(true);
33591
33592                if (tableModel instanceof Table) {
33593                        Table sourceTable = (Table) tableModel;
33594                        TableColumn sourceCol = findOrCreateTableStarColumn(sourceTable);
33595                        
33596                        for (TObjectName colName : inferredColumns) {
33597                                TableColumn tableColumn = modelFactory.createTableColumn(sourceTable, colName, false);
33598                                if (tableColumn != null) {
33599                                        //order by and group by fdr relation
33600                                        AbstractRelationship relation = modelFactory.createImpactRelation();
33601                                        relation.setEffectType(EffectType.function);
33602                                        relation.setTarget(new ResultColumnRelationshipElement(starColumn));
33603                                        relation.addSource(new TableColumnRelationshipElement(tableColumn));
33604                                        
33605                                        // star column fdd relation
33606                                        relation = modelFactory.createDataFlowRelation();
33607                                        relation.setEffectType(EffectType.function);
33608                                        relation.setTarget(new ResultColumnRelationshipElement(starColumn));
33609                                        relation.addSource(new TableColumnRelationshipElement(tableColumn));
33610                                }
33611                        }
33612
33613                        
33614                        if (sourceCol != null) {
33615                                DataFlowRelationship rel = modelFactory.createDataFlowRelation();
33616                                rel.setEffectType(EffectType.function);
33617                                rel.setTarget(new ResultColumnRelationshipElement(starColumn));
33618                                rel.addSource(new TableColumnRelationshipElement(sourceCol));
33619                        }
33620                        
33621                } else if (tableModel instanceof ResultSet) {
33622                        ResultSet sourceRS = (ResultSet) tableModel;
33623                        ResultColumn sourceCol = findOrCreateResultStarColumn(sourceRS);
33624                        for (TObjectName colName : inferredColumns) {
33625                                sourceCol.bindStarLinkColumn(colName);
33626                        }
33627                        
33628                        if (sourceCol != null) {
33629                                DataFlowRelationship rel = modelFactory.createDataFlowRelation();
33630                                rel.setEffectType(EffectType.function);
33631                                rel.setTarget(new ResultColumnRelationshipElement(starColumn));
33632                                rel.addSource(new ResultColumnRelationshipElement(sourceCol));
33633                        }
33634                }
33635        }
33636
33637        /**
33638         * Builds the Function lineage model for a structured-dataflow descriptor.
33639         * Creates per-field FunctionResultColumns and links each to an exact
33640         * structured source TableColumn (e.g. {@code nodes[*].key}). Returns the
33641         * Function on success; returns {@code null} when the source column cannot
33642         * be resolved so the caller falls back to existing function lineage.
33643         */
33644        private Function createStructuredDataflowFunction(StructuredDataflowDescriptor desc) {
33645                TParseTreeNode syntaxNode = desc.getSyntaxNode();
33646                if (!(syntaxNode instanceof TFunctionCall)) {
33647                        return null;
33648                }
33649                TFunctionCall fnCall = (TFunctionCall) syntaxNode;
33650
33651                Function function = null;
33652                Object existing = modelManager.getModel(fnCall);
33653                if (existing instanceof Function) {
33654                        function = (Function) existing;
33655                }
33656                boolean alreadyBuilt = function != null
33657                                && function.getColumns() != null
33658                                && !function.getColumns().isEmpty();
33659                if (alreadyBuilt) {
33660                        return function;
33661                }
33662                if (function == null) {
33663                        function = modelFactory.createFunction(fnCall);
33664                }
33665
33666                StructuredValueSource src = desc.getSource();
33667                TObjectName sourceColumn = src.getSourceColumn();
33668                TTable srcTable = sourceColumn != null ? sourceColumn.getSourceTable() : null;
33669                Object srcTableModel = srcTable != null ? modelManager.getModel(srcTable) : null;
33670                if (!(srcTableModel instanceof Table)) {
33671                        return null;
33672                }
33673                Table sourceTable = (Table) srcTableModel;
33674
33675                for (StructuredFieldBinding fb : desc.getFieldBindings()) {
33676                        TableColumn pathColumn = findOrCreateStructuredPathColumn(sourceTable, fb.getSourcePath());
33677                        if (pathColumn == null) {
33678                                continue;
33679                        }
33680
33681                        TObjectName fieldName = new TObjectName();
33682                        fieldName.setString(fb.getOutputFieldName());
33683                        ResultColumn fnResult = modelFactory.createFunctionResultColumn(function, fieldName);
33684
33685                        DataFlowRelationship rel = modelFactory.createDataFlowRelation();
33686                        rel.setEffectType(EffectType.function);
33687                        rel.setTarget(new ResultColumnRelationshipElement(fnResult));
33688                        rel.addSource(new TableColumnRelationshipElement(pathColumn));
33689                }
33690                return function;
33691        }
33692
33693        /**
33694         * Resolve a struct-field-style outer reference ({@code structAlias.field}
33695         * or {@code relAlias.structAlias.field}) against in-scope subquery
33696         * QueryTables that hold a struct-bearing result column. When found,
33697         * append a relation source pointing at the function's field result
33698         * column so the exact source path (e.g. {@code nodes[*].key}) is
33699         * preserved through the outer projection. Returns {@code true} when
33700         * the reference was bound and the caller should skip orphan-column
33701         * fallback.
33702         */
33703        private boolean tryAppendStructuredFieldRelation(DataFlowRelationship relation,
33704                        TObjectName columnName, TTableList tableList) {
33705                if (relation == null || columnName == null || tableList == null) {
33706                        return false;
33707                }
33708                String fullText = columnName.toString();
33709                if (fullText == null) return false;
33710                int firstDot = fullText.indexOf('.');
33711                if (firstDot < 0) return false;
33712
33713                String structAlias;
33714                String fieldName;
33715                int lastDot = fullText.lastIndexOf('.');
33716                if (firstDot == lastDot) {
33717                        structAlias = fullText.substring(0, firstDot);
33718                        fieldName = fullText.substring(firstDot + 1);
33719                } else {
33720                        structAlias = fullText.substring(firstDot + 1, lastDot);
33721                        fieldName = fullText.substring(lastDot + 1);
33722                }
33723                if (structAlias == null || structAlias.isEmpty()) return false;
33724                if (fieldName == null || fieldName.isEmpty()) return false;
33725
33726                for (int i = 0; i < tableList.size(); i++) {
33727                        TTable tt = tableList.getTable(i);
33728                        if (tt == null) continue;
33729                        Object tm = modelManager.getModel(tt);
33730                        if (!(tm instanceof QueryTable)) continue;
33731                        QueryTable qt = (QueryTable) tm;
33732                        if (qt.getColumns() == null) continue;
33733                        for (ResultColumn rc : qt.getColumns()) {
33734                                if (rc == null || rc.getName() == null) continue;
33735                                if (!SQLUtil.sameName(option.getVendor(), ESQLDataObjectType.dotColumn, structAlias, rc.getName())) continue;
33736                                ResultColumn fieldColumn = findStructuredFieldColumn(rc, fieldName);
33737                                if (fieldColumn != null) {
33738                                        relation.addSource(new ResultColumnRelationshipElement(fieldColumn));
33739                                        return true;
33740                                }
33741                        }
33742                }
33743                return false;
33744        }
33745
33746        /**
33747         * Given a QueryTable result column that aliases a structured generator
33748         * (e.g. {@code nodes1} backed by Spark {@code explode(from_json(...))}),
33749         * locate the FunctionResultColumn named {@code fieldName}. The Function
33750         * model is discovered by walking the existing data-flow relations whose
33751         * target is the alias column.
33752         */
33753        private ResultColumn findStructuredFieldColumn(ResultColumn aliasColumn, String fieldName) {
33754                if (aliasColumn == null || fieldName == null) return null;
33755                Relationship[] rels = modelManager.getRelations();
33756                if (rels == null) return null;
33757                String normalField = fieldName.trim();
33758                for (Relationship r : rels) {
33759                        if (!(r instanceof DataFlowRelationship)) continue;
33760                        DataFlowRelationship dfr = (DataFlowRelationship) r;
33761                        if (dfr.getTarget() == null) continue;
33762                        if (!(dfr.getTarget().getElement() == aliasColumn)) continue;
33763                        if (dfr.getSources() == null) continue;
33764                        for (RelationshipElement<?> se : dfr.getSources()) {
33765                                Object srcElement = se.getElement();
33766                                if (!(srcElement instanceof ResultColumn)) continue;
33767                                ResultColumn src = (ResultColumn) srcElement;
33768                                if (SQLUtil.sameName(option.getVendor(), ESQLDataObjectType.dotColumn, normalField, src.getName())) {
33769                                        return src;
33770                                }
33771                        }
33772                }
33773                return null;
33774        }
33775
33776        /**
33777         * Find-or-create a TableColumn carrying a structured display path
33778         * (e.g. {@code nodes[*].key}) on the given source table. Unlike the
33779         * star-column {@link #findOrCreateTableStarColumn(Table)}, this does
33780         * not treat the dotted/bracketed text as a schema-qualified name;
33781         * the entire path is used as the column display, so existing flat
33782         * columns named after the leaf field (e.g. {@code key}) do not
33783         * collide with structured-path columns.
33784         */
33785        private TableColumn findOrCreateStructuredPathColumn(Table table,
33786                        gudusoft.gsqlparser.common.structured.StructuredColumnPath path) {
33787                String pathDisplay = path == null ? null : path.toDisplayString();
33788                if (table == null || pathDisplay == null || pathDisplay.isEmpty()) return null;
33789                // one column per path of the table, by its structural identity (plan section 4.7), not by its display
33790                FieldNodeKey key = FieldNodeKey.of(table, path.getSegments());
33791                TableColumn column = structuredPathColumns.get(key);
33792                if (column == null) {
33793                        column = new TableColumn(table, pathDisplay);
33794                        structuredPathColumns.put(key, column);
33795                }
33796                return column;
33797        }
33798
33799        /** The columns {@link #findOrCreateStructuredPathColumn} made, by table and path. */
33800        private final Map<FieldNodeKey, TableColumn> structuredPathColumns = new HashMap<FieldNodeKey, TableColumn>();
33801
33802        /**
33803         * Whether a reference names a result column. A column with an identity is named by the reference's identity
33804         * alone; a column without one (an unaliased expression, a synthesized name) by the name service's equality of the
33805         * two names as written.
33806         */
33807        private boolean referencesColumn(TObjectName reference, ResultColumn column) {
33808                CanonKey columnKey = column.getIdentifierKey();
33809                return columnKey != null ? columnKey.equals(DlineageUtil.columnKey(reference))
33810                                : DlineageUtil.sameColumnSegment(DlineageUtil.rawColumnName(reference), column.getName());
33811        }
33812
33813        /** Whether the first segment of a reference, as written (a BigQuery struct base), names a result column. */
33814        private boolean referencesColumnSegment(String segment, ResultColumn column) {
33815                CanonKey columnKey = column.getIdentifierKey();
33816                return columnKey != null
33817                                ? columnKey.equals(DlineageUtil.lexicalColumnKey(getOption().getVendor(), segment))
33818                                : DlineageUtil.sameColumnSegment(segment, column.getName());
33819        }
33820
33821        /**
33822         * STRUCT tree: links a field read through a CTE or a subquery, {@code customer.address.city} or
33823         * {@code c.customer.name} where {@code customer} is a column of it, to the column named by the read's base. A
33824         * result set has no field nodes, so the read comes from that whole column, as a read of {@code customer} does.
33825         * Matched by its last segment, the read found no column and its edge was lost. A set operation is left to its own
33826         * path, which already matches a field read by its base.
33827         *
33828         * @return whether the reference is such a read and the query has a column named by its base
33829         */
33830        private boolean linkStructFieldReadThroughQuery(QueryTable queryTable, TSelectSqlStatement subquery,
33831                        TObjectName reference, Object modelObject, DataFlowRelationship relation) {
33832                if (subquery != null && subquery.isCombinedQuery()) {
33833                        return false;
33834                }
33835                ResultColumn column = structFieldReadBaseColumn(reference, queryTable.getColumns());
33836                if (column == null) {
33837                        return false;
33838                }
33839                column = structFieldReadSource(column, reference);
33840                if (!column.equals(modelObject)) {
33841                        relation.addSource(new ResultColumnRelationshipElement(column, reference));
33842                }
33843                return true;
33844        }
33845
33846        /**
33847         * Whether {@code column} is a column of a SELECT that joins with USING: a USING column merges both sides, so a
33848         * single published source does not prove the value passes through unchanged (Mantis 4836).
33849         */
33850        private static boolean mergesJoinUsingColumns(ResultColumn column) {
33851                if (!(column.getResultSet() instanceof SelectResultSet)) {
33852                        return false;
33853                }
33854                TSelectSqlStatement select = ((SelectResultSet) column.getResultSet()).getSelectStmt();
33855                if (select == null || select.getJoins() == null) {
33856                        return false;
33857                }
33858                for (int i = 0; i < select.getJoins().size(); i++) {
33859                        TJoin join = select.getJoins().getJoin(i);
33860                        if (join.getJoinItems() == null) {
33861                                continue;
33862                        }
33863                        for (int j = 0; j < join.getJoinItems().size(); j++) {
33864                                if (join.getJoinItems().getJoinItem(j).getUsingColumns() != null) {
33865                                        return true;
33866                                }
33867                        }
33868                }
33869                return false;
33870        }
33871
33872        /**
33873         * STRUCT tree: what a field read through a CTE or a derived table reads, given the column {@code base} named by
33874         * the read's base ({@code customer} for {@code customer.address.city}). When that column passes on a table column
33875         * unchanged, through result columns each with one source and no function on the way, the read is of the field
33876         * itself: a field node below each result column on the way ({@code customer.address.city}), fed by the next one
33877         * and the last by the table's field node, so {@code v.city <- orders_raw.customer.address.city}. Otherwise (a
33878         * function, a STRUCT the query builds, a set operation, a wildcard, a field the DDL does not declare) the whole
33879         * column {@code base}, as before (Mantis 4836).
33880         */
33881        private ResultColumn structFieldReadSource(ResultColumn base, TObjectName reference) {
33882                gudusoft.gsqlparser.common.structured.StructuredColumnPath read = reference.getFieldPathRead();
33883                if (base == null || read == null || read.getSegments().isEmpty()
33884                                || !nestedProfile().buildsDeclaredTree()) {
33885                        return base;
33886                }
33887                ResultColumn built = builtFieldNode(base, read);
33888                if (built != null) {
33889                        // a field of a STRUCT the derived table's query built: its own node (Mantis 4893)
33890                        return built;
33891                }
33892                List<ResultColumn> chain = new ArrayList<ResultColumn>();
33893                Set<Object> visited = Collections.newSetFromMap(new IdentityHashMap<Object, Boolean>());
33894                Object current = base;
33895                while (current instanceof ResultColumn) {
33896                        ResultColumn column = (ResultColumn) current;
33897                        if (!visited.add(column) || column.getResultSet() instanceof Function || column.isStarColumn()
33898                                        || column.isStruct() || mergesJoinUsingColumns(column)) {
33899                                return base;
33900                        }
33901                        chain.add(column);
33902                        Object source = null;
33903                        for (Relationship relation : modelManager.getRelations()) {
33904                                if (!(relation instanceof DataFlowRelationship) || relation.getTarget() == null
33905                                                || relation.getTarget().getElement() != column) {
33906                                        continue;
33907                                }
33908                                if (((DataFlowRelationship) relation).getEffectType() != EffectType.select
33909                                                || relation.getSources().size() != 1 || source != null) {
33910                                        return base;
33911                                }
33912                                source = ((RelationshipElement<?>) relation.getSources().iterator().next()).getElement();
33913                        }
33914                        if (source == null) {
33915                                return base;
33916                        }
33917                        current = source;
33918                }
33919                if (!(current instanceof TableColumn)) {
33920                        return base;
33921                }
33922                TableColumn origin = (TableColumn) current;
33923                List<gudusoft.gsqlparser.common.structured.StructuredPathSegment> segments =
33924                                new ArrayList<gudusoft.gsqlparser.common.structured.StructuredPathSegment>();
33925                TableColumn declared = origin;
33926                if (origin.isFieldPathNode()) {
33927                        if (origin.getDeclaredColumn() == null) {
33928                                return base;
33929                        }
33930                        declared = origin.getDeclaredColumn();
33931                        segments.addAll(origin.getFieldPath().getSegments());
33932                }
33933                segments.addAll(read.getSegments());
33934                TableColumn tableNode = modelFactory.createFieldPath(declared, segments, reference);
33935                if (tableNode == null) {
33936                        // the DDL declares no such field: the whole column, as before
33937                        return base;
33938                }
33939                StringBuilder suffix = new StringBuilder();
33940                for (gudusoft.gsqlparser.common.structured.StructuredPathSegment segment : read.getSegments()) {
33941                        suffix.append(segment.toDisplayString());
33942                }
33943                Object upstream = tableNode;
33944                for (int i = chain.size() - 1; i >= 0; i--) {
33945                        ResultColumn column = chain.get(i);
33946                        // one node per field of the column, by its structural identity (plan section 4.7)
33947                        ResultColumn node = column.getResultSet().getFieldPathNode(FieldNodeKey.of(column, read.getSegments()));
33948                        if (node == null) {
33949                                TSourceToken path = new TSourceToken(column.getName() + suffix);
33950                                TSourceToken at = reference.getStartToken();
33951                                if (at != null) {
33952                                        path.lineNo = at.lineNo;
33953                                        path.columnNo = at.columnNo;
33954                                }
33955                                // a node of its own: never name-matched to a column, so a quoted alias "customer.address.city"
33956                                // stays the column it is
33957                                node = modelFactory.createDeterminedResultColumn(column.getResultSet(),
33958                                                TObjectName.createObjectName(option.getVendor(), EDbObjectType.column, path));
33959                                node.setStruct(true);
33960                                column.getResultSet().addFieldPathColumn(node, column, read.getSegments());
33961                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
33962                                relation.setEffectType(EffectType.select);
33963                                relation.setTarget(new ResultColumnRelationshipElement(node));
33964                                if (upstream instanceof TableColumn) {
33965                                        relation.addSource(new TableColumnRelationshipElement((TableColumn) upstream));
33966                                } else {
33967                                        relation.addSource(new ResultColumnRelationshipElement((ResultColumn) upstream));
33968                                }
33969                        }
33970                        upstream = node;
33971                }
33972                return (ResultColumn) upstream;
33973        }
33974
33975        /**
33976         * STRUCT tree: the column of a CTE or a subquery that a field read through it reads, the one named by the read's
33977         * base ({@code customer} for {@code customer.address.city}); null when the reference reads a whole column, when
33978         * the columns have none of that name, and when the read is not proven: its base also names the relation it is read
33979         * from ({@code c.name FROM q AS c}, which BigQuery reads as ambiguous), or the DDL of the column the CTE passes on
33980         * has no such field.
33981         */
33982        private ResultColumn structFieldReadBaseColumn(TObjectName reference, List<ResultColumn> columns) {
33983                if (!nestedProfile().buildsDeclaredTree() || reference.getFieldPathRead() == null
33984                                || reference.getFieldPathBaseToken() == null) {
33985                        return null;
33986                }
33987                String base = reference.getFieldPathBaseToken().toString();
33988                TTable relation = reference.getSourceTable();
33989                if (relation != null) {
33990                        String relationName = !SQLUtil.isEmpty(relation.getAliasName()) ? relation.getAliasName()
33991                                        : relation.getName();
33992                        if (relationName != null
33993                                        && SQLUtil.sameName(option.getVendor(), ESQLDataObjectType.dotTable, base, relationName)) {
33994                                return null;
33995                        }
33996                }
33997                for (ResultColumn column : columns) {
33998                        if (!column.isStarColumn() && referencesColumnSegment(base, column)) {
33999                                return declaresFieldRead(passedOnDeclaredColumn(column), reference.getFieldPathRead()) ? column : null;
34000                        }
34001                }
34002                return null;
34003        }
34004
34005        /** Declared types whose values a field read can reach: the STRUCT types, and BigQuery JSON. */
34006        private static final Set<String> FIELD_READABLE_TYPES = new HashSet<String>(
34007                        Arrays.asList("struct", "row", "record", "json"));
34008
34009        /**
34010         * STRUCT tree: whether the DDL of {@code origin}, a declared column or field node, lets {@code read} reach a field
34011         * below it: each field of the path is a field its declared type lists, each {@code [*]} the element of a declared
34012         * array, and a declared column whose type lists no fields is a STRUCT or JSON one. True when nothing is declared
34013         * to check against ({@code origin} null, or a field of a type GSP does not know).
34014         */
34015        private boolean declaresFieldRead(TableColumn origin,
34016                        gudusoft.gsqlparser.common.structured.StructuredColumnPath read) {
34017                TableColumn node = origin;
34018                for (int i = 0; node != null && i < read.getSegments().size(); i++) {
34019                        gudusoft.gsqlparser.common.structured.StructuredPathSegment segment = read.getSegments().get(i);
34020                        boolean field = segment.getKind() == gudusoft.gsqlparser.common.structured.StructuredPathSegmentKind.FIELD;
34021                        if (node.isFieldsDeclared()) {
34022                                TableColumn next = null;
34023                                for (TableColumn declared : node.getDeclaredFields()) {
34024                                        List<gudusoft.gsqlparser.common.structured.StructuredPathSegment> path = declared.getFieldPath()
34025                                                        .getSegments();
34026                                        if (field && DlineageUtil.sameName(ESQLDataObjectType.dotColumn,
34027                                                        path.get(path.size() - 1).getName(), segment.getName())) {
34028                                                next = declared;
34029                                                break;
34030                                        }
34031                                }
34032                                if (next == null) {
34033                                        return false;
34034                                }
34035                                node = next;
34036                        } else if (node.getDeclaredElement() != null) {
34037                                if (field) {
34038                                        return false;
34039                                }
34040                                node = node.getDeclaredElement();
34041                        } else {
34042                                // a declared column with a scalar type has no fields; a field node carries no type to tell
34043                                return node.isFieldPathNode() || node.getDataType() == null
34044                                                || FIELD_READABLE_TYPES.contains(node.getDataType().toLowerCase(Locale.ROOT));
34045                        }
34046                }
34047                return true;
34048        }
34049
34050        /**
34051         * STRUCT tree: the declared column or field node that a result column passes on unchanged, through other result
34052         * columns, each with one source and no function on the way; null when there is none, or when its table has no
34053         * DDL.
34054         */
34055        private TableColumn passedOnDeclaredColumn(ResultColumn column) {
34056                Object current = column;
34057                for (int depth = 0; depth < 32 && current instanceof ResultColumn; depth++) {
34058                        if (((ResultColumn) current).getResultSet() instanceof Function) {
34059                                return null;
34060                        }
34061                        Object source = null;
34062                        for (Relationship relation : modelManager.getRelations()) {
34063                                if (!(relation instanceof DataFlowRelationship) || relation.getTarget() == null
34064                                                || relation.getTarget().getElement() != current) {
34065                                        continue;
34066                                }
34067                                if (((DataFlowRelationship) relation).getEffectType() != EffectType.select
34068                                                || relation.getSources().size() != 1 || source != null) {
34069                                        return null;
34070                                }
34071                                source = ((RelationshipElement<?>) relation.getSources().iterator().next()).getElement();
34072                        }
34073                        current = source;
34074                }
34075                return current instanceof TableColumn && ((TableColumn) current).getTable().isCreateTable()
34076                                ? (TableColumn) current : null;
34077        }
34078
34079        /**
34080         * STRUCT tree: the relation a read belongs to, given the one a lookup found. A field read TSQLResolver2 could not
34081         * place belongs to the one relation that declares its base column ({@link #structFieldReadDeclaredOwner}), and a
34082         * field read that several relations could own belongs to none (plan rule R3). Any other read keeps {@code found}.
34083         */
34084        private TTable structFieldReadTable(TTable found, TObjectName reference, TTableList tables) {
34085                TTable declaredOwner = structFieldReadDeclaredOwner(reference, tables);
34086                if (declaredOwner != null) {
34087                        return declaredOwner;
34088                }
34089                return found != null && isUndecidedFieldRead(reference) ? null : found;
34090        }
34091
34092        /**
34093         * STRUCT tree: the relation of {@code tables} that owns a field read TSQLResolver2 could not place, such as BigQuery
34094         * {@code customer.address.city FROM orders_raw o, z}, whose table is then only the parse's guess: the one relation
34095         * that declares the base column, by DDL or as a column of a CTE or a subquery, or else the one relation that can
34096         * have it, when the others are declared without it. Null when the read is placed, ambiguous, or not owned by
34097         * exactly one relation.
34098         */
34099        private TTable structFieldReadDeclaredOwner(TObjectName reference, TTableList tables) {
34100                if (!nestedProfile().buildsDeclaredTree() || tables == null
34101                                || reference.getFieldPathBaseToken() == null || reference.getFieldPathRead() == null
34102                                || reference.getResolution() == null || reference.getResolution().isExactMatch()
34103                                || reference.getResolution().isAmbiguous()) {
34104                        return null;
34105                }
34106                String base = reference.getFieldPathBaseToken().toString();
34107                TTable declaring = null, possible = null;
34108                int declaringCount = 0, possibleCount = 0;
34109                for (int i = 0; i < tables.size(); i++) {
34110                        TTable table = tables.getTable(i);
34111                        Object model = modelManager.getModel(table);
34112                        Boolean has = null;   // true: declares the base; false: declared without it; null: unknown
34113                        if (model instanceof Table) {
34114                                Table tableModel = (Table) model;
34115                                if (tableModel.isCreateTable()) {
34116                                        has = Boolean.FALSE;
34117                                        for (TableColumn column : tableModel.getColumns()) {
34118                                                if (DlineageUtil.sameName(ESQLDataObjectType.dotColumn, column.getName(), base)) {
34119                                                        has = Boolean.TRUE;
34120                                                        break;
34121                                                }
34122                                        }
34123                                }
34124                        } else if (model instanceof ResultSet && !(model instanceof Function)) {
34125                                has = Boolean.FALSE;
34126                                for (ResultColumn column : ((ResultSet) model).getColumns()) {
34127                                        if (column.isStarColumn()) {
34128                                                has = null;
34129                                        } else if (referencesColumnSegment(base, column)) {
34130                                                has = Boolean.TRUE;
34131                                                break;
34132                                        }
34133                                }
34134                        } else {
34135                                continue;
34136                        }
34137                        if (Boolean.TRUE.equals(has)) {
34138                                declaring = table;
34139                                declaringCount++;
34140                        } else if (has == null) {
34141                                possible = table;
34142                                possibleCount++;
34143                        }
34144                }
34145                if (declaringCount == 1) {
34146                        return declaring;
34147                }
34148                return declaringCount == 0 && possibleCount == 1 ? possible : null;
34149        }
34150
34151        /**
34152         * The key two published columns of one result set collide on, which is when the output qualifies them by
34153         * table: the identity for an identifier, the text for a label (an expression, a literal).
34154         */
34155        private static Object publishedColumnKey(ResultColumn column) {
34156                // Two spellings of one name (HANA sp.Position and sp.POSITION) are one column and collide; a literal 'Name'
34157                // beside the column Name is not that column. An alias used twice (x, shown as x and x(1)) is one name twice.
34158                CanonKey key = column.getIdentifierKey();
34159                if (key != null) {
34160                        return key;
34161                }
34162                // A field path (BigQuery a.left.Name) publishes its leaf: two leaves the vendor reads as one name collide.
34163                CanonKey leaf = DlineageUtil.fieldLeafKey(columnObjectName(column));
34164                return leaf != null ? leaf : ModelLabelKey.of(ModelLabelKey.Kind.SYNTHETIC, column.getName());
34165        }
34166
34167        /** The parsed name a result column was made from, when it was made from one. */
34168        private static TObjectName columnObjectName(ResultColumn column) {
34169                Object object = column.getColumnObject();
34170                if (object instanceof TObjectName) {
34171                        return (TObjectName) object;
34172                }
34173                if (object instanceof TResultColumn && ((TResultColumn) object).getExpr() != null
34174                                && ((TResultColumn) object).getExpr().getExpressionType() == EExpressionType.simple_object_name_t) {
34175                        return ((TResultColumn) object).getExpr().getObjectOperand();
34176                }
34177                return null;
34178        }
34179
34180        /** Whether a query column and a table column are one column: by identity when both have one, else by name. */
34181        private static boolean sameModelColumn(Column a, Column b) {
34182                if (a.getIdentifierKey() != null && b.getIdentifierKey() != null) {
34183                        return a.getIdentifierKey().equals(b.getIdentifierKey());
34184                }
34185                return DlineageUtil.sameColumnSegment(a.getName(), b.getName());
34186        }
34187
34188        /** The name of a copy of a model column: a synthesized star for a star, else a name with the given text. */
34189        private static TObjectName copyColumnName(Column source, String text) {
34190                if (source.isStarColumn()) {
34191                        return DlineageUtil.syntheticStarName();
34192                }
34193                return Column.nameOfColumn(source, text);
34194        }
34195
34196        /** Whether a select-list item is a star ({@code *}, {@code t.*}), by the parse. */
34197        /**
34198         * STRUCT tree: the columns an INSERT without a column list fills by position. A select item that builds a STRUCT,
34199         * or an array of STRUCTs, yields one result column per field ({@code struct(a, b) AS nest1} gives
34200         * {@code nest1.a} and {@code nest1.b}), so when the select list has exactly one result column per declared leaf
34201         * field, each STRUCT column with declared fields takes one position per leaf field, depth first, and an array of
34202         * STRUCTs one per leaf field of its element. Otherwise, and outside the STRUCT tree, the table's columns.
34203         */
34204        private List<TableColumn> insertPositionalColumns(Table table, List<ResultColumn> items) {
34205                if (!nestedProfile().buildsDeclaredTree() || !table.isCreateTable()) {
34206                        return table.getColumns();
34207                }
34208                List<TableColumn> leaves = new ArrayList<TableColumn>();
34209                for (TableColumn column : table.getColumns()) {
34210                        addDeclaredLeaves(column, leaves);
34211                }
34212                return leaves.size() != table.getColumns().size() && leaves.size() == items.size() ? leaves
34213                                : table.getColumns();
34214        }
34215
34216        /** The declared leaf fields below {@code node}, depth first; {@code node} itself when its fields are not declared. */
34217        private static void addDeclaredLeaves(TableColumn node, List<TableColumn> leaves) {
34218                List<TableColumn> fields = node.getDeclaredFields();
34219                if (fields.isEmpty()) {
34220                        TableColumn element = node.getDeclaredElement();
34221                        if (element != null && !element.getDeclaredFields().isEmpty()) {
34222                                fields = element.getDeclaredFields();
34223                        }
34224                }
34225                if (fields.isEmpty()) {
34226                        leaves.add(node);
34227                        return;
34228                }
34229                for (TableColumn field : fields) {
34230                        addDeclaredLeaves(field, leaves);
34231                }
34232        }
34233
34234        /**
34235         * The columns of {@code table} an item of an array constructor reads, {@code ARRAY[customer.customer_id]}: the field
34236         * path node it reads in the STRUCT tree, else every column whose name its name matches, as this was always found.
34237         */
34238        /** The name of an unaliased array item: the last field it reads, else its column (Mantis 4833). */
34239        private static String unaliasedArrayItemName(TObjectName item) {
34240                gudusoft.gsqlparser.common.structured.StructuredColumnPath read = item.getFieldPathRead();
34241                if (read != null && !read.getSegments().isEmpty()) {
34242                        gudusoft.gsqlparser.common.structured.StructuredPathSegment last = read.getSegments().get(read.getSegments().size() - 1);
34243                        if (last.getKind() == gudusoft.gsqlparser.common.structured.StructuredPathSegmentKind.FIELD && last.getName() != null) {
34244                                return last.getName();
34245                        }
34246                }
34247                return item.getColumnNameOnly();
34248        }
34249
34250        private List<TableColumn> arrayItemSources(Table table, TObjectName item) {
34251                List<TableColumn> sources = new ArrayList<TableColumn>();
34252                if (nestedProfile().buildsDeclaredTree() && item.getFieldPathRead() != null) {
34253                        TableColumn node = modelFactory.createTableColumn(table, item, false);
34254                        if (node != null) {
34255                                sources.add(node);
34256                        }
34257                        return sources;
34258                }
34259                for (TableColumn column : table.getColumns()) {
34260                        if (DlineageUtil.sameColumnName(item.toString(), column.getName())) {
34261                                sources.add(column);
34262                        }
34263                }
34264                return sources;
34265        }
34266
34267        /**
34268         * Whether {@code star} is a bare {@code *} over several tables, as in {@code SELECT * FROM a JOIN b}. It reads every
34269         * one of them, while {@code getSourceTable()} names only one, so expanding that table's known columns alone drops
34270         * the others' (Mantis 4970: in a branch of a UNION the outer query then read the wrong column by position).
34271         */
34272        private static boolean starReadsSeveralTables(TObjectName star) {
34273                List<TTable> tables = star.getSourceTableList();
34274                return star.getTableToken() == null && tables != null && tables.size() > 1;
34275        }
34276
34277        /**
34278         * The column of a view or table that receives {@code source}, one of the result columns an item is bound to. The *
34279         * among them, of a star over a join that expanded its other tables (Mantis 4981), is received by the item's own
34280         * {@code *}, a star that keeps the names read through it, as an item bound to its star is received; any other
34281         * column by its name.
34282         */
34283        private TableColumn itemColumnTarget(Table table, ResultColumn source) {
34284                TObjectName star = source.isStarColumn() && source.getColumnObject() instanceof TResultColumn
34285                                ? ((TResultColumn) source.getColumnObject()).getFieldAttr() : null;
34286                if (star == null || !DlineageUtil.isWildcard(star)) {
34287                        return modelFactory.createInsertTableColumn(table, source);
34288                }
34289                TableColumn target = modelFactory.createInsertTableColumn(table, star);
34290                if (target != null && target.isStarColumn() && source.hasStarLinkColumn()) {
34291                        target.shareStarLinksFrom(source);
34292                }
34293                return target;
34294        }
34295
34296        /**
34297         * The result columns of {@code item} in {@code resultSet}, in FROM order, keyed by name as an expanded star's are.
34298         * {@code star} is the wildcard among them; a writer reaches it only by iterating, never by its key, so it takes a
34299         * key no column of the item has: a column a table declares as {@code [*]} keeps the key {@code *} (Codex review of
34300         * Mantis 4981).
34301         */
34302        private static LinkedHashMap<String, ResultColumn> itemColumns(ResultSet resultSet, TResultColumn item,
34303                        ResultColumn star) {
34304                Set<String> names = new HashSet<String>();
34305                for (ResultColumn column : resultSet.getColumns()) {
34306                        if (column.getColumnObject() == item && column != star) {
34307                                names.add(column.getName());
34308                        }
34309                }
34310                String starKey = star.getName();
34311                for (int n = 1; names.contains(starKey); n++) {
34312                        starKey = star.getName() + "#" + n;
34313                }
34314                LinkedHashMap<String, ResultColumn> columns = new LinkedHashMap<String, ResultColumn>();
34315                for (ResultColumn column : resultSet.getColumns()) {
34316                        if (column.getColumnObject() == item) {
34317                                columns.put(column == star ? starKey : column.getName(), column);
34318                        }
34319                }
34320                return columns;
34321        }
34322
34323        /** The star {@link #analyzeStarOfTables} analyzes, and the only tables it reads; null otherwise. */
34324        private TObjectName starOfTables;
34325        private List<TTable> starOfTablesRead;
34326
34327        /**
34328         * Analyzes {@code column}, the item {@code star} of a {@code SELECT *} over a join, as a read of {@code tables}
34329         * only: the tables of the join whose columns are not known, when the others' columns were expanded into result
34330         * columns of their own (Mantis 4981). The restriction is keyed by the star node, so a star of a derived table this
34331         * analysis reaches reads its own tables.
34332         */
34333        private void analyzeStarOfTables(TResultColumn column, TObjectName star, List<TTable> tables) {
34334                TObjectName outerStar = starOfTables;
34335                List<TTable> outerTables = starOfTablesRead;
34336                starOfTables = star;
34337                starOfTablesRead = tables;
34338                try {
34339                        analyzeResultColumn(column, EffectType.select);
34340                } finally {
34341                        starOfTables = outerStar;
34342                        starOfTablesRead = outerTables;
34343                }
34344        }
34345
34346        /**
34347         * Expands {@code column}, a star of a branch of a set operation, from the known columns of {@code starTables}, the
34348         * tables it reads in FROM order: a table the script creates or a determined result set. The other branches and the
34349         * query reading the set operation pair its columns by position, so each table's columns keep theirs (Mantis 4970).
34350         * A later table's column spelled like an earlier one's is the same column when a USING or NATURAL join merges them,
34351         * and is otherwise renamed {@code name1}, as in a plain select list. Returns false, having done nothing, when a
34352         * table's columns are not known; the star is then left to its star links.
34353         */
34354        private boolean expandSetBranchStar(TSelectSqlStatement stmt, ResultSet resultSetModel, Object queryModel,
34355                        TResultColumn column, List<TTable> starTables,
34356                        Map<String, Pair<String, TExpression>> replaceAsIdentifierMap, Map<String, TObjectName> replaceColumnMap) {
34357                for (TTable sourceTable : starTables) {
34358                        if (!columnsKnown(sourceTable)) {
34359                                return false;
34360                        }
34361                }
34362                // the spellings earlier tables published: createStarResultColumn keys a star's columns by them
34363                Set<String> earlierNames = new HashSet<String>();
34364                Set<String> names = new HashSet<String>();
34365                List<TTable> earlierTables = new ArrayList<TTable>();
34366                for (TTable sourceTable : starTables) {
34367                        expandSetBranchStarTable(stmt, resultSetModel, queryModel, column, sourceTable, replaceAsIdentifierMap,
34368                                        replaceColumnMap, earlierNames, names, earlierTables);
34369                }
34370                return true;
34371        }
34372
34373        /** Whether the columns of {@code table} are known: a table the script creates or a determined result set. */
34374        private boolean columnsKnown(TTable table) {
34375                Object tableModel = modelManager.getModel(table);
34376                return tableModel instanceof Table && ((Table) tableModel).isCreateTable()
34377                                || tableModel instanceof ResultSet && ((ResultSet) tableModel).isDetermined();
34378        }
34379
34380        /**
34381         * Mantis 4975: the tables of {@code star}, a star over a join in a branch of a set operation, to expand into result
34382         * columns of their own before the star reads the rest: the tables the script declares, when the others are one run
34383         * of the join, so that the star stands where their columns do. Empty when every table, or none, is declared, or when
34384         * the other tables are apart (the star would stand for both places at once). A derived table stays in the star: its
34385         * columns, read through the star by name, would also take the star's tables (Codex review).
34386         */
34387        private List<TTable> splitSetBranchStarTables(List<TTable> starTables) {
34388                int first = -1;
34389                int last = -1;
34390                for (int k = 0; k < starTables.size(); k++) {
34391                        if (!columnsDeclared(starTables.get(k))) {
34392                                if (first < 0) {
34393                                        first = k;
34394                                }
34395                                last = k;
34396                        }
34397                }
34398                if (first < 0 || last - first + 1 == starTables.size()) {
34399                        return Collections.emptyList();
34400                }
34401                List<TTable> known = new ArrayList<TTable>();
34402                for (int k = 0; k < starTables.size(); k++) {
34403                        if (columnsDeclared(starTables.get(k))) {
34404                                if (k > first && k < last) {
34405                                        return Collections.emptyList();
34406                                }
34407                                known.add(starTables.get(k));
34408                        }
34409                }
34410                return known;
34411        }
34412
34413        /** Whether {@code table} is a table the script declares. */
34414        private boolean columnsDeclared(TTable table) {
34415                Object tableModel = modelManager.getModel(table);
34416                return tableModel instanceof Table && ((Table) tableModel).isCreateTable();
34417        }
34418
34419        /**
34420         * Whether a join of {@code stmt}'s own FROM clause merges columns, USING or NATURAL: a merged column stands before
34421         * the other columns of the tables it merges, not in their order (Codex review of Mantis 4975).
34422         */
34423        private boolean mergesJoinColumns(TSelectSqlStatement stmt) {
34424                if (!usingColumnsOf(stmt).isEmpty()) {
34425                        return true;
34426                }
34427                if (stmt.joins != null) {
34428                        for (int i = 0; i < stmt.joins.size(); i++) {
34429                                TJoin join = stmt.joins.getJoin(i);
34430                                for (int j = 0; join != null && join.getJoinItems() != null && j < join.getJoinItems().size(); j++) {
34431                                        TJoinItem item = join.getJoinItems().getJoinItem(j);
34432                                        if (item != null && isNaturalJoin(item.getJoinType())) {
34433                                                return true;
34434                                        }
34435                                }
34436                        }
34437                }
34438                Deque<TTable> pending = new ArrayDeque<TTable>();
34439                if (stmt.getRelations() != null) {
34440                        pending.addAll(stmt.getRelations());
34441                }
34442                while (!pending.isEmpty()) {
34443                        TTable relation = pending.pop();
34444                        if (relation == null || relation.getTableType() != ETableSource.join || relation.getJoinExpr() == null) {
34445                                continue;
34446                        }
34447                        TJoinExpr join = relation.getJoinExpr();
34448                        if (isNaturalJoin(join.getJointype())) {
34449                                return true;
34450                        }
34451                        if (join.getLeftTable() != null) {
34452                                pending.push(join.getLeftTable());
34453                        }
34454                        if (join.getRightTable() != null) {
34455                                pending.push(join.getRightTable());
34456                        }
34457                }
34458                return false;
34459        }
34460
34461        private static boolean isNaturalJoin(EJoinType type) {
34462                return type != null && type.name().startsWith("natural");
34463        }
34464
34465        /**
34466         * Expands the columns of {@code sourceTable}, one of the tables {@code column}, a star of a branch of a set
34467         * operation, reads, into result columns of the branch ({@link #expandSetBranchStar}); {@code earlierNames},
34468         * {@code names} and {@code earlierTables} carry the spellings and tables of the tables before it.
34469         */
34470        private void expandSetBranchStarTable(TSelectSqlStatement stmt, ResultSet resultSetModel, Object queryModel,
34471                        TResultColumn column, TTable sourceTable, Map<String, Pair<String, TExpression>> replaceAsIdentifierMap,
34472                        Map<String, TObjectName> replaceColumnMap, Set<String> earlierNames, Set<String> names,
34473                        List<TTable> earlierTables) {
34474                Object tableModel = modelManager.getModel(sourceTable);
34475                if (tableModel instanceof Table) {
34476                        for (TableColumn tableColumn : ((Table) tableModel).getColumns()) {
34477                                if (exceptedByStar(column, tableColumn.getName(), tableColumn.isStruct())) {
34478                                        continue;
34479                                }
34480                                if (replaceAsIdentifierMap.containsKey(tableColumn.getName())) {
34481                                        replaceStarColumn(resultSetModel, tableColumn.getName(), replaceAsIdentifierMap,
34482                                                        replaceColumnMap);
34483                                        continue;
34484                                }
34485                                String name = tableColumn.getName();
34486                                ResultColumn resultColumn;
34487                                if (!earlierNames.contains(name)) {
34488                                        resultColumn = modelFactory.createStarResultColumn(resultSetModel, column, name, tableColumn);
34489                                } else if (isUsingMerged(stmt, tableColumn, sourceTable, earlierTables)) {
34490                                        // one column of the join: the column an earlier table published reads this table's too
34491                                        resultColumn = modelFactory.createStarResultColumn(resultSetModel, column, name);
34492                                } else {
34493                                        name = unpublishedName(name, names);
34494                                        resultColumn = modelFactory.createStarResultColumn(resultSetModel, column, name);
34495                                        resultColumn.inheritStarColumn(tableColumn);
34496                                }
34497                                names.add(name);
34498                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
34499                                relation.setEffectType(EffectType.select);
34500                                relation.setTarget(new ResultColumnRelationshipElement(resultColumn));
34501                                relation.addSource(new TableColumnRelationshipElement(tableColumn));
34502                        }
34503                } else {
34504                        for (ResultColumn tableColumn : ((ResultSet) tableModel).getColumns()) {
34505                                if (exceptedByStar(column, tableColumn.getName(), tableColumn.isStruct())) {
34506                                        continue;
34507                                }
34508                                if (replaceAsIdentifierMap.containsKey(tableColumn.getName())) {
34509                                        replaceStarColumn(resultSetModel, tableColumn.getName(), replaceAsIdentifierMap,
34510                                                        replaceColumnMap);
34511                                        continue;
34512                                }
34513                                boolean referenced = tableColumn.getRefColumnName() != null;
34514                                ResultSet target = referenced ? (ResultSet) queryModel : resultSetModel;
34515                                String name = referenced ? tableColumn.getRefColumnName() : tableColumn.getName();
34516                                ResultColumn resultColumn;
34517                                if (!earlierNames.contains(name)) {
34518                                        resultColumn = modelFactory.createStarResultColumn(target, column, name, tableColumn);
34519                                } else {
34520                                        name = unpublishedName(name, names);
34521                                        resultColumn = modelFactory.createStarResultColumn(target, column, name);
34522                                        resultColumn.inheritStarColumn(tableColumn);
34523                                }
34524                                names.add(name);
34525                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
34526                                relation.setEffectType(EffectType.select);
34527                                relation.setTarget(new ResultColumnRelationshipElement(resultColumn));
34528                                relation.addSource(new ResultColumnRelationshipElement(tableColumn));
34529                        }
34530                }
34531                earlierNames.addAll(names);
34532                earlierTables.add(sourceTable);
34533        }
34534
34535        /**
34536         * Whether every branch of {@code setOperation} is one {@code *}, without EXCEPT or REPLACE, of the same tables in the
34537         * same order. A set operation takes its column names from its first branch and pairs the others by position; when
34538         * the branches read the same tables the same way, a column has the same position and name in each, so a name the
34539         * set operation's {@code *} may hold is the column of that name in every branch (Mantis 4974). Otherwise another
34540         * branch's column at that position may have any name.
34541         */
34542        private boolean branchesReadTheSameTables(TSelectSqlStatement setOperation) {
34543                if (setOperation == null) {
34544                        return false;
34545                }
34546                List<Object> first = null;
34547                Deque<TSelectSqlStatement> stack = new ArrayDeque<TSelectSqlStatement>();
34548                stack.push(setOperation);
34549                while (!stack.isEmpty()) {
34550                        TSelectSqlStatement branch = stack.pop();
34551                        if (branch.getSetOperatorType() != ESetOperatorType.none) {
34552                                if (branch.getRightStmt() != null) {
34553                                        stack.push(branch.getRightStmt());
34554                                }
34555                                if (branch.getLeftStmt() != null) {
34556                                        stack.push(branch.getLeftStmt());
34557                                }
34558                                continue;
34559                        }
34560                        TResultColumnList items = branch.getResultColumnList();
34561                        if (items == null || items.size() != 1 || !isStarItem(items.getResultColumn(0))) {
34562                                return false;
34563                        }
34564                        TResultColumn star = items.getResultColumn(0);
34565                        if (star.getExceptColumnList() != null || (star.getReplaceExprAsIdentifiers() != null
34566                                        && !star.getReplaceExprAsIdentifiers().isEmpty())) {
34567                                return false;
34568                        }
34569                        List<TTable> tables = star.getFieldAttr().getSourceTableList();
34570                        if (tables == null || tables.isEmpty()) {
34571                                return false;
34572                        }
34573                        List<Object> models = new ArrayList<Object>();
34574                        for (TTable table : tables) {
34575                                Object model = modelManager.getModel(table);
34576                                if (!(model instanceof Table)) {
34577                                        return false;
34578                                }
34579                                models.add(model);
34580                        }
34581                        if (first == null) {
34582                                first = models;
34583                        } else if (first.size() != models.size()) {
34584                                return false;
34585                        } else {
34586                                for (int i = 0; i < models.size(); i++) {
34587                                        if (first.get(i) != models.get(i)) {
34588                                                return false;
34589                                        }
34590                                }
34591                        }
34592                }
34593                return first != null;
34594        }
34595
34596        /** {@code name} followed by the first number that makes it a name the star has not published yet. */
34597        private static String unpublishedName(String name, Set<String> published) {
34598                for (int index = 1;; index++) {
34599                        if (!published.contains(name + index)) {
34600                                return name + index;
34601                        }
34602                }
34603        }
34604
34605        /** Whether the EXCEPT/EXCLUDE list of a star drops the column {@code name}, or a field of it when it is a struct. */
34606        private static boolean exceptedByStar(TResultColumn column, String name, boolean struct) {
34607                if (column.getExceptColumnList() == null) {
34608                        return false;
34609                }
34610                for (TObjectName objectName : column.getExceptColumnList()) {
34611                        if (DlineageUtil.sameColumnName(objectName.toString(), name)) {
34612                                return true;
34613                        }
34614                }
34615                if (struct) {
34616                        for (String part : SQLUtil.parseNames(name)) {
34617                                for (TObjectName objectName : column.getExceptColumnList()) {
34618                                        if (DlineageUtil.sameColumnName(objectName.toString(), part)) {
34619                                                return true;
34620                                        }
34621                                }
34622                        }
34623                }
34624                return false;
34625        }
34626
34627        /** The column {@code * REPLACE (expr AS name)} gives in place of the star's column {@code name}. */
34628        private void replaceStarColumn(ResultSet resultSetModel, String name,
34629                        Map<String, Pair<String, TExpression>> replaceAsIdentifierMap, Map<String, TObjectName> replaceColumnMap) {
34630                Pair<String, TExpression> expr = replaceAsIdentifierMap.get(name);
34631                ResultColumn resultColumn = modelFactory.createResultColumn(resultSetModel, replaceColumnMap.get(name));
34632                Transform transform = new Transform();
34633                transform.setType(Transform.EXPRESSION);
34634                TObjectName expression = new TObjectName();
34635                expression.setString(expr.first);
34636                transform.setCode(expression);
34637                resultColumn.setTransform(transform);
34638                analyzeResultColumnExpressionRelation(resultColumn, expr.second);
34639        }
34640
34641        private static boolean isStarItem(TResultColumn item) {
34642                return item != null && item.getExpr() != null
34643                                && item.getExpr().getExpressionType() == EExpressionType.simple_object_name_t
34644                                && DlineageUtil.isStarName(item.getExpr().getObjectOperand());
34645        }
34646
34647        /** Whether a model column's parse node (a name or a select-list item) is a star. */
34648        private static boolean isStarNode(TParseTreeNode node) {
34649                return node instanceof TObjectName ? DlineageUtil.isStarName((TObjectName) node)
34650                                : node instanceof TResultColumn && isStarItem((TResultColumn) node);
34651        }
34652
34653        /** Whether a model column's parse node is an unqualified star ({@code *}, not {@code t.*}). */
34654        private static boolean isBareStarNode(TParseTreeNode node) {
34655                TObjectName name = node instanceof TObjectName ? (TObjectName) node
34656                                : node instanceof TResultColumn && isStarItem((TResultColumn) node)
34657                                                ? ((TResultColumn) node).getExpr().getObjectOperand() : null;
34658                return name != null && DlineageUtil.isStarName(name) && name.getTableToken() == null;
34659        }
34660
34661        /** Whether a select list has a star item. */
34662        private static boolean hasStarItem(TResultColumnList list) {
34663                for (int i = 0; list != null && i < list.size(); i++) {
34664                        if (isStarItem(list.getResultColumn(i))) {
34665                                return true;
34666                        }
34667                }
34668                return false;
34669        }
34670
34671        /** Whether a select list's last item is a star. */
34672        private static boolean lastItemIsStar(TResultColumnList list) {
34673                return list != null && list.size() > 0 && isStarItem(list.getResultColumn(list.size() - 1));
34674        }
34675
34676        /** The table's star column, created when the table has none. */
34677        private TableColumn findOrCreateTableStarColumn(Table table) {
34678                for (TableColumn tc : table.getColumns()) {
34679                        if (tc.isStarColumn()) return tc;
34680                }
34681                return modelFactory.createTableColumn(table, DlineageUtil.syntheticStarName(), true);
34682        }
34683
34684        /** The result set's star column, created when it has none. */
34685        private ResultColumn findOrCreateResultStarColumn(ResultSet resultSet) {
34686                for (ResultColumn rc : resultSet.getColumns()) {
34687                        if (rc.isStarColumn()) return rc;
34688                }
34689                return modelFactory.createResultColumn(resultSet, DlineageUtil.syntheticStarName());
34690        }
34691
34692        private void getFunctionExpressions(List<TExpression> directExpressions, List<TExpression> indirectExpressions,
34693                        TFunctionCall functionCall) {
34694                if (functionCall.getArgs() != null) {
34695                        for (int k = 0; k < functionCall.getArgs().size(); k++) {
34696                                TExpression expr = functionCall.getArgs().getExpression(k);
34697                                if(FunctionUtility.isDirectRelation(option.getVendor(), functionCall.getFunctionName().toString(),  functionCall.getArgs().size(), k)) {
34698                                        directExpressions.add(expr);
34699                                }
34700                                if(FunctionUtility.isIndirectRelation(option.getVendor(), functionCall.getFunctionName().toString(),  functionCall.getArgs().size(), k)) {
34701                                        if("DECODE".equalsIgnoreCase(functionCall.getFunctionName().toString()) && option.getVendor() == EDbVendor.dbvoracle) {
34702                                                if(option.isShowCaseWhenAsDirect()) {
34703                                                        directExpressions.add(expr);
34704                                                        continue;
34705                                                }
34706                                        }
34707                                        indirectExpressions.add(expr);
34708                                }
34709                        }
34710                }
34711                collectOracleXmlFunctionExpressions(functionCall, directExpressions, indirectExpressions);
34712                collectArrayAggOrderByExpressions(functionCall, directExpressions, indirectExpressions);
34713                collectAggregateOrderByExpressions(functionCall, directExpressions, indirectExpressions);
34714                if (functionCall.getTrimArgument() != null) {
34715                        TTrimArgument args = functionCall.getTrimArgument();
34716                        TExpression expr = args.getStringExpression();
34717                        if (expr != null) {
34718                                directExpressions.add(expr);
34719                        }
34720                        expr = args.getTrimCharacter();
34721                        if (expr != null) {
34722                                directExpressions.add(expr);
34723                        }
34724                }
34725
34726                if (functionCall.getAgainstExpr() != null) {
34727                        directExpressions.add(functionCall.getAgainstExpr());
34728                }
34729                // split(s, ':')[n]: the element the subscript selects depends on n (Mantis 4817)
34730                directExpressions.addAll(functionCall.getSubscriptExpressions());
34731//              if (functionCall.getBetweenExpr() != null) {
34732//                      directExpressions.add(functionCall.getBetweenExpr());
34733//              }
34734                if (functionCall.getExpr1() != null) {
34735                        directExpressions.add(functionCall.getExpr1());
34736                }
34737                if (functionCall.getExpr2() != null) {
34738                        directExpressions.add(functionCall.getExpr2());
34739                }
34740                if (functionCall.getExpr3() != null) {
34741                        directExpressions.add(functionCall.getExpr3());
34742                }
34743                if (functionCall.getParameter() != null) {
34744                        directExpressions.add(functionCall.getParameter());
34745                }
34746                if (functionCall.getWindowDef() != null && functionCall.getWindowDef().getPartitionClause() != null) {
34747                        TExpressionList args = functionCall.getWindowDef().getPartitionClause().getExpressionList();
34748                        if (args != null) {
34749                                for (int k = 0; k < args.size(); k++) {
34750                                        TExpression expr = args.getExpression(k);
34751                                        if (expr != null) {
34752                                                indirectExpressions.add(expr);
34753                                        }
34754                                }
34755                        }
34756                }
34757                if (functionCall.getWindowDef() != null && functionCall.getWindowDef().getOrderBy() != null) {
34758                        TOrderByItemList orderByList = functionCall.getWindowDef().getOrderBy().getItems();
34759                        for (int i = 0; i < orderByList.size(); i++) {
34760                                TOrderByItem element = orderByList.getOrderByItem(i);
34761                                TExpression expression = element.getSortKey();
34762                                indirectExpressions.add(expression);
34763                        }
34764                }
34765                if (functionCall.getWithinGroup() != null && functionCall.getWithinGroup().getOrderBy() != null) {
34766                        TOrderByItemList orderByList = functionCall.getWithinGroup().getOrderBy().getItems();
34767                        for (int i = 0; i < orderByList.size(); i++) {
34768                                TOrderByItem element = orderByList.getOrderByItem(i);
34769                                TExpression expression = element.getSortKey();
34770                                indirectExpressions.add(expression);
34771                        }
34772                }
34773                if (functionCall.getCallTarget() != null) {
34774                        directExpressions.add(functionCall.getCallTarget().getExpr());
34775                }
34776                if (functionCall.getFieldValues() != null) {
34777                        for (int k = 0; k < functionCall.getFieldValues().size(); k++) {
34778                                TExpression expr = functionCall.getFieldValues().getResultColumn(k).getExpr();
34779                                directExpressions.add(expr);
34780                        }
34781                }
34782                if (functionCall instanceof TJsonObjectFunction) {
34783                        TJsonObjectFunction jsonObject = (TJsonObjectFunction)functionCall;
34784                        for (int k = 0; k < jsonObject.getKeyValues().size(); k++) {
34785                                TExpression expr = jsonObject.getKeyValues().get(k).getValue();
34786                                directExpressions.add(expr);
34787                        }
34788                }
34789                if (functionCall.getGroupConcatParam() != null) {
34790                        for (int k = 0; k < functionCall.getGroupConcatParam().getExprList().size(); k++) {
34791                                TExpression expr = functionCall.getGroupConcatParam().getExprList().getExpression(k);
34792                                directExpressions.add(expr);
34793                        }
34794                }
34795        }
34796
34797        private void getFunctionExpressions(List<TExpression> directExpressions, List<TExpression> indirectExpressions,
34798                                                                                TFunctionCall functionCall, int argumentIndex) {
34799                if (functionCall.getArgs() != null && argumentIndex < functionCall.getArgs().size()) {
34800                        TExpression expr = functionCall.getArgs().getExpression(argumentIndex);
34801                        if (FunctionUtility.isDirectRelation(option.getVendor(), functionCall.getFunctionName().toString(), functionCall.getArgs().size(), argumentIndex)) {
34802                                directExpressions.add(expr);
34803                        }
34804                        if (FunctionUtility.isIndirectRelation(option.getVendor(), functionCall.getFunctionName().toString(), functionCall.getArgs().size(), argumentIndex)) {
34805                                indirectExpressions.add(expr);
34806                        }
34807                }
34808        }
34809        
34810        private void getFunctionExpressions(List<TExpression> directExpressions, List<TExpression> indirectExpressions,
34811                        TCallStatement functionCall, int argumentIndex) {
34812                if (functionCall.getArgs() != null && argumentIndex < functionCall.getArgs().size()) {
34813                        TExpression expr = functionCall.getArgs().getExpression(argumentIndex);
34814                        if (FunctionUtility.isDirectRelation(option.getVendor(), functionCall.getRoutineName().toString(),
34815                                        functionCall.getArgs().size(), argumentIndex)) {
34816                                directExpressions.add(expr);
34817                        }
34818                        if (FunctionUtility.isIndirectRelation(option.getVendor(), functionCall.getRoutineName().toString(),
34819                                        functionCall.getArgs().size(), argumentIndex)) {
34820                                indirectExpressions.add(expr);
34821                        }
34822                }
34823        }
34824
34825        private void getFunctionExpressions(List<TExpression> directExpressions, List<TExpression> indirectExpressions,
34826                        TDb2CallStmt functionCall, int argumentIndex) {
34827                if (functionCall.getParameters() != null && argumentIndex < functionCall.getParameters().size()) {
34828                        TExpression expr = functionCall.getParameters().getExpression(argumentIndex);
34829                        if (FunctionUtility.isDirectRelation(option.getVendor(), functionCall.getProcedureName().toString(),
34830                                        functionCall.getParameters().size(), argumentIndex)) {
34831                                directExpressions.add(expr);
34832                        }
34833                        if (FunctionUtility.isIndirectRelation(option.getVendor(), functionCall.getProcedureName().toString(),
34834                                        functionCall.getParameters().size(), argumentIndex)) {
34835                                indirectExpressions.add(expr);
34836                        }
34837                }
34838        }
34839
34840        private void getFunctionExpressions(List<TExpression> directExpressions, List<TExpression> indirectExpressions,
34841                                                                                TMssqlExecute functionCall, String argumentName, int argumentIndex) {
34842                if (functionCall.getParameters() != null) {
34843                        for (int i = 0; i < functionCall.getParameters().size(); i++) {
34844                                TExecParameter param = functionCall.getParameters().getExecParameter(i);
34845                                if (param.getParameterName() != null) {
34846                                        if (DlineageUtil.compareColumnIdentifier(param.getParameterName().toString(), argumentName)) {
34847                                                TExpression expr = param.getParameterValue();
34848                                                directExpressions.add(expr);
34849                                        }
34850                                } else if (i == argumentIndex) {
34851                                        TExpression expr = param.getParameterValue();
34852                                        directExpressions.add(expr);
34853                                }
34854                        }
34855                }
34856        }
34857
34858        private void analyzeJoin(TJoin join, EffectType effectType) {
34859                if (join.getJoinItems() != null) {
34860                        for (int j = 0; j < join.getJoinItems().size(); j++) {
34861                                TJoinItem joinItem = join.getJoinItems().getJoinItem(j);
34862                                TExpression expr = joinItem.getOnCondition();
34863                                if (expr != null) {
34864                                        analyzeFilterCondition(null, expr, joinItem.getJoinType(), JoinClauseType.on, effectType);
34865                                }
34866                        }
34867                }
34868
34869                if (join.getJoin() != null) {
34870                        analyzeJoin(join.getJoin(), effectType);
34871                }
34872        }
34873
34874        private TSelectSqlStatement getParentSetSelectStmt(TSelectSqlStatement stmt) {
34875                TCustomSqlStatement parent = stmt.getParentStmt();
34876                if (parent == null)
34877                        return null;
34878                if (parent.getStatements() != null) {
34879                        for (int i = 0; i < parent.getStatements().size(); i++) {
34880                                TCustomSqlStatement temp = parent.getStatements().get(i);
34881                                if (temp instanceof TSelectSqlStatement) {
34882                                        TSelectSqlStatement select = (TSelectSqlStatement) temp;
34883                                        if (select.getLeftStmt() == stmt || select.getRightStmt() == stmt)
34884                                                return select;
34885                                }
34886                        }
34887                }
34888                if (parent instanceof TSelectSqlStatement) {
34889                        TSelectSqlStatement select = (TSelectSqlStatement) parent;
34890                        if (select.getLeftStmt() == stmt || select.getRightStmt() == stmt)
34891                                return select;
34892                }
34893                return null;
34894        }
34895
34896        private void createSelectSetResultColumns(SelectSetResultSet resultSet, TSelectSqlStatement stmt) {
34897                if (stmt.getSetOperatorType() != ESetOperatorType.none) {
34898                        createSelectSetResultColumns(resultSet, stmt.getLeftStmt());
34899                } else {
34900                        TResultColumnList columnList = stmt.getResultColumnList();
34901                        ResultSet subqueryResultSet = (ResultSet) modelManager.getModel(columnList);
34902                        if(subqueryResultSet!=null && subqueryResultSet.isDetermined()) {
34903                                for (int j = 0; j < subqueryResultSet.getColumns().size(); j++) {
34904                                        ResultColumn tableColumn = subqueryResultSet.getColumns().get(j);
34905                                        String name = tableColumn.getRefColumnName() != null
34906                                                        ? tableColumn.getRefColumnName() : tableColumn.getName();
34907                                        boolean emptyName = SQLUtil.isEmpty(name);
34908                                        if (emptyName) {
34909                                                // Empty delimited identifiers have no normalized name. Keep
34910                                                // their set-column ordinal and recover the lexical spelling.
34911                                                name = tableColumn.getFullName();
34912                                        }
34913                                        TObjectName columnName = copyColumnName(tableColumn, name == null ? "" : name);
34914                                        if (emptyName && tableColumn.getColumnObject() != null) {
34915                                                // Reuse the real span instead of publishing a synthetic 1:1
34916                                                // coordinate for the recovered spelling.
34917                                                columnName.setStartTokenDirectly(tableColumn.getColumnObject().getStartToken());
34918                                                columnName.setEndTokenDirectly(tableColumn.getColumnObject().getEndToken());
34919                                        }
34920                                        modelFactory.createDeterminedResultColumn(resultSet, columnName).inheritIdentifier(tableColumn);
34921                                }
34922                                resultSet.setDetermined(true);
34923                                return;
34924                        }
34925                        
34926                        boolean isDetermined = true;
34927                        for (int i = 0; i < columnList.size(); i++) {
34928                                TResultColumn column = columnList.getResultColumn(i);
34929                                
34930                                if (isStarItem(column)) {
34931                                        TObjectName columnObject = column.getFieldAttr();
34932                                        TTable sourceTable = columnObject.getSourceTable();
34933                                        if (starReadsSeveralTables(columnObject)) {
34934                                                // one select-set column for the star, as for a star of unknown columns (Mantis 4970)
34935                                                sourceTable = null;
34936                                                isDetermined = false;
34937                                        }
34938                                        if (sourceTable != null) {
34939                                                Object tableModel = modelManager.getModel(sourceTable);
34940                                                if (tableModel instanceof Table && ((Table) tableModel).isCreateTable()) {
34941                                                        Table table = (Table) tableModel;
34942                                                        for (int j = 0; j < table.getColumns().size(); j++) {
34943                                                                TableColumn tableColumn = table.getColumns().get(j);
34944                                                                if (column.getExceptColumnList() != null) {
34945                                                                        boolean except = false;
34946                                                                        for (TObjectName objectName : column.getExceptColumnList()) {
34947                                                                                if (DlineageUtil.sameColumnName(objectName.toString(), tableColumn.getName())) {
34948                                                                                        except = true;
34949                                                                                        break;
34950                                                                                }
34951                                                                        }
34952                                                                        if (!except && tableColumn.isStruct()) {
34953                                                                                List<String> names = SQLUtil
34954                                                                                                .parseNames(tableColumn.getName());
34955                                                                                for (String name : names) {
34956                                                                                        for (TObjectName objectName : column
34957                                                                                                        .getExceptColumnList()) {
34958                                                                                                if (DlineageUtil.sameColumnName(objectName.toString(), name)) {
34959                                                                                                        except = true;
34960                                                                                                        break;
34961                                                                                                }
34962                                                                                        }
34963                                                                                        if (except) {
34964                                                                                                break;
34965                                                                                        }
34966                                                                                }
34967                                                                        }
34968                                                                        if (except) {
34969                                                                                continue;
34970                                                                        }
34971                                                                }
34972                                                                TObjectName columnName = new TObjectName();
34973                                                                columnName.setString(tableColumn.getName());
34974                                                                ResultColumn resultColumn = modelFactory.createResultColumn(
34975                                                                                resultSet, columnName);
34976                                                                resultColumn.inheritIdentifier(tableColumn);
34977                                                                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
34978                                                                relation.setEffectType(EffectType.select);
34979                                                                relation.setTarget(new ResultColumnRelationshipElement(resultColumn));
34980                                                                relation.addSource(new TableColumnRelationshipElement(tableColumn));
34981                                                        }
34982                                                        continue;
34983                                                } else if (tableModel instanceof ResultSet
34984                                                                && ((ResultSet) tableModel).isDetermined()) {
34985                                                        ResultSet table = (ResultSet) tableModel;
34986                                                        for (int j = 0; j < table.getColumns().size(); j++) {
34987                                                                ResultColumn tableColumn = table.getColumns().get(j);
34988                                                                if (column.getExceptColumnList() != null) {
34989                                                                        boolean except = false;
34990                                                                        for (TObjectName objectName : column.getExceptColumnList()) {
34991                                                                                if (DlineageUtil.sameColumnName(objectName.toString(), tableColumn.getName())) {
34992                                                                                        except = true;
34993                                                                                        break;
34994                                                                                }
34995                                                                        }
34996                                                                        if (!except && tableColumn.isStruct()) {
34997                                                                                List<String> names = SQLUtil
34998                                                                                                .parseNames(tableColumn.getName());
34999                                                                                for (String name : names) {
35000                                                                                        for (TObjectName objectName : column
35001                                                                                                        .getExceptColumnList()) {
35002                                                                                                if (DlineageUtil.sameColumnName(objectName.toString(), name)) {
35003                                                                                                        except = true;
35004                                                                                                        break;
35005                                                                                                }
35006                                                                                        }
35007                                                                                        if (except) {
35008                                                                                                break;
35009                                                                                        }
35010                                                                                }
35011                                                                        }
35012                                                                        if (except) {
35013                                                                                continue;
35014                                                                        }
35015                                                                }
35016                                                                if (tableColumn.getRefColumnName() != null) {
35017                                                                        TObjectName columnName = new TObjectName();
35018                                                                        columnName.setString(tableColumn.getRefColumnName());
35019                                                                        ResultColumn resultColumn = modelFactory.createResultColumn(
35020                                                                                        resultSet, columnName);
35021                                                                        resultColumn.inheritIdentifier(tableColumn);
35022                                                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
35023                                                                        relation.setEffectType(EffectType.select);
35024                                                                        relation.setTarget(new ResultColumnRelationshipElement(resultColumn));
35025                                                                        relation.addSource(new ResultColumnRelationshipElement(tableColumn));
35026                                                                } else {
35027                                                                        TObjectName columnName = new TObjectName();
35028                                                                        columnName.setString(tableColumn.getName());
35029                                                                        ResultColumn resultColumn = modelFactory.createResultColumn(
35030                                                                                        resultSet, columnName);
35031                                                                        resultColumn.inheritIdentifier(tableColumn);
35032                                                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
35033                                                                        relation.setEffectType(EffectType.select);
35034                                                                        relation.setTarget(new ResultColumnRelationshipElement(resultColumn));
35035                                                                        relation.addSource(new ResultColumnRelationshipElement(tableColumn));
35036                                                                }
35037                                                        }
35038                                                        continue;
35039                                                }
35040                                                else {
35041                                                        isDetermined = false;
35042                                                }
35043                                        }
35044                                }
35045                                        
35046                                ResultColumn resultColumn = modelFactory.createSelectSetResultColumn(resultSet, column, i);
35047
35048                                if (resultColumn.getColumnObject() instanceof TResultColumn) {
35049                                        TResultColumn columnObject = (TResultColumn) resultColumn.getColumnObject();
35050                                        if (columnObject.getFieldAttr() != null) {
35051                                                if (DlineageUtil.isStarName((columnObject.getFieldAttr()))) {
35052                                                        TObjectName fieldAttr = columnObject.getFieldAttr();
35053                                                        TTable sourceTable = fieldAttr.getSourceTable();
35054                                                        if (fieldAttr.getTableToken() != null && sourceTable != null) {
35055                                                                TObjectName[] columns = modelManager.getTableColumns(sourceTable);
35056                                                                for (int j = 0; j < columns.length; j++) {
35057                                                                        TObjectName columnName = columns[j];
35058                                                                        if (columnName == null) {
35059                                                                                continue;
35060                                                                        }
35061                                                                        if (DlineageUtil.isStarName((columnName))) {
35062                                                                                continue;
35063                                                                        }
35064                                                                        resultColumn.bindStarLinkColumn(columnName);
35065                                                                }
35066
35067                                                                if (modelManager.getModel(sourceTable) instanceof Table) {
35068                                                                        Table tableModel = (Table) modelManager.getModel(sourceTable);
35069                                                                        if (tableModel != null && !tableModel.getColumns().isEmpty()) {
35070                                                                                for (int z = 0; z < tableModel.getColumns().size(); z++) {
35071                                                                                        if (tableModel.getColumns().get(z).isStarColumn()) {
35072                                                                                                continue;
35073                                                                                        }
35074                                                                                        resultColumn.bindStarLinkColumn(
35075                                                                                                        tableModel.getColumns().get(z).getColumnObject());
35076                                                                                }
35077                                                                        }
35078                                                                } else if (modelManager.getModel(sourceTable) instanceof QueryTable) {
35079                                                                        QueryTable tableModel = (QueryTable) modelManager.getModel(sourceTable);
35080                                                                        if (tableModel != null && !tableModel.getColumns().isEmpty()) {
35081                                                                                for (ResultColumn item : tableModel.getColumns()) {
35082                                                                                        if (item.hasStarLinkColumn()) {
35083                                                                                                for (TObjectName starLinkColumn : item.getStarLinkColumnList()) {
35084                                                                                                        if (DlineageUtil.isStarName((starLinkColumn))) {
35085                                                                                                                continue;
35086                                                                                                        }
35087                                                                                                        resultColumn.bindStarLinkColumn(starLinkColumn);
35088                                                                                                }
35089                                                                                        } else if (item.getColumnObject() instanceof TObjectName) {
35090                                                                                                TObjectName starLinkColumn = (TObjectName) item.getColumnObject();
35091                                                                                                if (DlineageUtil.isStarName((starLinkColumn))) {
35092                                                                                                        continue;
35093                                                                                                }
35094                                                                                                resultColumn.bindStarLinkColumn(starLinkColumn);
35095                                                                                        }
35096                                                                                }
35097                                                                        }
35098                                                                }
35099
35100                                                        } else {
35101                                                                TTableList tables = stmt.getTables();
35102                                                                for (int k = 0; k < tables.size(); k++) {
35103                                                                        TTable tableElement = tables.getTable(k);
35104                                                                        TObjectName[] columns = modelManager.getTableColumns(tableElement);
35105                                                                        for (int j = 0; j < columns.length; j++) {
35106                                                                                TObjectName columnName = columns[j];
35107                                                                                if (columnName == null) {
35108                                                                                        continue;
35109                                                                                }
35110                                                                                if (DlineageUtil.isStarName((columnName))) {
35111                                                                                        if (modelManager.getModel(tableElement) instanceof Table) {
35112                                                                                                Table tableModel = (Table) modelManager.getModel(tableElement);
35113                                                                                                if (tableModel != null && !tableModel.getColumns().isEmpty()) {
35114                                                                                                        for (int z = 0; z < tableModel.getColumns().size(); z++) {
35115                                                                                                                resultColumn.bindStarLinkColumn(
35116                                                                                                                                tableModel.getColumns().get(z).getColumnObject());
35117                                                                                                        }
35118                                                                                                }
35119                                                                                        } else if (modelManager.getModel(tableElement) instanceof QueryTable) {
35120                                                                                                QueryTable tableModel = (QueryTable) modelManager
35121                                                                                                                .getModel(tableElement);
35122                                                                                                if (tableModel != null && !tableModel.getColumns().isEmpty()) {
35123                                                                                                        for (ResultColumn item : tableModel.getColumns()) {
35124                                                                                                                if (item.hasStarLinkColumn()) {
35125                                                                                                                        for (TObjectName starLinkColumn : item
35126                                                                                                                                        .getStarLinkColumnList()) {
35127                                                                                                                                resultColumn.bindStarLinkColumn(starLinkColumn);
35128                                                                                                                        }
35129                                                                                                                } else if (item.getColumnObject() instanceof TObjectName) {
35130                                                                                                                        resultColumn.bindStarLinkColumn(
35131                                                                                                                                        (TObjectName) item.getColumnObject());
35132                                                                                                                }
35133                                                                                                        }
35134                                                                                                }
35135                                                                                        }
35136                                                                                        continue;
35137                                                                                }
35138                                                                                resultColumn.bindStarLinkColumn(columnName);
35139                                                                        }
35140                                                                }
35141                                                        }
35142                                                }
35143                                        }
35144                                }
35145                                
35146                                resultSet.setDetermined(isDetermined);
35147                        }
35148                }
35149        }
35150
35151        private void analyzeResultColumn(TResultColumn column, EffectType effectType) {
35152                // A SELECT * star column whose source resolves to a determined result set
35153                // is bound to a LinkedHashMap of per-column ResultColumns (see
35154                // ModelFactory.createStarResultColumn), and its data-flow relationships
35155                // are already created inline during star expansion in analyzeSelectStmt().
35156                // Re-analyzing the raw "*" here is redundant; skip it. (analyzeDataFlowRelation
35157                // keeps the same guard as a defensive backstop.)
35158                if (modelManager.getModel(column) instanceof LinkedHashMap) {
35159                        return;
35160                }
35161                TExpression expression = column.getExpr();
35162                if (expression.getExpressionType() == EExpressionType.sqlserver_proprietary_column_alias_t) {
35163                        expression = expression.getRightOperand();
35164                }
35165
35166                if (expression.getExpressionType() == EExpressionType.array_t) {
35167                        if (expression.getExprList() != null) {
35168                                for (TExpression expr : expression.getExprList()) {
35169                                        columnsInExpr visitor = new columnsInExpr();
35170                                        expr.inOrderTraverse(visitor);
35171                                        List<TObjectName> objectNames = visitor.getObjectNames();
35172
35173                                        List<TParseTreeNode> functions = visitor.getFunctions();
35174
35175                                        if (functions != null && !functions.isEmpty()) {
35176                                                analyzeFunctionDataFlowRelation(column, functions, effectType);
35177                                        }
35178
35179                                        List<TSelectSqlStatement> subquerys = visitor.getSubquerys();
35180                                        if (subquerys != null && !subquerys.isEmpty()) {
35181                                                analyzeSubqueryDataFlowRelation(column, subquerys, effectType);
35182                                        }
35183
35184                                        analyzeDataFlowRelation(column, objectNames, column.getExceptColumnList(), effectType, functions);
35185
35186                                        List<TParseTreeNode> constants = visitor.getConstants();
35187                                        Object columnObject = modelManager.getModel(column);
35188                                        analyzeConstantDataFlowRelation(columnObject, constants, effectType, functions);
35189
35190                                        analyzeRecordSetRelation(functions, effectType);
35191                                        // analyzeResultColumnImpact( column, effectType, functions);
35192                                }
35193                        }
35194                        else {
35195                                List<TParseTreeNode> constants = new ArrayList<TParseTreeNode>();
35196                                TConstant constant = new TConstant();
35197                                constant.setString(expression.toString());
35198                                constants.add(constant);
35199                                Object columnObject = modelManager.getModel(column);
35200                                analyzeConstantDataFlowRelation(columnObject, constants, effectType, null);
35201                        }
35202                } else {
35203                        columnsInExpr visitor = new columnsInExpr();
35204                        expression.inOrderTraverse(visitor);
35205                        List<TObjectName> objectNames = visitor.getObjectNames();
35206
35207                        List<TParseTreeNode> functions = visitor.getFunctions();
35208
35209                        if (functions != null && !functions.isEmpty()) {
35210                                analyzeFunctionDataFlowRelation(column, functions, effectType);
35211                        }
35212
35213                        List<TSelectSqlStatement> subquerys = visitor.getSubquerys();
35214                        if (subquerys != null && !subquerys.isEmpty()) {
35215                                analyzeSubqueryDataFlowRelation(column, subquerys, effectType);
35216                        }
35217
35218                        analyzeDataFlowRelation(column, objectNames, column.getExceptColumnList(), effectType, functions);
35219
35220                        List<TParseTreeNode> constants = visitor.getConstants();
35221                        Object columnObject = modelManager.getModel(column);
35222                        analyzeConstantDataFlowRelation(columnObject, constants, effectType, functions);
35223
35224                        analyzeRecordSetRelation(functions, effectType);
35225                        // analyzeResultColumnImpact( column, effectType, functions);
35226                }
35227        }
35228        
35229        
35230        /**
35231         * A VALUES derived table that parses as a subquery, e.g. PostgreSQL, Athena and Databricks
35232         * {@code LEFT JOIN (VALUES (0, 'G'), (1, 'I')) AS r(rank, type)}: its select has a value clause and no
35233         * result column list, so it gets no result set model. Each row feeds the alias columns by position, the
35234         * same way the {@link ETableSource#rowList} branch handles SQL Server and Snowflake VALUES tables
35235         * (Mantis 4802). Without an alias column list the columns take the vendor's default names,
35236         * {@link TValueClause#defaultColumnName}: PostgreSQL column1, column2, ... (Mantis 4823).
35237         */
35238        private void analyzeValuesDerivedTable(QueryTable queryTable, TValueClause valueClause) {
35239                List<TResultColumnList> rows = valueClause.getRows();
35240                if (rows == null || rows.isEmpty()) {
35241                        return;
35242                }
35243                if (queryTable.getColumns().isEmpty() && rows.get(0) != null) {
35244                        for (int j = 1; j <= rows.get(0).size(); j++) {
35245                                String name = TValueClause.defaultColumnName(option.getVendor(), j);
35246                                if (name == null) {
35247                                        break;
35248                                }
35249                                TObjectName columnName = new TObjectName();
35250                                columnName.setString(name);
35251                                modelFactory.createResultColumn(queryTable, columnName, true);
35252                        }
35253                }
35254                if (queryTable.getColumns().isEmpty()) {
35255                        return;
35256                }
35257                for (TResultColumnList row : rows) {
35258                        if (row == null) {
35259                                continue;
35260                        }
35261                        for (int j = 0; j < row.size() && j < queryTable.getColumns().size(); j++) {
35262                                analyzeValueColumn(queryTable.getColumns().get(j), row.getResultColumn(j), EffectType.select);
35263                        }
35264                }
35265        }
35266
35267        /**
35268         * Mantis 5010: the columns of a CTE defined by VALUES, as UPDATE ... FROM reads it, or a query (Mantis 5017).
35269         * Position j of every row feeds the column the list names at j or, past the list, the column of the vendor's
35270         * default name for j ({@link TValueClause#defaultColumnName}; PostgreSQL keeps those names after a shorter list). A
35271         * column a reference made earlier is found by its name, not by its place among the CTE's columns, which is the
35272         * order of the reads. The relations into the columns belong to {@code process}, as the UPDATE's other CTE relations
35273         * do; a query passes null.
35274         */
35275        private void analyzeValuesCte(QueryTable queryTable, TValueClause valueClause, TObjectNameList cteColumns,
35276                        Process process) {
35277                List<TResultColumnList> rows = valueClause.getRows();
35278                if (rows == null || rows.isEmpty() || rows.get(0) == null) {
35279                        return;
35280                }
35281                List<ResultColumn> positions = new ArrayList<ResultColumn>();
35282                for (int j = 0; j < rows.get(0).size(); j++) {
35283                        TObjectName name = null;
35284                        if (cteColumns != null && j < cteColumns.size()) {
35285                                name = cteColumns.getObjectName(j);
35286                        } else if (TValueClause.defaultColumnName(option.getVendor(), j + 1) != null) {
35287                                name = new TObjectName();
35288                                name.setString(TValueClause.defaultColumnName(option.getVendor(), j + 1));
35289                        }
35290                        ResultColumn column = name == null ? null : matchResultColumn(queryTable.getColumns(), name);
35291                        if (name != null && column == null) {
35292                                column = modelFactory.createResultColumn(queryTable, name, true);
35293                        }
35294                        positions.add(column);
35295                }
35296                int relationsBefore = modelManager.getRelations().length;
35297                for (TResultColumnList row : rows) {
35298                        for (int j = 0; row != null && j < row.size() && j < positions.size(); j++) {
35299                                if (positions.get(j) != null) {
35300                                        analyzeValueColumn(positions.get(j), row.getResultColumn(j), EffectType.select);
35301                                }
35302                        }
35303                }
35304                setProcessOfRelationsInto(relationsBefore, positions, process);
35305        }
35306
35307        /**
35308         * Mantis 5015: SET p = (VALUES (...)) and SET (p, q) = (VALUES (...)) assign from a VALUES list, which has no result
35309         * set model: item {@code position} of each row feeds {@code tableColumn}, as the rows of a VALUES derived table feed
35310         * its columns, in the UPDATE's process. Reading the model threw NullPointerException.
35311         */
35312        private void analyzeUpdateSetValues(TableColumn tableColumn, TValueClause valueClause, int position,
35313                        Process process) {
35314                List<TResultColumnList> rows = valueClause.getRows();
35315                if (rows == null) {
35316                        return;
35317                }
35318                int relationsBefore = modelManager.getRelations().length;
35319                for (TResultColumnList row : rows) {
35320                        if (row != null && position < row.size()) {
35321                                analyzeValueColumn(tableColumn, row.getResultColumn(position), EffectType.update);
35322                        }
35323                }
35324                setProcessOfRelationsInto(relationsBefore, Collections.singletonList(tableColumn), process);
35325        }
35326
35327        /** Gives {@code process} to the relations made since {@code relationsBefore} into one of {@code targets} without one. */
35328        private void setProcessOfRelationsInto(int relationsBefore, List<?> targets, Process process) {
35329                Relationship[] relations = modelManager.getRelations();
35330                for (int i = relationsBefore; i < relations.length; i++) {
35331                        if (!(relations[i] instanceof AbstractRelationship) || relations[i].getTarget() == null
35332                                        || ((AbstractRelationship) relations[i]).getProcess() != null) {
35333                                continue;
35334                        }
35335                        Object target = relations[i].getTarget().getElement();
35336                        for (Object column : targets) {
35337                                if (column != null && column == target) {
35338                                        ((AbstractRelationship) relations[i]).setProcess(process);
35339                                        break;
35340                                }
35341                        }
35342                }
35343        }
35344
35345        private void analyzeValueColumn(Object object, TResultColumn column, EffectType effectType) {
35346                TExpression expression = column.getExpr();
35347                if (expression.getExpressionType() == EExpressionType.sqlserver_proprietary_column_alias_t) {
35348                        expression = expression.getRightOperand();
35349                }
35350
35351                if (expression.getExpressionType() == EExpressionType.array_t) {
35352                        if (expression.getExprList() != null) {
35353                                for (TExpression expr : expression.getExprList()) {
35354                                        analyzeValueExpression(object, expr, column, effectType);
35355                                }
35356                        }
35357                        else {
35358                                List<TParseTreeNode> constants = new ArrayList<TParseTreeNode>();
35359                                TConstant constant = new TConstant();
35360                                constant.setString(expression.toString());
35361                                constants.add(constant);
35362                                Object columnObject = modelManager.getModel(column);
35363                                analyzeConstantDataFlowRelation(object, constants, effectType, null);
35364                        }
35365                } else {
35366                        analyzeValueExpression(object, expression, column, effectType);
35367                }
35368        }
35369
35370        /** The inputs of one value of a VALUES row, or of one element of an ARRAY in it, into {@code object}. */
35371        private void analyzeValueExpression(Object object, TExpression expression, TResultColumn column, EffectType effectType) {
35372                columnsInExpr visitor = new columnsInExpr();
35373                expression.inOrderTraverse(visitor);
35374                // A function in a VALUES row, e.g. (VALUES (COALESCE((SELECT x FROM t), 0))) AS r(a), gives the column its
35375                // value as in a SELECT list: its arguments, a scalar subquery or a column, feed it (Mantis 4824)
35376                List<TParseTreeNode> functions = visitor.getFunctions();
35377                if (functions != null && !functions.isEmpty()) {
35378                        analyzeFunctionDataFlowRelation(object, functions, effectType);
35379                }
35380                // A scalar subquery in a VALUES row, e.g. (VALUES ((SELECT max(p) FROM t), 'G')) AS r(rank, type),
35381                // is the value of that column: link its result (Mantis 4802)
35382                List<TSelectSqlStatement> subquerys = visitor.getSubquerys();
35383                if (subquerys != null && !subquerys.isEmpty()) {
35384                        analyzeSubqueryDataFlowRelation(object, subquerys, effectType);
35385                }
35386                List<TObjectName> objectNames = visitor.getObjectNames();
35387                analyzeDataFlowRelation(object, objectNames, column.getExceptColumnList(), effectType, null, null);
35388                List<TParseTreeNode> constants = visitor.getConstants();
35389                analyzeConstantDataFlowRelation(object, constants, effectType, null);
35390        }
35391
35392        private void analyzeTableColumn(TableColumn tableColumn, TFunctionCall functionCall, EffectType effectType) {
35393                // SQL-LR: a nested analysis never inherits the reference of the loop that called it
35394                SqllrObservations sqllr = modelManager.getSqllrObservations();
35395                int sqllrBarrier = sqllr == null ? -1 : sqllr.openFrame();
35396                try {
35397                        analyzeTableColumnInSqllrBarrier(tableColumn, functionCall, effectType);
35398                } finally {
35399                        if (sqllr != null) {
35400                                sqllr.closeFrame(sqllrBarrier);
35401                        }
35402                }
35403        }
35404
35405        private void analyzeTableColumnInSqllrBarrier(TableColumn tableColumn, TFunctionCall functionCall, EffectType effectType) {
35406                // ClickHouse external table functions are modelled as source TABLES
35407                // (modelClickhouseExternalTableFunction); running the generic
35408                // function-argument analysis on top fabricates argument-named columns
35409                // on that table (remote('h', mydb, mytable) invented a column "mydb")
35410                // and re-attaches the generic function node the modelling replaced.
35411                if (option.getVendor() == EDbVendor.dbvclickhouse && functionCall != null
35412                                && functionCall.getFunctionName() != null) {
35413                        String rawName = functionCall.getFunctionName().toString();
35414                        if (isClickhouseTableFunction(rawName, "remote")
35415                                        || isClickhouseTableFunction(rawName, "remoteSecure")
35416                                        || isClickhouseTableFunction(rawName, "file")
35417                                        || isClickhouseTableFunction(rawName, "s3")
35418                                        || isClickhouseTableFunction(rawName, "url")
35419                                        || isClickhouseTableFunction(rawName, "hdfs")
35420                                        || isClickhouseTableFunction(rawName, "fileCluster")
35421                                        || isClickhouseTableFunction(rawName, "s3Cluster")
35422                                        || isClickhouseTableFunction(rawName, "urlCluster")
35423                                        || isClickhouseTableFunction(rawName, "hdfsCluster")
35424                                        || isClickhouseTableFunction(rawName, "azureBlobStorage")
35425                                        || isClickhouseTableFunction(rawName, "azureBlobStorageCluster")) {
35426                                return;
35427                        }
35428                }
35429                List<TParseTreeNode> functions = new ArrayList<TParseTreeNode>();
35430                functions.add(functionCall);
35431
35432                if (functions != null && !functions.isEmpty()) {
35433                        analyzeFunctionDataFlowRelation(tableColumn, functions, effectType);
35434                }
35435
35436                analyzeRecordSetRelation(functions, effectType);
35437        }
35438
35439        private void analyzeRecordSetRelation(List<TParseTreeNode> functions, EffectType effectType) {
35440                analyzeRecordSetRelation(functions, effectType, false);
35441        }
35442
35443        /**
35444         * The row influence of each aggregate in {@code functions}: the GROUP BY keys, the HAVING clause and the rows of
35445         * the statement. {@code inHaving}: the aggregates are those of the HAVING clause itself, which therefore is not
35446         * one of their inputs.
35447         */
35448        private void analyzeRecordSetRelation(List<TParseTreeNode> functions, EffectType effectType, boolean inHaving) {
35449                if (functions == null || functions.size() == 0)
35450                        return;
35451
35452                List<TFunctionCall> aggregateFunctions = new ArrayList<TFunctionCall>();
35453                for (TParseTreeNode function : functions) {
35454                        if (function instanceof TFunctionCall && isAggregateFunction((TFunctionCall) function)) {
35455                                aggregateFunctions.add((TFunctionCall) function);
35456                        }
35457                }
35458
35459                if (aggregateFunctions.size() == 0)
35460                        return;
35461
35462                for (int i = 0; i < aggregateFunctions.size(); i++) {
35463                        TFunctionCall function = aggregateFunctions.get(i);
35464
35465                        TCustomSqlStatement stmt = stmtStack.peek();
35466                        if (stmt instanceof TSelectSqlStatement) {
35467                                TSelectSqlStatement select = (TSelectSqlStatement) stmt;
35468                                if (select.getGroupByClause() != null) {
35469                                        if (select.getGroupByClause().isAllModifier()) {
35470                                                // GROUP BY ALL: implicit grouping columns are the
35471                                                // non-aggregate expressions in the SELECT list.
35472                                                TResultColumnList resultColumns = select.getResultColumnList();
35473                                                if (resultColumns != null) {
35474                                                        for (int j = 0; j < resultColumns.size(); j++) {
35475                                                                TResultColumn column = resultColumns.getResultColumn(j);
35476                                                                TExpression expr = column.getExpr();
35477                                                                if (expr == null)
35478                                                                        continue;
35479                                                                columnsInExpr aggVisitor = new columnsInExpr();
35480                                                                expr.inOrderTraverse(aggVisitor);
35481                                                                boolean containsAggregate = false;
35482                                                                for (TParseTreeNode funcNode : aggVisitor.getFunctions()) {
35483                                                                        if (funcNode instanceof TFunctionCall
35484                                                                                        && isAggregateFunction((TFunctionCall) funcNode)) {
35485                                                                                containsAggregate = true;
35486                                                                                break;
35487                                                                        }
35488                                                                }
35489                                                                if (!containsAggregate) {
35490                                                                        analyzeAggregate(function, expr);
35491                                                                }
35492                                                        }
35493                                                }
35494                                        } else {
35495                                                TGroupByItemList groupByList = select.getGroupByClause().getItems();
35496                                                for (int j = 0; j < groupByList.size(); j++) {
35497                                                        TGroupByItem groupBy = groupByList.getGroupByItem(j);
35498                                                        TExpression expr = groupBy.getExpr();
35499                                                        analyzeAggregate(function, expr);
35500                                                }
35501                                        }
35502
35503                                        if (!inHaving && select.getGroupByClause().getHavingClause() != null) {
35504                                                analyzeAggregate(function, select.getGroupByClause().getHavingClause());
35505                                        }
35506                                        // if ("COUNT".equalsIgnoreCase(function.getFunctionName().toString()))
35507                                        {
35508                                                analyzeAggregate(function, null);
35509                                        }
35510                                } else {
35511                                        analyzeAggregate(function, null);
35512                                }
35513                        }
35514                }
35515        }
35516
35517        private void analyzeDataFlowRelation(TParseTreeNode gspObject, List<TObjectName> objectNames,
35518                        TObjectNameList exceptColumnList, EffectType effectType, List<TParseTreeNode> functions) {
35519                Object columnObject = modelManager.getModel(gspObject);
35520                analyzeDataFlowRelation(columnObject, objectNames, exceptColumnList, effectType, functions, null);
35521        }
35522
35523        private DataFlowRelationship analyzeDataFlowRelation(Object modelObject, List<TObjectName> objectNames, EffectType effectType,
35524                        List<TParseTreeNode> functions) {
35525                return analyzeDataFlowRelation(modelObject, objectNames, null, effectType, functions, null);
35526        }
35527        
35528        /**
35529         * Whether a column reference that no table exposes names this result column of its own SELECT block, by name.
35530         * Never for SAP IQ and SQL Anywhere: they take only the alias the resolvers bound the reference to, before any
35531         * table lookup ({@link #getBoundAliasColumn}); matching by name would also take a variable, a {@code @@} global or
35532         * the target of a {@code SELECT @v = x} assignment for an alias (Mantis 4752).
35533         */
35534        private boolean isAliasReference(TObjectName columnName, ResultColumn resultColumn) {
35535                if (TSQLEnv.isAliasOccludingColumn(option.getVendor())) {
35536                        return false;
35537                }
35538                return DlineageUtil.sameColumnName(columnName, resultColumn.getName());
35539        }
35540
35541        /**
35542         * SAP IQ and SQL Anywhere: the result column of the SELECT-list alias the resolvers bound this unqualified name to
35543         * ({@link TObjectName#getSourceColumn()}, from {@code TResultColumnList.findLateralAliasDefinition} for an alias
35544         * of its own SELECT block, {@code TSelectSqlStatement.findEnclosingAliasDefinition} for one of an enclosing
35545         * block), or null. The alias occludes a column of the same name, so it is taken before any table lookup
35546         * (Mantis 4752). Likewise for a HAVING name in the dialects of {@link TSQLEnv#isAliasVisibleInHaving}, which the
35547         * resolvers bind to the alias only when no table column of that name is proven.
35548         */
35549        private ResultColumn getBoundAliasColumn(TObjectName columnName, Object modelObject) {
35550                if (!TSQLEnv.isAliasOccludingColumn(option.getVendor())
35551                                && !(columnName.getLocation() == ESqlClause.having
35552                                                && TSQLEnv.isAliasVisibleInHaving(option.getVendor(), columnName.getGsqlparser()))) {
35553                        return null;
35554                }
35555                TResultColumn alias = columnName.getSourceColumn();
35556                if (alias == null || columnName.getSourceTable() != null || !alias.isMatchedUsingAlias(columnName)) {
35557                        return null;
35558                }
35559                Object model = modelManager.getModel(alias);
35560                if (!(model instanceof ResultColumn) || model.equals(modelObject)) {
35561                        return null;
35562                }
35563                return (ResultColumn) model;
35564        }
35565
35566        private DataFlowRelationship analyzeDataFlowRelation(Object modelObject, List<TObjectName> objectNames, EffectType effectType,
35567                        List<TParseTreeNode> functions, Process process) {
35568                return analyzeDataFlowRelation(modelObject, objectNames, null, effectType, functions, process);
35569        }
35570
35571        private DataFlowRelationship analyzeDataFlowRelation(Object modelObject, List<TObjectName> objectNames,
35572                        TObjectNameList exceptColumnList, EffectType effectType, List<TParseTreeNode> functions, Process process) {
35573                return analyzeDataFlowRelation(modelObject, objectNames, exceptColumnList, effectType, functions, process, null);
35574        }
35575        
35576        private DataFlowRelationship analyzeDataFlowRelation(Object modelObject, List<TObjectName> objectNames,
35577                        TObjectNameList exceptColumnList, EffectType effectType, List<TParseTreeNode> functions, Process process, Integer valueIndex) {
35578                // SQL-LR: a nested analysis never inherits the reference of the loop that called it
35579                SqllrObservations sqllr = modelManager.getSqllrObservations();
35580                int sqllrBarrier = sqllr == null ? -1 : sqllr.openFrame();
35581                try {
35582                        return analyzeDataFlowRelationInSqllrBarrier(modelObject, objectNames, exceptColumnList, effectType, functions, process,
35583                                        valueIndex);
35584                } finally {
35585                        if (sqllr != null) {
35586                                sqllr.closeFrame(sqllrBarrier);
35587                        }
35588                }
35589        }
35590
35591        private DataFlowRelationship analyzeDataFlowRelationInSqllrBarrier(Object modelObject, List<TObjectName> objectNames,
35592                        TObjectNameList exceptColumnList, EffectType effectType, List<TParseTreeNode> functions, Process process, Integer valueIndex) {
35593                if (objectNames == null || objectNames.size() == 0)
35594                        return null;
35595
35596                // Reject model objects this method cannot turn into a relationship BEFORE
35597                // creating one, since createDataFlowRelation() registers the relation
35598                // globally and an early return afterwards would leak an empty relation.
35599                //
35600                // - LinkedHashMap: a SELECT * star column whose source resolves to a
35601                //   determined result set is expanded into per-column ResultColumns held
35602                //   in a LinkedHashMap by ModelFactory.createStarResultColumn(), with its
35603                //   data-flow relationships created inline during star expansion in
35604                //   analyzeSelectStmt(). Re-analyzing the raw "*" here is a no-op.
35605                // - null: modelManager.getModel() found no binding for the gsp object.
35606                if (modelObject == null || modelObject instanceof LinkedHashMap) {
35607                        return null;
35608                }
35609                // Lineage is best-effort: an unexpected model type should not abort the
35610                // whole statement's lineage (this used to throw UnsupportedOperationException).
35611                // Log it for diagnosis and skip just this column instead.
35612                if (!(modelObject instanceof ResultColumn) && !(modelObject instanceof TableColumn)) {
35613                        logger.warn("analyzeDataFlowRelation: unhandled model type "
35614                                        + modelObject.getClass().getName() + ", effectType=" + effectType);
35615                        return null;
35616                }
35617
35618                boolean isStar = false;
35619                boolean showStar = false;
35620
35621                DataFlowRelationship relation = modelFactory.createDataFlowRelation();
35622                relation.setEffectType(effectType);
35623                relation.setProcess(process);
35624
35625                if (functions != null && !functions.isEmpty()) {
35626                        relation.setFunction(getFunctionName(functions.get(0)));
35627                }
35628
35629                int columnIndex = -1;
35630
35631                boolean isOut = false;
35632
35633                if (modelObject instanceof ResultColumn) {
35634                        relation.setTarget(new ResultColumnRelationshipElement((ResultColumn) modelObject));
35635
35636                        if (((ResultColumn) modelObject).isStarColumn()) {
35637                                isStar = true;
35638                                showStar = ((ResultColumn) modelObject).isShowStar();
35639                        }
35640
35641                        if (((ResultColumn) modelObject).getResultSet() != null) {
35642                                columnIndex = ((ResultColumn) modelObject).getResultSet().getColumns().indexOf(modelObject);
35643                        }
35644                } else if (modelObject instanceof TableColumn) {
35645                        Table table  = ((TableColumn) modelObject).getTable();
35646                        if(table.getSubType() == SubType.out && isNotInProcedure(table)){
35647                                isOut = true;
35648                                relation.addSource(new TableColumnRelationshipElement((TableColumn) modelObject));
35649                        }
35650                        else {
35651                                relation.setTarget(new TableColumnRelationshipElement((TableColumn) modelObject));
35652                        }
35653
35654                        if (((TableColumn) modelObject).isStarColumn()) {
35655                                isStar = true;
35656                        }
35657
35658                        if (((TableColumn) modelObject).getTable() != null) {
35659                                columnIndex = ((TableColumn) modelObject).getTable().getColumns().indexOf(modelObject);
35660                        }
35661                }
35662                // No trailing else: modelObject is guaranteed to be a ResultColumn or
35663                // TableColumn here (validated and logged above before relation creation).
35664
35665                SqllrObservations sqllrRefs = modelManager.getSqllrObservations();
35666                int sqllrFrame = sqllrRefs == null ? -1 : sqllrRefs.openFrame(modelObject);
35667                try { // SQL-LR: each element added in this loop is recorded with the reference of its iteration
35668                for (int i = 0; i < objectNames.size(); i++) {
35669                        TObjectName columnName = objectNames.get(i);
35670                        if (sqllrRefs != null) {
35671                                sqllrRefs.reference(sqllrFrame, columnName);
35672                        }
35673                        ResultColumn boundAlias = getBoundAliasColumn(columnName, modelObject);
35674                        if (boundAlias != null) {
35675                                relation.addSource(new ResultColumnRelationshipElement(boundAlias));
35676                                continue;
35677                        }
35678                        if (columnName.toString().indexOf(".") == -1 && isConstant(columnName)) {
35679                                boolean isConstant = true;
35680                                if (columnName.getSourceTable() != null) {
35681                                        Table tableModel = modelManager.getTableByName(
35682                                                        DlineageUtil.getTableIdentityKey(columnName.getSourceTable().getTableName().toString()));
35683                                        if (tableModel != null && tableModel.getColumns() != null) {
35684                                                for (int j = 0; j < tableModel.getColumns().size(); j++) {
35685                                                        if (sameTableColumn(columnName, tableModel.getColumns().get(j))) {
35686                                                                isConstant = false;
35687                                                                break;
35688                                                        }
35689                                                }
35690                                        }
35691                                }
35692
35693                                if (isConstant) {
35694                                        if (option.isShowConstantTable()) {
35695                                                Table constantTable = modelFactory.createConstantsTable(stmtStack.peek());
35696                                                TableColumn tableColumn = modelFactory.createTableColumn(constantTable, columnName, false);
35697                                                if(tableColumn!=null) {
35698                                                        relation.addSource(new TableColumnRelationshipElement(tableColumn));
35699                                                }
35700                                        }
35701                                        continue;
35702                                }
35703                        }
35704                        if (columnName.getDbObjectType() == EDbObjectType.variable) {
35705                                boolean find = false;
35706                                List<String> segments = SQLUtil.parseNames(columnName.toString());
35707                                if (segments.size() == 1) {
35708                                        Table variable = modelManager.visibleLocal(columnName.toString(), LocalSymbolKey.Kind.VARIABLE);
35709                                        if (variable != null && !variable.getColumns().isEmpty()) {
35710                                                TableColumn columnModel = variable.getColumns().get(0);
35711                                                TableColumn matchColumn = matchColumn(variable.getColumns(), columnName);
35712                                                if(matchColumn!=null){
35713                                                        columnModel = matchColumn;
35714                                                } else if (variable.getColumns().size() > 1 && variable instanceof Variable) {
35715                                                        // A bare reference to a field-expanded record is the
35716                                                        // WHOLE record (#695): bind its star, never an
35717                                                        // arbitrary first field — citing one field asserts
35718                                                        // the value ignores the others.
35719                                                        columnModel = compositeBindingColumn((Variable) variable);
35720                                                }
35721                                                if(isOut){
35722                                                        relation.setTarget(new TableColumnRelationshipElement(columnModel));
35723                                                }
35724                                                else {
35725                                                        relation.addSource(new TableColumnRelationshipElement(columnModel));
35726                                                }
35727                                                find = true;
35728                                        } else {
35729                                                if (columnName.toString().matches("\\$\\d+")) {
35730                                                        TCustomSqlStatement stmt = stmtStack.peek();
35731                                                        Procedure procedure = getEnclosingProcedure(stmt);
35732                                                        if(procedure!=null) {
35733                                                                Variable cursorVariable = modelFactory.createReferencedVariable(procedure.getArguments().get(Integer.valueOf(columnName.toString().replace("$", "")) - 1).getName(), true);
35734                                                                if (isOut) {
35735                                                                        relation.setTarget(new TableColumnRelationshipElement(cursorVariable.getColumns().get(0)));
35736                                                                } else {
35737                                                                        relation.addSource(new TableColumnRelationshipElement(cursorVariable.getColumns().get(0)));
35738                                                                }
35739                                                        }
35740                                                } else {
35741                                                        Variable cursorVariable = modelFactory.createReferencedVariable(columnName);
35742                                                        cursorVariable.setCreateTable(true);
35743                                                        cursorVariable.setSubType(SubType.record);
35744                                                        TableColumn variableProperty = null;
35745                                                        if(cursorVariable.getColumns() == null || cursorVariable.getColumns().isEmpty()) {
35746                                variableProperty = modelFactory.createTableColumn(cursorVariable, columnName,
35747                                        true);
35748                            }
35749                                                        else{
35750                                                                variableProperty = compositeBindingColumn(cursorVariable);
35751                                                        }
35752                                                        if (isOut) {
35753                                                                relation.setTarget(new TableColumnRelationshipElement(variableProperty));
35754                                                        } else {
35755                                                                relation.addSource(new TableColumnRelationshipElement(variableProperty));
35756                                                        }
35757                                                }
35758                                                find = true;
35759                                        }
35760                                } else if (option.getVendor() == EDbVendor.dbvoracle && columnName.getTableToken()!=null) {
35761                                        Variable cursorVariable = modelFactory
35762                                                        .createReferencedVariable(columnName.getTableToken().toString(), true);
35763
35764                                        TObjectName variableColumnName = new TObjectName();
35765                                        variableColumnName.setString(segments.get(segments.size() - 1));
35766
35767                                        if (cursorVariable.getColumns() != null) {
35768                                                for (int j = 0; j < cursorVariable.getColumns().size(); j++) {
35769                                                        if (DlineageUtil.sameColumnName(variableColumnName, cursorVariable.getColumns().get(j).getColumnObject())) {
35770                                                                TableColumn columnModel = cursorVariable.getColumns().get(j);
35771                                                                if (isOut) {
35772                                                                        relation.setTarget(new TableColumnRelationshipElement(columnModel));
35773                                                                } else {
35774                                                                        relation.addSource(new TableColumnRelationshipElement(columnModel));
35775                                                                }
35776                                                                find = true;
35777                                                        }
35778                                                }
35779                                        }
35780
35781                                        if (!find) {
35782                                                TableColumn variableColumn = new TableColumn(cursorVariable, variableColumnName);
35783                                                cursorVariable.addColumn(variableColumn);
35784                                                if (isOut) {
35785                                                        relation.setTarget(new TableColumnRelationshipElement(variableColumn));
35786                                                } else {
35787                                                        relation.addSource(new TableColumnRelationshipElement(variableColumn));
35788                                                }
35789                                        }
35790                                } else {
35791                                        Table variable = modelManager.visibleLocal(segments.get(segments.size() - 2),
35792                                                        LocalSymbolKey.Kind.VARIABLE);
35793                                        if (variable != null) {
35794                                                for (int j = 0; j < variable.getColumns().size(); j++) {
35795                                                        if (DlineageUtil.sameColumnName(columnName, variable.getColumns().get(j).getColumnObject())) {
35796                                                                TableColumn columnModel = variable.getColumns().get(j);
35797                                                                if (isOut) {
35798                                                                        relation.setTarget(new TableColumnRelationshipElement(columnModel));
35799                                                                } else {
35800                                                                        relation.addSource(new TableColumnRelationshipElement(columnModel));
35801                                                                }
35802                                                                find = true;
35803                                                        }
35804                                                }
35805                                        }
35806                                }
35807                                if (!find) {
35808                                        TCustomSqlStatement stmt = stmtStack.peek();
35809                                        if (getProcedureParentName(stmt) != null) {
35810                                                Procedure procedure = getEnclosingProcedure(stmt);
35811                                                if (procedure != null && procedure.getArguments() != null) {
35812                                                        for (Argument argument : procedure.getArguments()) {
35813                                                                if (DlineageUtil.sameName(ESQLDataObjectType.dotParameter, argument.getName(),
35814                                                                                columnName.toString())) {
35815                                                                        relation.addSource(new ArgumentRelationshipElement(argument));
35816                                                                }
35817                                                        }
35818                                                }
35819                                        }
35820                                }
35821                                continue;
35822                        }
35823                        
35824                        // Handle sequence pseudocolumn syntax (sequence.NEXTVAL or sequence.CURRVAL)
35825                        // Used by Oracle, Snowflake, and accepted by other vendors for compatibility
35826                        if(("NEXTVAL".equalsIgnoreCase(columnName.getColumnNameOnly()) || "CURRVAL".equalsIgnoreCase(columnName.getColumnNameOnly()))){
35827                                List<String> segments = SQLUtil.parseNames(columnName.toString());
35828                                if (segments.size() > 1) {
35829                                        segments.remove(segments.size()-1);
35830                                        Table table = modelFactory.createTableByName(SQLUtil.mergeSegments(segments, 0), true);
35831                                        table.setSequence(true);
35832                                        TableColumn seqCursor = modelFactory.createTableColumn(table, columnName, true);
35833                                        relation.addSource(new TableColumnRelationshipElement(seqCursor));
35834                                        continue;
35835                                }
35836                        }
35837
35838                        {
35839                                if (columnName.getSourceTable() != null) {
35840
35841                                }
35842                                else {
35843                                        Table variable = modelManager.visibleLocal(columnName.toString(), LocalSymbolKey.Kind.VARIABLE);
35844                                        if (variable == null) {
35845                                                // cur.col: the qualifier names a cursor.
35846                                                variable = modelManager.visibleLocal(columnName.getTableString(), LocalSymbolKey.Kind.CURSOR);
35847                                                if (variable != null && variable.isCursor()) {
35848                                                        TableColumn variableColumn = modelFactory.createInsertTableColumn(variable, columnName);
35849                                                        if (variableColumn != null) {
35850                                                                if(isOut){
35851                                                                        relation.setTarget(new TableColumnRelationshipElement(variableColumn));
35852                                                                }
35853                                                                else {
35854                                                                        relation.addSource(new TableColumnRelationshipElement(variableColumn));
35855                                                                }
35856                                                        } else {
35857                                                                TableColumn wholeValue = variable instanceof Variable
35858                                                                                ? compositeBindingColumn((Variable) variable)
35859                                                                                : variable.getColumns().get(0);
35860                                                                if(isOut){
35861                                                                        relation.setTarget(new TableColumnRelationshipElement(wholeValue));
35862                                                                }
35863                                                                else {
35864                                                                        relation.addSource(new TableColumnRelationshipElement(wholeValue));
35865                                                                }
35866                                                        }
35867                                                        continue;
35868                                                }
35869                                        } else if (variable.isVariable() || variable.isCursor()) {
35870                                                TableColumn columnModel = variable.getColumns().get(0);
35871                                                if (valueIndex != null) {
35872                                                        if(isOut){
35873                                                                relation.setTarget(new TableColumnRelationshipElement(columnModel, valueIndex));
35874                                                        }
35875                                                        else {
35876                                                                relation.addSource(new TableColumnRelationshipElement(columnModel, valueIndex));
35877                                                        }
35878                                                } else {
35879                                                        if(isOut){
35880                                                                relation.setTarget(new TableColumnRelationshipElement(columnModel));
35881                                                        }
35882                                                        else {
35883                                                                relation.addSource(new TableColumnRelationshipElement(columnModel));
35884                                                        }
35885                                                }
35886                                                continue;
35887                                        }
35888                                }
35889                        }
35890                        
35891                        if (columnName.getColumnNameOnly().startsWith("@")
35892                                        && (option.getVendor() == EDbVendor.dbvmssql || option.getVendor() == EDbVendor.dbvazuresql)) {
35893                                continue;
35894                        }
35895
35896                        if (columnName.getColumnNameOnly().startsWith(":")
35897                                        && (option.getVendor() == EDbVendor.dbvhana || option.getVendor() == EDbVendor.dbvteradata)) {
35898                                // HANA / Teradata :var in SQL reads the routine's variable var.
35899                                Table variable = modelManager.visibleLocal(columnName.getColumnNameOnly().replace(":", ""),
35900                                                LocalSymbolKey.Kind.VARIABLE);
35901                                if (variable != null) {
35902                                        for (int j = 0; j < variable.getColumns().size(); j++) {
35903                                                if (DlineageUtil.sameName(ESQLDataObjectType.dotColumn,
35904                                                                DlineageUtil.rawColumnName(columnName).replace(":", ""),
35905                                                                DlineageUtil.rawColumnName(variable.getColumns().get(j).getColumnObject()))) {
35906                                                        TableColumn columnModel = variable.getColumns().get(j);
35907                                                        relation.addSource(new TableColumnRelationshipElement(columnModel));
35908                                                }
35909                                        }
35910                                }
35911                                continue;
35912                        }
35913
35914                        boolean linkedFirstTable = false;
35915                        
35916                        TCustomSqlStatement stmt = stmtStack.peek();
35917                        TTableList tableList = stmt.tables;
35918                        if ((tableList == null || tableList.size() == 0) && (hiveFromTables != null && hiveFromTables.size() > 0)) {
35919                                tableList = hiveFromTables;
35920                        }
35921
35922                        List<TTable> tables = new ArrayList<TTable>();
35923                        {
35924                                TTable table = columnName.getSourceTable();
35925
35926                                // 针对CursorVariable特殊处理
35927                                if (columnName.getTableToken() != null) {
35928
35929                                        Table tableModel = null;
35930                                        if (table != null && modelManager.getModel(table) instanceof Table) {
35931                                                tableModel = (Table) modelManager.getModel(table);
35932                                        }
35933
35934                                        if (tableModel == null) {
35935                                                // A qualifier no FROM item declares: a record, cursor or trigger correlation visible from here
35936                                                // first, as PL/SQL scoping does; else a table of that name.
35937                                                tableModel = modelManager.resolveMixedRelation(columnName.getTableToken().toString(),
35938                                                                modelManager.getTableByName(DlineageUtil.getTableIdentityKey(columnName.getTableToken().toString())),
35939                                                                true);
35940                                        }
35941
35942                                        if (tableModel != null) {
35943
35944                                                if (table == null) {
35945                                                        table = tableModel.getTableObject();
35946                                                }
35947
35948                                                if (tableModel.isVariable()) {
35949                                                        if (!isStar && DlineageUtil.isStarName((columnName))) {
35950                                                                TObjectName[] columns = modelManager.getTableColumns(table);
35951                                                                for (int j = 0; j < columns.length; j++) {
35952                                                                        TObjectName objectName = columns[j];
35953                                                                        if (objectName == null || DlineageUtil.isStarName((objectName))) {
35954                                                                                continue;
35955                                                                        }
35956                                                                        TableColumn columnModel = modelFactory.createTableColumn(tableModel, objectName,
35957                                                                                        false);
35958                                                                        relation.addSource(new TableColumnRelationshipElement(columnModel));
35959                                                                }
35960                                                        } else {
35961                                                                if (DlineageUtil.isStarName((columnName)) && !tableModel.getColumns().isEmpty()) {
35962
35963                                                                        for (int j = 0; j < tableModel.getColumns().size(); j++) {
35964                                                                                TableColumn columnModel = tableModel.getColumns().get(j);
35965                                                                                if (exceptColumnList != null) {
35966                                                                                        boolean flag = false;
35967                                                                                        for (TObjectName objectName : exceptColumnList) {
35968                                                                                                if (DlineageUtil.sameColumnName(objectName, columnModel.getColumnObject())) {
35969                                                                                                        flag = true;
35970                                                                                                        break;
35971                                                                                                }
35972                                                                                        }
35973                                                                                        if (flag) {
35974                                                                                                continue;
35975                                                                                        }
35976                                                                                }
35977                                                                                relation.addSource(new TableColumnRelationshipElement(columnModel));
35978                                                                        }
35979
35980                                                                        if (isStar && showStar) {
35981                                                                                TableColumn columnModel = modelFactory.createTableColumn(tableModel, columnName,
35982                                                                                                false);
35983                                                                                if (columnModel == null) {
35984                                                                                        if (tableModel.isCreateTable()) {
35985                                                                                                for (TableColumn tableColumn : tableModel.getColumns()) {
35986                                                                                                        relation.addSource(new TableColumnRelationshipElement(tableColumn));
35987                                                                                                        relation.setShowStarRelation(true);
35988                                                                                                }
35989                                                                                        }
35990                                                                                } else {
35991                                                                                        relation.addSource(new TableColumnRelationshipElement(columnModel));
35992                                                                                        relation.setShowStarRelation(true);
35993                                                                                }
35994                                                                        }
35995                                                                } else {
35996                                                                        TableColumn columnModel = modelFactory.createTableColumn(tableModel, columnName,
35997                                                                                        false);
35998                                                                        
35999                                                                        if(columnModel == null && containStarColumn(tableModel.getColumns())){
36000                                                                                columnModel = getStarColumn(tableModel.getColumns());
36001                                                                                if (columnModel != null && tableModel.getSubType() == SubType.record_type) {
36002                                                                                        if(!DlineageUtil.isStarName((columnName))){
36003                                                                                                columnModel.bindStarLinkColumn(columnName);
36004                                                                                        }
36005                                                                                }
36006                                                                        }
36007                                                                        
36008                                                                        if (columnModel == null) {
36009                                                                                continue;
36010                                                                        }
36011                                                                        if (columnModel.hasStarLinkColumn()) {
36012                                                                                relation.addSource(new TableColumnRelationshipElement(columnModel,
36013                                                                                                columnModel.starLinkIndexOf(columnName)));
36014                                                                        } else {
36015                                                                                if (isOut) {
36016                                                                                        relation.setTarget(new TableColumnRelationshipElement(columnModel));
36017                                                                                } else {
36018                                                                                        relation.addSource(new TableColumnRelationshipElement(columnModel));
36019                                                                                }
36020                                                                        }
36021                                                                        if (columnName.getSourceTable() != null
36022                                                                                        && columnName.getSourceTable().getTableType() == ETableSource.function) {
36023                                                                                analyzeTableColumn(columnModel, columnName.getSourceTable().getFuncCall(),
36024                                                                                                effectType);
36025                                                                        }
36026                                                                }
36027                                                        }
36028                                                        continue;
36029                                                }
36030                                        }
36031                                }
36032
36033                                if (table == null) {
36034                                        table = modelManager.getTable(stmt, columnName);
36035                                }
36036
36037                                if (table == null) {
36038                                        if (columnName.getTableToken() != null || !DlineageUtil.isStarName((columnName))) {
36039                                                table = columnName.getSourceTable();
36040                                        }
36041                                        
36042                                        if (table == null && !SQLUtil.isEmpty(columnName.getTableString())) {
36043                                                table = modelManager.getTableFromColumn(columnName);
36044                                        }
36045                                }
36046
36047                                if (table == null) {
36048                                        if (tableList != null) {
36049                                                for (int j = 0; j < tableList.size(); j++) {
36050                                                        if (table != null)
36051                                                                break;
36052
36053                                                        TTable tTable = tableList.getTable(j);
36054                                                        if (tTable.getTableType().name().startsWith("open")) {
36055                                                                continue;
36056                                                        }
36057
36058                                                        if (getTableLinkedColumns(tTable) != null && getTableLinkedColumns(tTable).size() > 0) {
36059                                                                for (int z = 0; z < getTableLinkedColumns(tTable).size(); z++) {
36060                                                                        TObjectName refer = getTableLinkedColumns(tTable).getObjectName(z);
36061                                                                        if (DlineageUtil.isStarName((refer)))
36062                                                                                continue;
36063                                                                        // For BigQuery struct field access, match base column name from FieldPath
36064                                                                        String structFullName = getStructFieldFullName(columnName);
36065                                                                        if (structFullName != null) {
36066                                                                                String baseName = getStructFieldBaseName(columnName);
36067                                                                                if (baseName != null && DlineageUtil.sameColumnName(refer, baseName)) {
36068                                                                                        table = tTable;
36069                                                                                        break;
36070                                                                                }
36071                                                                        }
36072                                                                        if (DlineageUtil.sameColumnName(refer, columnName)) {
36073                                                                                table = tTable;
36074                                                                                break;
36075                                                                        }
36076                                                                }
36077                                                        }
36078
36079                                                        if (tTable.getLinkTable() != null) {
36080                                                                tTable = tTable.getLinkTable();
36081                                                                for (int z = 0; z < getTableLinkedColumns(tTable).size(); z++) {
36082                                                                        TObjectName refer = getTableLinkedColumns(tTable).getObjectName(z);
36083                                                                        if (DlineageUtil.isStarName((refer)))
36084                                                                                continue;
36085                                                                        if (DlineageUtil.sameColumnName(refer, columnName)) {
36086                                                                                table = tTable;
36087                                                                                break;
36088                                                                        }
36089                                                                }
36090                                                        }
36091
36092                                                        if (table != null)
36093                                                                break;
36094
36095                                                        if (columnName.getTableToken() != null && (SQLUtil.sameName(option.getVendor(), ESQLDataObjectType.dotTable,
36096                                                                        columnName.getTableToken().getAstext(), tTable.getName())
36097                                                                        || SQLUtil.sameName(option.getVendor(), ESQLDataObjectType.dotTable, columnName.getTableToken().getAstext(), tTable.getAliasName()))) {
36098                                                                table = tTable;
36099                                                                break;
36100                                                        }
36101                                                }
36102                                        }
36103
36104                                        if (table == null) {
36105                                                for (int j = 0; j < tableList.size(); j++) {
36106                                                        if (table != null)
36107                                                                break;
36108
36109                                                        TTable tTable = tableList.getTable(j);
36110                                                        Object model = ModelBindingManager.get().getModel(tTable);
36111                                                        if (model instanceof Table) {
36112                                                                Table tableModel = (Table) model;
36113                                                                // STRUCT tree: a field read is owned by the table that declares its column, as a
36114                                                                // whole-column read would be (customer.address.city with only orders declaring customer)
36115                                                                String structBase = nestedProfile().buildsDeclaredTree()
36116                                                                                && tableModel.isCreateTable() ? getStructFieldBaseName(columnName) : null;
36117                                                                for (int z = 0; tableModel.getColumns() != null
36118                                                                                && z < tableModel.getColumns().size(); z++) {
36119                                                                        TableColumn refer = tableModel.getColumns().get(z);
36120                                                                        if (structBase != null && DlineageUtil.sameName(ESQLDataObjectType.dotColumn,
36121                                                                                        refer.getName(), structBase)) {
36122                                                                                table = tTable;
36123                                                                                break;
36124                                                                        }
36125                                                                        if (DlineageUtil.sameColumnName(refer.getName(), columnName)) {
36126                                                                                table = tTable;
36127                                                                                break;
36128                                                                        }
36129                                                                        if (refer.hasStarLinkColumn()) {
36130                                                                                for (TObjectName linkColumn : refer.getStarLinkColumnList()) {
36131                                                                                        if (DlineageUtil.sameColumnName(linkColumn, columnName)) {
36132                                                                                                table = tTable;
36133                                                                                                break;
36134                                                                                        }
36135                                                                                }
36136                                                                        }
36137                                                                }
36138                                                        } else if (model instanceof QueryTable) {
36139                                                                QueryTable tableModel = (QueryTable) model;
36140                                                                for (int z = 0; tableModel.getColumns() != null
36141                                                                                && z < tableModel.getColumns().size(); z++) {
36142                                                                        ResultColumn refer = tableModel.getColumns().get(z);
36143                                                                        // Try FieldPath-based matching first
36144                                                                        String structFullName = getStructFieldFullName(columnName);
36145                                                                        if (structFullName != null) {
36146                                                                                String baseName = getStructFieldBaseName(columnName);
36147                                                                                if (baseName != null && DlineageUtil.columnMatches(refer, baseName)) {
36148                                                                                        table = tTable;
36149                                                                                        break;
36150                                                                                }
36151                                                                        }
36152                                                                        List<String> splits = SQLUtil.parseNames(columnName.toString());
36153                                                                        if (splits.size() > 1 && EDbVendor.dbvbigquery == getOption().getVendor()) {
36154                                                                                if (DlineageUtil.columnMatches(refer, splits.get(0))) {
36155                                                                                        table = tTable;
36156                                                                                        break;
36157                                                                                }
36158                                                                        }
36159                                                                        else if (DlineageUtil.columnMatches(refer, columnName)) {
36160                                                                                table = tTable;
36161                                                                                break;
36162                                                                        }
36163                                                                        if (refer.hasStarLinkColumn()) {
36164                                                                                for (TObjectName linkColumn : refer.getStarLinkColumnList()) {
36165                                                                                        if (DlineageUtil.sameColumnName(linkColumn, columnName)) {
36166                                                                                                table = tTable;
36167                                                                                                break;
36168                                                                                        }
36169                                                                                }
36170                                                                        }
36171                                                                }
36172                                                        }
36173                                                }
36174                                        }
36175                                }
36176
36177                                // STRUCT tree: a field read TSQLResolver2 could not place belongs to the one relation that declares its
36178                                // base column, and plan rule R3: a field read whose column TSQLResolver2 found in several relations has no
36179                                // proven owner; neither the parse's first guess nor a same-named column of some table settles it.
36180                                table = structFieldReadTable(table, columnName, tableList);
36181
36182                                if (columnName.getTableToken() == null && DlineageUtil.isStarName((columnName))) {
36183                                        if (columnName == starOfTables) {
36184                                                tables.addAll(starOfTablesRead);
36185                                        } else if (!hasJoin(stmt)) {
36186                                                tables.add(table);
36187                                        } else {
36188                                                for (int j = 0; j < tableList.size(); j++) {
36189                                                        tables.add(tableList.getTable(j));
36190                                                }
36191                                        }
36192                                } else if (table != null) {
36193                                        tables.add(table);
36194                                }
36195
36196                                // 此处特殊处理,多表关联无法找到 column 所属的 Table, tTable.getLinkedColumns
36197                                // 也找不到,退而求其次采用第一个表
36198
36199                                if (stmt.getParentStmt() != null && isApplyJoin(stmt.getParentStmt())
36200                                                && (tableList != null && tableList.size() > 0)) {
36201                                        stmt = stmt.getParentStmt();
36202                                        TTable applyTable = tableList.getTable(0);
36203                                        if (modelManager.getModel(table) == null) {
36204                                                modelFactory.createTable(applyTable);
36205                                        }
36206                                }
36207
36208                                if (columnName.toString().indexOf(".")==-1 && isConstant(columnName)) {
36209                                        boolean isConstant = true;
36210                                        if (columnName.getSourceTable() != null) {
36211                                                Table tableModel = modelManager.getTableByName(
36212                                                                DlineageUtil.getTableIdentityKey(columnName.getSourceTable().getTableName().toString()));
36213                                                if (tableModel != null && tableModel.getColumns() != null) {
36214                                                        for (int j = 0; j < tableModel.getColumns().size(); j++) {
36215                                                                if (sameTableColumn(columnName, tableModel.getColumns().get(j))) {
36216                                                                        isConstant = false;
36217                                                                        break;
36218                                                                }
36219                                                        }
36220                                                }
36221                                        }
36222
36223                                        if (isConstant) {
36224                                                if (option.isShowConstantTable()) {
36225                                                        Table constantTable = modelFactory.createConstantsTable(stmtStack.peek());
36226                                                        TableColumn tableColumn = modelFactory.createTableColumn(constantTable, columnName, false);
36227                                                        relation.addSource(new TableColumnRelationshipElement(tableColumn));
36228                                                }
36229                                                continue;
36230                                        }
36231                                }
36232
36233                                if (tableList != null && tableList.size() != 0 && tables.size() == 0
36234                                                && !(isBuiltInFunctionName(columnName) && isFromFunction(columnName))) {
36235                                        if (modelManager.getModel(stmt) instanceof ResultSet) {
36236                                                ResultSet resultSetModel = (ResultSet) modelManager.getModel(stmt);
36237                                                boolean find = false;
36238                                                for (ResultColumn resultColumn : resultSetModel.getColumns()) {
36239                                                        if(resultColumn.equals(modelObject)) {
36240                                                                continue;
36241                                                        }
36242                                                        if (!TSQLEnv.isAliasReferenceForbidden.get(option.getVendor())) {
36243                                                                if (isAliasReference(columnName, resultColumn)) {
36244                                                                        if (resultColumn.getColumnObject() != null) {
36245                                                                                int startToken = resultColumn.getColumnObject().getStartToken().posinlist;
36246                                                                                int endToken = resultColumn.getColumnObject().getEndToken().posinlist;
36247                                                                                if (columnName.getStartToken().posinlist >= startToken
36248                                                                                                && columnName.getEndToken().posinlist <= endToken) {
36249                                                                                        continue;
36250                                                                                }
36251                                                                        }
36252                                                                        relation.addSource(new ResultColumnRelationshipElement(resultColumn));
36253                                                                        find = true;
36254                                                                        break;
36255                                                                }
36256                                                        }
36257                                                }
36258                                                if (find) {
36259                                                        continue;
36260                                                }
36261                                        }
36262
36263                                        // Structured-dataflow outer field resolution: an unresolved
36264                                        // reference like nodes1.key (or t.nodes1.key) may be a
36265                                        // struct-field projection produced by a structured generator
36266                                        // (e.g. Spark explode(from_json(...))) in a visible subquery.
36267                                        // Link directly to the matching FunctionResultColumn so the
36268                                        // exact source path (nodes[*].key) flows through.
36269                                        if (tryAppendStructuredFieldRelation(relation, columnName, tableList)) {
36270                                                continue;
36271                                        }
36272
36273                                        TObjectName pseudoTableName = new TObjectName();
36274                                        // Use qualified prefix from column name if available (e.g., sch.pk_constv2 from sch.pk_constv2.c_cdsl)
36275                                        // Otherwise fall back to default pseudo table name
36276                                        String qualifiedPrefix = getQualifiedPrefixFromColumn(columnName);
36277                                        pseudoTableName.setString(qualifiedPrefix != null ? qualifiedPrefix : "pseudo_table_include_orphan_column");
36278                                        Table pseudoTable = modelFactory.createTableByName(pseudoTableName);
36279                                        pseudoTable.setPseudo(true);
36280                                        TableColumn pseudoTableColumn = modelFactory.createTableColumn(pseudoTable, columnName, true);
36281
36282                                        // If not linking to first table and column has qualified prefix (3-part name like sch.pkg.col),
36283                                        // add the pseudo table column as source
36284                                        if (!isLinkOrphanColumnToFirstTable() && pseudoTableColumn != null && qualifiedPrefix != null) {
36285                                                SqllrObservations decided = SqllrObservations.current();
36286                                                if (decided != null) {
36287                                                        decided.decision(SqllrObservations.DECISION_ORPHAN_PSEUDO_TABLE, columnName, pseudoTable,
36288                                                                        "an orphan column with a qualified prefix");
36289                                                }
36290                                                if (isOut) {
36291                                                        relation.setTarget(new TableColumnRelationshipElement(pseudoTableColumn));
36292                                                } else {
36293                                                        relation.addSource(new TableColumnRelationshipElement(pseudoTableColumn));
36294                                                }
36295                                        }
36296
36297                                        if (isLinkOrphanColumnToFirstTable()) {
36298                                                TTable orphanTable = tableList.getTable(0);
36299                                                SqllrObservations decided = SqllrObservations.current();
36300                                                if (decided != null) {
36301                                                        decided.decision(SqllrObservations.DECISION_ORPHAN_FIRST_TABLE, columnName, orphanTable,
36302                                                                        "linkOrphanColumnToFirstTable");
36303                                                }
36304                                                tables.add(orphanTable);
36305                                                Object tableModel = modelManager.getModel(orphanTable);
36306                                                if (tableModel == null) {
36307                                                        if(orphanTable.getSubquery()!=null) {
36308                                                                QueryTable queryTable = modelFactory.createQueryTable(orphanTable);
36309                                                                TSelectSqlStatement subquery = orphanTable.getSubquery();
36310                                                                analyzeSelectStmt(subquery);
36311                                                        }
36312                                                        else {
36313                                                                tableModel = modelFactory.createTable(orphanTable);
36314                                                        }
36315                                                }
36316                                                if (tableModel instanceof Table) {
36317                                                        TableColumn orphanColum = modelFactory.createTableColumn((Table) tableModel, columnName, false);
36318                                                        if(orphanColum!=null) {
36319                                                                ErrorInfo errorInfo = new ErrorInfo();
36320                                                                errorInfo.setErrorType(ErrorInfo.LINK_ORPHAN_COLUMN);
36321                                                                errorInfo.setErrorMessage("Link orphan column [" + columnName.toString()
36322                                                                                + "] to the first table [" + orphanTable.getFullNameWithAliasString() + "]");
36323                                                                errorInfo.setStartPosition(new Pair3<Long, Long, String>(columnName.getStartToken().lineNo,
36324                                                                                columnName.getStartToken().columnNo, ModelBindingManager.getGlobalHash()));
36325                                                                errorInfo.setEndPosition(new Pair3<Long, Long, String>(columnName.getEndToken().lineNo,
36326                                                                                columnName.getEndToken().columnNo + columnName.getEndToken().getAstext().length(),
36327                                                                                ModelBindingManager.getGlobalHash()));
36328                                                                errorInfo.fillInfo(this);
36329                                                                errorInfos.add(errorInfo);
36330                                                        }
36331                                                        if (orphanTable.getSubquery() != null) {
36332                                                                TSelectSqlStatement subquery = orphanTable.getSubquery();
36333                                                                if (lastItemIsStar(subquery.getResultColumnList()) && subquery.getTables().size() == 1) {
36334                                                                        TTable subqueryTable = subquery.getTables().getTable(0);
36335                                                                        Object sourceTable = modelManager.getModel(subqueryTable);
36336                                                                        if(sourceTable instanceof Table) {
36337                                                                                modelFactory.createTableColumn((Table) sourceTable, columnName, false);
36338                                                                        }
36339                                                                        else if(sourceTable instanceof ResultSet) {
36340                                                                                modelFactory.createResultColumn((ResultSet) sourceTable, columnName, false);
36341                                                                        }
36342                                                                }
36343                                                        }
36344                                                        else if (orphanTable.getCTE()!=null && orphanTable.getCTE().getSubquery() != null) {
36345                                                                TSelectSqlStatement subquery = orphanTable.getCTE().getSubquery();
36346                                                                if (lastItemIsStar(subquery.getResultColumnList()) && subquery.getTables().size() == 1) {
36347                                                                        TTable subqueryTable = subquery.getTables().getTable(0);
36348                                                                        Object sourceTable = modelManager.getModel(subqueryTable);
36349                                                                        if(sourceTable instanceof Table) {
36350                                                                                modelFactory.createTableColumn((Table) sourceTable, columnName, false);
36351                                                                        }
36352                                                                        else if(sourceTable instanceof ResultSet) {
36353                                                                                modelFactory.createResultColumn((ResultSet) sourceTable, columnName, false);
36354                                                                        }
36355                                                                }
36356                                                        }
36357                                                }
36358                                                
36359                                                linkedFirstTable = true;
36360                                        }
36361                                }
36362                        }
36363
36364                        for (int k = 0; k < tables.size(); k++) {
36365                                TTable table = tables.get(k);
36366                                if (table != null) {
36367                                        Object object = modelManager.getModel(table);
36368                                        if(object instanceof PivotedTable) {
36369                                                TPivotClause clause = (TPivotClause)((PivotedTable)object).getGspObject();
36370                                                if(clause.getAliasClause()!=null) {
36371                                                        object = modelManager.getModel(clause.getAliasClause());
36372                                                }
36373                                        }
36374                                        if (object == null && table.getTableType() == ETableSource.objectname) {
36375                                                if (table.getCTE() != null) {
36376                                                        QueryTable queryTable = modelFactory.createQueryTable(table);
36377                                                        TSelectSqlStatement subquery = table.getCTE().getSubquery();
36378                                                        analyzeSelectStmt(subquery);
36379                                                        ResultSet resultSetModel = (ResultSet) modelManager.getModel(subquery);
36380
36381                                                        if (resultSetModel != null && resultSetModel != queryTable
36382                                                                        && !resultSetModel.getRelationRows().getHoldRelations().isEmpty()) {
36383                                                                ImpactRelationship impactRelation = modelFactory.createImpactRelation();
36384                                                                impactRelation.setEffectType(EffectType.select);
36385                                                                impactRelation.addSource(new RelationRowsRelationshipElement<ResultSetRelationRows>(
36386                                                                                resultSetModel.getRelationRows()));
36387                                                                impactRelation.setTarget(new RelationRowsRelationshipElement<ResultSetRelationRows>(
36388                                                                                queryTable.getRelationRows()));
36389                                                        }
36390
36391                                                        if (resultSetModel != null && resultSetModel != queryTable) {
36392                                                                for (int j = 0; j < resultSetModel.getColumns().size(); j++) {
36393                                                                        ResultColumn sourceColumn = resultSetModel.getColumns().get(j);
36394                                                                        ResultColumn targetColumn = modelFactory.createSelectSetResultColumn(queryTable,
36395                                                                                        sourceColumn);
36396
36397                                                                        DataFlowRelationship queryRalation = modelFactory.createDataFlowRelation();
36398                                                                        queryRalation.setEffectType(EffectType.select);
36399                                                                        queryRalation.setTarget(new ResultColumnRelationshipElement(targetColumn));
36400                                                                        queryRalation.addSource(new ResultColumnRelationshipElement(sourceColumn));
36401                                                                }
36402                                                        }
36403                                                        
36404                                                        object = queryTable;
36405                                                        
36406                                                } else {
36407                                                        object = modelFactory.createTable(table);
36408                                                }
36409                                        }
36410                                        if (object instanceof Function) {
36411                                                relation.addSource(new ResultColumnRelationshipElement(((Function)object).getColumns().get(0)));
36412                                                continue;
36413                                        } else if (object instanceof ResultSet && !(object instanceof QueryTable)) {
36414                                                //Object tableModel = modelManager.getModel(columnName.getSourceTable());
36415                                                appendResultColumnRelationSource(modelObject, relation, columnIndex, columnName,
36416                                                                (ResultSet)object);
36417                                                if(table.getTableType() == ETableSource.function && !relation.getSources().isEmpty()) {
36418                                                        relation.getSources().stream().reduce((first, second) -> second).get().addTransform(Transform.FUNCTION, table);
36419                                                }
36420                                                continue;
36421                                        }
36422                                        else if (object instanceof Table) {
36423                                                Table tableModel = (Table) modelManager.getModel(table);
36424                                                if (tableModel != null) {
36425                                                        if (!isStar && DlineageUtil.isStarName((columnName))) {
36426                                                                TObjectName[] columns = modelManager.getTableColumns(table);
36427                                                                for (int j = 0; j < columns.length; j++) {
36428                                                                        TObjectName objectName = columns[j];
36429                                                                        if (objectName == null || (DlineageUtil.isStarName((objectName)) && tableModel.isDetermined())) {
36430                                                                                continue;
36431                                                                        }
36432                                                                        TableColumn columnModel = modelFactory.createTableColumn(tableModel, objectName,
36433                                                                                        false);
36434                                                                        if(columnModel == null) {
36435                                                                                continue;
36436                                                                        }
36437                                                                        relation.addSource(new TableColumnRelationshipElement(columnModel));
36438                                                                }
36439                                                        } else {
36440                                                                if (DlineageUtil.isStarName((columnName)) && !tableModel.getColumns().isEmpty()) {
36441                                                                        Map<String, Pair<String, TExpression>> replaceAsIdentifierMap = new HashMap<String, Pair<String, TExpression>>();
36442                                                                        Map<String, TObjectName> replaceColumnMap = new HashMap<String, TObjectName>();
36443                                                                        if(modelObject instanceof ResultColumn && ((ResultColumn)modelObject).getColumnObject() instanceof TResultColumn) {
36444                                                                                TResultColumn resultColumn =  (TResultColumn )((ResultColumn)modelObject).getColumnObject();
36445                                                                                if(resultColumn.getReplaceExprAsIdentifiers()!=null && resultColumn.getReplaceExprAsIdentifiers().size()>0) {
36446                                                                                        for(TReplaceExprAsIdentifier replace: resultColumn.getReplaceExprAsIdentifiers()) {
36447                                                                                                replaceAsIdentifierMap.put(replace.getIdentifier().toString(), new Pair<String, TExpression>(resultColumn.getExpr().getExceptReplaceClause().toString(), replace.getExpr()));
36448                                                                                                replaceColumnMap.put(replace.getIdentifier().toString(), replace.getIdentifier());
36449                                                                                        }
36450                                                                                        ResultSet resultSet = ((ResultColumn)modelObject).getResultSet();
36451                                                                                        if(tableModel.isDetermined()) {
36452                                                                                                resultSet.getColumns().clear();
36453                                                                                        }
36454                                                                                }
36455                                                                        }
36456                                                                        
36457                                                                        for (int j = 0; j < tableModel.getColumns().size(); j++) {
36458                                                                                TableColumn columnModel = tableModel.getColumns().get(j);
36459                                                                                if (exceptColumnList != null) {
36460                                                                                        boolean flag = false;
36461                                                                                        for (TObjectName objectName : exceptColumnList) {
36462                                                                                                if (DlineageUtil.sameColumnName(objectName, columnModel.getColumnObject())) {
36463                                                                                                        flag = true;
36464                                                                                                        break;
36465                                                                                                }
36466                                                                                        }
36467                                                                                        if (flag) {
36468                                                                                                continue;
36469                                                                                        }
36470                                                                                }
36471                                                                                
36472                                                                                if (replaceAsIdentifierMap.containsKey(tableModel.getColumns().get(j).getName())) {
36473                                                                                        Pair<String, TExpression> expr = replaceAsIdentifierMap.get(tableModel.getColumns().get(j).getName());
36474                                                                                        ResultSet resultSet = ((ResultColumn)modelObject).getResultSet();
36475                                                                                        ResultColumn resultColumn = modelFactory.createResultColumn(resultSet, replaceColumnMap.get(tableModel.getColumns().get(j).getName()));
36476                                                                                        Transform transform = new Transform();
36477                                                                                        transform.setType(Transform.EXPRESSION);
36478                                                                                        TObjectName expression = new TObjectName();
36479                                                                                        expression.setString(expr.first);
36480                                                                                transform.setCode(expression);
36481                                                                                        resultColumn.setTransform(transform);
36482                                                                                        analyzeResultColumnExpressionRelation(resultColumn, expr.second);
36483                                                                                } else {
36484                                                                                        if(!replaceAsIdentifierMap.isEmpty()) {
36485                                                                                                ResultSet resultSet = ((ResultColumn) modelObject).getResultSet();
36486                                                                                                ResultColumn resultColumn = modelFactory.createResultColumn(resultSet,
36487                                                                                                                columnModel.getColumnObject());
36488                                                                                                DataFlowRelationship relation1 = modelFactory.createDataFlowRelation();
36489                                                                                                relation1.setEffectType(effectType);
36490                                                                                                relation1.setProcess(process);
36491                                                                                                relation1.setTarget(new ResultColumnRelationshipElement(resultColumn));
36492                                                                                                relation1.addSource(new TableColumnRelationshipElement(columnModel));
36493                                                                                        }
36494                                                                                        else {
36495                                                                                                relation.addSource(
36496                                                                                                        new TableColumnRelationshipElement(columnModel));
36497                                                                                        }
36498                                                                                }
36499                                                                        }
36500
36501                                                                        if (isStar && showStar) {
36502                                                                                TableColumn columnModel = modelFactory.createTableColumn(tableModel, columnName,
36503                                                                                                false);
36504                                                                                if (columnModel == null) {
36505                                                                                        if(tableModel.isCreateTable()) {
36506                                                                                                for (TableColumn tableColumn : tableModel.getColumns()) {
36507                                                                                                        relation.addSource(new TableColumnRelationshipElement(tableColumn));
36508                                                                                                        relation.setShowStarRelation(true);
36509                                                                                                }
36510                                                                                        }
36511                                                                                } else {
36512                                                                                        relation.addSource(new TableColumnRelationshipElement(columnModel));
36513                                                                                        relation.setShowStarRelation(true);
36514                                                                                }
36515                                                                        }
36516                                                                } else {
36517                                                                        TableColumn columnModel = modelFactory.createTableColumn(tableModel, columnName,
36518                                                                                        false);
36519                                                                        if(columnModel == null) {
36520                                                                                if(tableModel.isCreateTable()) {
36521                                                                                        boolean flag = false;
36522                                                                                        // Try FieldPath-based matching first for BigQuery/Redshift struct fields
36523                                                                                        String structFullName = getStructFieldFullName(columnName);
36524                                                                                        if (structFullName != null && !flag) {
36525                                                                                                for (TableColumn tableColumn : tableModel.getColumns()) {
36526                                                                                                        if (tableColumn.isStruct()
36527                                                                                                                && DlineageUtil.sameColumnPath(tableColumn.getName(), structFullName)) {
36528                                                                                                                relation.addSource(new TableColumnRelationshipElement(tableColumn));
36529                                                                                                                flag = true;
36530                                                                                                                if (modelObject instanceof ResultColumn) {
36531                                                                                                                        ((ResultColumn) modelObject).setStruct(true);
36532                                                                                                                } else if (modelObject instanceof TableColumn) {
36533                                                                                                                        ((TableColumn) modelObject).setStruct(true);
36534                                                                                                                }
36535                                                                                                                break;
36536                                                                                                        }
36537                                                                                                }
36538                                                                                        }
36539                                                                                        if (ModelBindingManager.getGlobalVendor() == EDbVendor.dbvbigquery || ModelBindingManager.getGlobalVendor() == EDbVendor.dbvredshift) {
36540                                                                                                for (TableColumn tableColumn : tableModel.getColumns()) {
36541                                                                                                        if(tableColumn.isStruct()) {
36542                                                                                                                List<String> names = SQLUtil.parseNames(tableColumn.getName());
36543                                                                                                                if (modelObject instanceof TableColumn) {
36544                                                                                                                        TableColumn targetColumn = (TableColumn) modelObject;
36545                                                                                                                        if (targetColumn.isStruct()) {
36546                                                                                                                                List<String> targetNames = SQLUtil
36547                                                                                                                                                .parseNames(targetColumn.getName());
36548                                                                                                                                if (!DlineageUtil.sameColumnName(targetNames.get(0), names.get(0))) {
36549                                                                                                                                        continue;
36550                                                                                                                                }
36551                                                                                                                        }
36552                                                                                                                }
36553                                                                                                                else if (modelObject instanceof ResultColumn) {
36554                                                                                                                        ResultColumn targetColumn = (ResultColumn) modelObject;
36555                                                                                                                        if (targetColumn.isStruct()) {
36556                                                                                                                                List<String> targetNames = SQLUtil
36557                                                                                                                                                .parseNames(targetColumn.getName());
36558                                                                                                                                if (!DlineageUtil.sameColumnName(targetNames.get(0), names.get(0))) {
36559                                                                                                                                        continue;
36560                                                                                                                                }
36561                                                                                                                        }
36562                                                                                                                }
36563                                                                                                                for(String name: names) {
36564                                                                                                                        if (DlineageUtil.sameColumnName(name, modelObject.toString())) {
36565                                                                                                                                relation.addSource(
36566                                                                                                                                                new TableColumnRelationshipElement(tableColumn));
36567                                                                                                                                flag = true;
36568                                                                                                                                if (modelObject instanceof ResultColumn) {
36569                                                                                                                                        ((ResultColumn)modelObject).setStruct(true);
36570                                                                                                                                }
36571                                                                                                                                else if (modelObject instanceof TableColumn) {
36572                                                                                                                                        ((TableColumn)modelObject).setStruct(true);
36573                                                                                                                                }
36574                                                                                                                                break;
36575                                                                                                                        }
36576                                                                                                                }
36577
36578                                                                                                                // a STRUCT tree element (items[*]) reads its fields as nodes, never as a
36579                                                                                                                // column named after the element and the read
36580                                                                                                                if (!flag && tableModel.getColumns().size() == 1 && tableModel
36581                                                                                                                                .getColumns().get(0).getSourceColumn() != null
36582                                                                                                                                && !tableModel.getColumns().get(0).getSourceColumn().isFieldPathNode()) {
36583                                                                                                                        TableColumn sourceColumn = tableModel
36584                                                                                                                                        .getColumns().get(0).getSourceColumn();
36585                                                                                                                        Table sourceTable = sourceColumn.getTable();
36586                                                                                                                        TObjectName sourceColumnName = new TObjectName();
36587                                                                                                                        sourceColumnName.setString(sourceColumn.getName() + "."
36588                                                                                                                                        + columnName.getColumnNameOnly());
36589                                                                                                                        TableColumn sourceTableColumn = modelFactory.createTableColumn(sourceTable, sourceColumnName, true);
36590                                                                                                                        relation.addSource(
36591                                                                                                                                        new TableColumnRelationshipElement(sourceTableColumn));
36592                                                                                                                        flag = true;
36593                                                                                                                        break;
36594                                                                                                                }
36595                                                                                                        }
36596                                                                                                        else if (DlineageUtil.sameColumnName(tableColumn.getName(), modelObject.toString())) {
36597                                                                                                                relation.addSource(
36598                                                                                                                                new TableColumnRelationshipElement(tableColumn));
36599                                                                                                                flag = true;
36600                                                                                                                break;
36601                                                                                                        }
36602                                                                                                }
36603                                                                                                if (modelObject instanceof TableColumn) {
36604                                                                                                        TableColumn column = (TableColumn) modelObject;
36605                                                                                                        for (TableColumn tableColumn : tableModel.getColumns()) {
36606                                                                                                                if (tableColumn.getColumnIndex() == null) {
36607                                                                                                                        continue;
36608                                                                                                                }
36609                                                                                                                if (tableColumn.getName().toLowerCase(Locale.ROOT)
36610                                                                                                                                .indexOf(column.getName().toLowerCase(Locale.ROOT)) == -1
36611                                                                                                                                && tableColumn.getName().toLowerCase(Locale.ROOT)
36612                                                                                                                                                .indexOf(column.getColumnObject().toString()
36613                                                                                                                                                                .toLowerCase(Locale.ROOT)) == -1) {
36614                                                                                                                        continue;
36615                                                                                                                }
36616                                                                                                                flag = true;
36617                                                                                                                relation.addSource(
36618                                                                                                                                new TableColumnRelationshipElement(tableColumn));
36619                                                                                                                relation.setShowStarRelation(true);
36620                                                                                                        }
36621                                                                                                        if (flag)
36622                                                                                                                break;
36623
36624                                                                                                } else if (modelObject instanceof ResultColumn) {
36625                                                                                                        ResultColumn column = (ResultColumn) modelObject;
36626                                                                                                        for (TableColumn tableColumn : tableModel.getColumns()) {
36627                                                                                                                if (tableColumn.getColumnIndex() == null) {
36628                                                                                                                        continue;
36629                                                                                                                }
36630                                                                                                                if (tableColumn.getName().toLowerCase(Locale.ROOT)
36631                                                                                                                                .indexOf(column.getName().toLowerCase(Locale.ROOT)) == -1
36632                                                                                                                                && tableColumn.getName().toLowerCase(Locale.ROOT)
36633                                                                                                                                                .indexOf(column.getColumnObject().toString()
36634                                                                                                                                                                .toLowerCase(Locale.ROOT)) == -1) {
36635                                                                                                                        continue;
36636                                                                                                                }
36637                                                                                                                flag = true;
36638                                                                                                                relation.addSource(
36639                                                                                                                                new TableColumnRelationshipElement(tableColumn));
36640                                                                                                                relation.setShowStarRelation(true);
36641                                                                                                        }
36642                                                                                                        if (flag)
36643                                                                                                                break;
36644
36645                                                                                                }
36646                                                                                        }
36647                                                                                        if (!flag 
36648                                                                                                        && (isStar 
36649                                                                                                                        || DlineageUtil.sameColumnName(columnName, tableModel.getName())
36650                                                                                                                        || DlineageUtil.sameColumnName(columnName, tableModel.getAlias()))) {
36651                                                                                                for (TableColumn tableColumn : tableModel.getColumns()) {
36652                                                                                                        relation.addSource(new TableColumnRelationshipElement(tableColumn));
36653                                                                                                        relation.setShowStarRelation(true);
36654                                                                                                }
36655                                                                                        }
36656                                                                                }
36657                                                                        }
36658                                                                        else {
36659                                                                                if (linkedFirstTable || columnModel.getCandidateParents() != null) {
36660                                                                                        if (columnName.getCandidateTables() != null
36661                                                                                                        && columnName.getCandidateTables().size() > 1) {
36662                                                                                                List<Object> candidateParents = new ArrayList<Object>();
36663                                                                                                for(TTable tableItem: columnName.getCandidateTables()) {
36664                                                                                                        Object model = modelManager.getModel(tableItem);
36665                                                                                                        if(model!=null) {
36666                                                                                                                candidateParents.add(model);
36667                                                                                                        }
36668                                                                                                }
36669                                                                                                if (candidateParents.size() > 1) {
36670                                                                                                        columnModel.setCandidateParents(candidateParents);
36671                                                                                                }
36672                                                                                        }
36673                                                                                }
36674                                                                                relation.addSource(new TableColumnRelationshipElement(columnModel));
36675                                                                                relation.setShowStarRelation(true);
36676                                                                                if(modelObject instanceof TableColumn) {
36677                                                                                        TableColumn targetTableColumn = (TableColumn)modelObject;
36678                                                                                        if(targetTableColumn.getTable().getSubType() == SubType.unnest && targetTableColumn.getTable().getColumns().size() == 1) {
36679                                                                                                targetTableColumn.setSourceColumn(columnModel);
36680                                                                                                targetTableColumn.setStruct(true);
36681                                                                                        }
36682                                                                                }
36683                                                                                if (columnName.getSourceTable() != null
36684                                                                                                && columnName.getSourceTable().getTableType() == ETableSource.function) {
36685                                                                                        analyzeTableColumn(columnModel, columnName.getSourceTable().getFuncCall(),
36686                                                                                                        effectType);
36687                                                                                }
36688                                                                        }
36689                                                                }
36690                                                        }
36691                                                }
36692                                        } else if (modelManager.getModel(table) instanceof QueryTable) {
36693                                                QueryTable queryTable = (QueryTable) modelManager.getModel(table);
36694
36695                                                TObjectNameList cteColumns = null;
36696                                                TSelectSqlStatement subquery = null;
36697                                                if (queryTable.getTableObject().getCTE() != null) {
36698                                                        subquery = queryTable.getTableObject().getCTE().getSubquery();
36699                                                        cteColumns = queryTable.getTableObject().getCTE().getColumnList();
36700                                                } else if (queryTable.getTableObject().getAliasClause() != null
36701                                                                && queryTable.getTableObject().getAliasClause().getColumns() != null) {
36702
36703                                                } else if (queryTable.getTableObject().getTableExpr() != null
36704                                                                && queryTable.getTableObject().getTableExpr().getSubQuery() != null) {
36705                                                        subquery = queryTable.getTableObject().getTableExpr().getSubQuery();
36706                                                } else {
36707                                                        subquery = queryTable.getTableObject().getSubquery();
36708                                                }
36709
36710                                                if (cteColumns != null) {
36711                                                        for (int j = 0; j < cteColumns.size(); j++) {
36712                                                                modelFactory.createResultColumn(queryTable, cteColumns.getObjectName(j));
36713                                                        }
36714                                                }
36715
36716                                                if (linkStructFieldReadThroughQuery(queryTable, subquery, columnName, modelObject, relation)) {
36717                                                        continue;
36718                                                }
36719
36720                                                if (subquery != null && subquery.isCombinedQuery()) {
36721                                                        SelectSetResultSet selectSetResultSetModel = (SelectSetResultSet) modelManager
36722                                                                        .getModel(subquery);
36723
36724                                                        if (selectSetResultSetModel != null
36725                                                                        && !selectSetResultSetModel.getRelationRows().getHoldRelations().isEmpty()) {
36726                                                                ImpactRelationship impactRelation = modelFactory.createImpactRelation();
36727                                                                impactRelation.setEffectType(EffectType.select);
36728                                                                impactRelation.addSource(new RelationRowsRelationshipElement<ResultSetRelationRows>(
36729                                                                                selectSetResultSetModel.getRelationRows()));
36730                                                                impactRelation.setTarget(new RelationRowsRelationshipElement<ResultSetRelationRows>(
36731                                                                                queryTable.getRelationRows()));
36732                                                        }
36733
36734                                                        if (selectSetResultSetModel != null) {
36735                                                                if (isWildcardReference(columnName)) {
36736                                                                        Map<String, Pair<String, TExpression>> replaceAsIdentifierMap = new HashMap<String, Pair<String, TExpression>>();
36737                                                                        Map<String, TObjectName> replaceColumnMap = new HashMap<String, TObjectName>();
36738                                                                        if(modelObject instanceof ResultColumn && ((ResultColumn)modelObject).getColumnObject() instanceof TResultColumn) {
36739                                                                                TResultColumn resultColumn =  (TResultColumn )((ResultColumn)modelObject).getColumnObject();
36740                                                                                if(resultColumn.getReplaceExprAsIdentifiers()!=null && resultColumn.getReplaceExprAsIdentifiers().size()>0) {
36741                                                                                        for(TReplaceExprAsIdentifier replace: resultColumn.getReplaceExprAsIdentifiers()) {
36742                                                                                                replaceAsIdentifierMap.put(replace.getIdentifier().toString(), new Pair<String, TExpression>(resultColumn.getExpr().getExceptReplaceClause().toString(), replace.getExpr()));
36743                                                                                                replaceColumnMap.put(replace.getIdentifier().toString(), replace.getIdentifier());
36744                                                                                        }
36745                                                                                        ResultSet resultSet = ((ResultColumn)modelObject).getResultSet();
36746                                                                                        if(selectSetResultSetModel.isDetermined()) {
36747                                                                                                resultSet.getColumns().clear();
36748                                                                                        }
36749                                                                                }
36750                                                                        }
36751                                                                        
36752                                                                        
36753                                                                        for (int j = 0; j < selectSetResultSetModel.getColumns().size(); j++) {
36754                                                                                ResultColumn sourceColumn = selectSetResultSetModel.getColumns().get(j);
36755                                                                                if (cteColumns != null) {
36756                                                                                        if (j < cteColumns.size()) {
36757                                                                                                ResultColumn targetColumn = queryTable.getColumns().get(j);
36758
36759                                                                                                if (exceptColumnList != null) {
36760                                                                                                        boolean flag = false;
36761                                                                                                        for (TObjectName objectName : exceptColumnList) {
36762                                                                                                                if (DlineageUtil.sameColumnName(objectName, targetColumn.getName())) {
36763                                                                                                                        flag = true;
36764                                                                                                                        break;
36765                                                                                                                }
36766                                                                                                        }
36767                                                                                                        if (flag) {
36768                                                                                                                continue;
36769                                                                                                        }
36770                                                                                                }
36771
36772                                                                                                if (replaceAsIdentifierMap.containsKey(targetColumn.getName())) {
36773                                                                                                        Pair<String, TExpression> expr = replaceAsIdentifierMap.get(targetColumn.getName());
36774                                                                                                        ResultSet resultSet = ((ResultColumn)modelObject).getResultSet();
36775                                                                                                        ResultColumn resultColumn = modelFactory.createResultColumn(resultSet, replaceColumnMap.get(targetColumn.getName()));
36776                                                                                                        Transform transform = new Transform();
36777                                                                                                        transform.setType(Transform.EXPRESSION);
36778                                                                                                        TObjectName expression = new TObjectName();
36779                                                                                                        expression.setString(expr.first);
36780                                                                                                transform.setCode(expression);
36781                                                                                                        resultColumn.setTransform(transform);
36782                                                                                                        analyzeResultColumnExpressionRelation(resultColumn, expr.second);
36783                                                                                                } else {
36784                                                                                                        if(!replaceAsIdentifierMap.isEmpty()) {
36785                                                                                                                ResultSet resultSet = ((ResultColumn) modelObject).getResultSet();
36786                                                                                                                TObjectName resultColumnName = new TObjectName();
36787                                                                                                                resultColumnName.setString(targetColumn.getName());
36788                                                                                                                ResultColumn resultColumn = modelFactory.createResultColumn(resultSet,
36789                                                                                                                                resultColumnName);
36790                                                                                                                DataFlowRelationship relation1 = modelFactory.createDataFlowRelation();
36791                                                                                                                relation1.setEffectType(effectType);
36792                                                                                                                relation1.setProcess(process);
36793                                                                                                                relation1.setTarget(new ResultColumnRelationshipElement(resultColumn));
36794                                                                                                                relation1.addSource(new ResultColumnRelationshipElement(targetColumn));
36795                                                                                                        }
36796                                                                                                        else {
36797                                                                                                                relation.addSource(
36798                                                                                                                        new ResultColumnRelationshipElement(targetColumn));
36799                                                                                                        }
36800                                                                                                }
36801                                                                                                
36802                                                                                        }
36803                                                                                } else {
36804                                                                                        ResultColumn targetColumn = modelFactory
36805                                                                                                        .createSelectSetResultColumn(queryTable, sourceColumn);
36806
36807                                                                                        DataFlowRelationship combinedQueryRelation = modelFactory
36808                                                                                                        .createDataFlowRelation();
36809                                                                                        combinedQueryRelation.setEffectType(effectType);
36810                                                                                        combinedQueryRelation
36811                                                                                                        .setTarget(new ResultColumnRelationshipElement(targetColumn));
36812                                                                                        combinedQueryRelation
36813                                                                                                        .addSource(new ResultColumnRelationshipElement(sourceColumn));
36814
36815                                                                                        relation.addSource(new ResultColumnRelationshipElement(targetColumn));
36816                                                                                }
36817                                                                        }
36818                                                                        break;
36819                                                                } else {
36820                                                                        boolean flag = false;
36821
36822                                                                        for (int j = 0; j < selectSetResultSetModel.getColumns().size(); j++) {
36823                                                                                ResultColumn sourceColumn = selectSetResultSetModel
36824                                                                                                .getColumns().get(j);
36825                                                                                List<String> splits = SQLUtil.parseNames(columnName.toString());        
36826                                                                                if (splits.size() > 1 && EDbVendor.dbvbigquery == getOption().getVendor()) {
36827                                                                                        if (DlineageUtil.sameColumnSegment(sourceColumn.getName(), splits.get(0))
36828                                                                                                        || DlineageUtil.sameColumnSegment(sourceColumn.getName(), splits.get(1))) {
36829                                                                                                ResultColumn targetColumn = modelFactory
36830                                                                                                                .createSelectSetResultColumn(queryTable, sourceColumn);
36831
36832                                                                                                DataFlowRelationship combinedQueryRelation = modelFactory
36833                                                                                                                .createDataFlowRelation();
36834                                                                                                combinedQueryRelation.setEffectType(effectType);
36835                                                                                                combinedQueryRelation.setTarget(
36836                                                                                                                new ResultColumnRelationshipElement(targetColumn));
36837                                                                                                combinedQueryRelation.addSource(
36838                                                                                                                new ResultColumnRelationshipElement(sourceColumn));
36839
36840                                                                                                relation.addSource(
36841                                                                                                                new ResultColumnRelationshipElement(targetColumn));
36842                                                                                                flag = true;
36843                                                                                                break;
36844                                                                                        }
36845                                                                                }
36846                                                                                else if (DlineageUtil.sameColumnName(sourceColumn.getName(), columnName)) {
36847                                                                                        ResultColumn targetColumn = modelFactory
36848                                                                                                        .createSelectSetResultColumn(queryTable, sourceColumn);
36849
36850                                                                                        DataFlowRelationship combinedQueryRelation = modelFactory
36851                                                                                                        .createDataFlowRelation();
36852                                                                                        combinedQueryRelation.setEffectType(effectType);
36853                                                                                        combinedQueryRelation
36854                                                                                                        .setTarget(new ResultColumnRelationshipElement(targetColumn));
36855                                                                                        combinedQueryRelation
36856                                                                                                        .addSource(new ResultColumnRelationshipElement(sourceColumn));
36857
36858                                                                                        relation.addSource(new ResultColumnRelationshipElement(targetColumn));
36859                                                                                        flag = true;
36860                                                                                        break;
36861                                                                                }
36862                                                                                else if (sourceColumn instanceof SelectSetResultColumn && ((SelectSetResultColumn)sourceColumn).getAliasSet().size() > 1) {
36863                                                                                        for (String alias : ((SelectSetResultColumn)sourceColumn).getAliasSet()) {
36864                                                                                                if (DlineageUtil.sameColumnName(alias, columnName)) {
36865                                                                                                        ResultColumn targetColumn = modelFactory
36866                                                                                                                        .createSelectSetResultColumn(queryTable, sourceColumn);
36867
36868                                                                                                        DataFlowRelationship combinedQueryRelation = modelFactory
36869                                                                                                                        .createDataFlowRelation();
36870                                                                                                        combinedQueryRelation.setEffectType(effectType);
36871                                                                                                        combinedQueryRelation.setTarget(
36872                                                                                                                        new ResultColumnRelationshipElement(targetColumn));
36873                                                                                                        combinedQueryRelation.addSource(
36874                                                                                                                        new ResultColumnRelationshipElement(sourceColumn));
36875
36876                                                                                                        relation.addSource(
36877                                                                                                                        new ResultColumnRelationshipElement(targetColumn));
36878                                                                                                        flag = true;
36879                                                                                                        break;
36880                                                                                                }
36881                                                                                        }
36882                                                                                        if (flag) {
36883                                                                                                break;
36884                                                                                        }
36885                                                                                }
36886                                                                        }
36887
36888                                                                        if (flag) {
36889                                                                                break;
36890                                                                        } else if (columnIndex != -1) {
36891                                                                                for (int j = 0; j < selectSetResultSetModel.getColumns().size(); j++) {
36892                                                                                        ResultColumn sourceColumn = selectSetResultSetModel.getColumns().get(j);
36893                                                                                        if (sourceColumn.hasStarLinkColumn()) {
36894                                                                                                if (cteColumns != null) {
36895                                                                                                        // the column list names the CTE's columns: a reference reads the column of its
36896                                                                                                        // name, not the one at the position of the set operation's star (Mantis 4914)
36897                                                                                                        ResultColumn namedColumn = null;
36898                                                                                                        for (ResultColumn cteColumn : queryTable.getColumns()) {
36899                                                                                                                if (namedColumn == null && DlineageUtil.sameColumnName(cteColumn.getName(), columnName)) {
36900                                                                                                                        namedColumn = cteColumn;
36901                                                                                                                }
36902                                                                                                        }
36903                                                                                                        if (namedColumn != null) {
36904                                                                                                                relation.addSource(new ResultColumnRelationshipElement(namedColumn));
36905                                                                                                        } else if (j < cteColumns.size()) {
36906                                                                                                                ResultColumn targetColumn = queryTable.getColumns().get(j);
36907                                                                                                                relation.addSource(
36908                                                                                                                                new ResultColumnRelationshipElement(targetColumn));
36909                                                                                                        }
36910                                                                                                } 
36911                                                                                                else {
36912                                                                                                        if(sourceColumn.hasStarLinkColumn()) {
36913                                                                                                                for (TObjectName linkColumn : sourceColumn.getStarLinkColumnList()) {
36914                                                                                                                        if (DlineageUtil.sameColumnName(linkColumn, columnName)) {
36915                                                                                                                                ResultColumn targetColumn = modelFactory
36916                                                                                                                                                .createSelectSetResultColumn(queryTable, sourceColumn);
36917
36918                                                                                                                                DataFlowRelationship combinedQueryRelation = modelFactory
36919                                                                                                                                                .createDataFlowRelation();
36920                                                                                                                                combinedQueryRelation.setEffectType(effectType);
36921                                                                                                                                combinedQueryRelation.setTarget(
36922                                                                                                                                                new ResultColumnRelationshipElement(targetColumn, linkColumn));
36923                                                                                                                                combinedQueryRelation.addSource(
36924                                                                                                                                                new ResultColumnRelationshipElement(sourceColumn));
36925
36926                                                                                                                                relation.addSource(
36927                                                                                                                                                new ResultColumnRelationshipElement(targetColumn, linkColumn));
36928                                                                                                                                flag = true;
36929                                                                                                                                break;
36930                                                                                                                        }
36931                                                                                                                }
36932                                                                                                        }
36933                                                                                                        
36934                                                                                                        if (!flag && sourceColumn.isStarColumn()
36935                                                                                                                        && branchesReadTheSameTables(subquery)) {
36936                                                                                                                // a name the set operation's * may hold: read it from the
36937                                                                                                                // star by that name, as a star of a plain subquery is read
36938                                                                                                                // (appendResultColumnRelationSource), so only the sources
36939                                                                                                                // that can have it feed the column, not every column the
36940                                                                                                                // star reads (Mantis 4974)
36941                                                                                                                if (!containsStarColumn(sourceColumn, columnName)) {
36942                                                                                                                        sourceColumn.bindStarLinkColumn(columnName);
36943                                                                                                                }
36944                                                                                                                ResultColumn targetColumn = modelFactory
36945                                                                                                                                .createSelectSetResultColumn(queryTable, sourceColumn);
36946                                                                                                                if (!containsStarColumn(targetColumn, columnName)) {
36947                                                                                                                        targetColumn.bindStarLinkColumn(columnName);
36948                                                                                                                }
36949                                                                                                                DataFlowRelationship combinedQueryRelation = modelFactory
36950                                                                                                                                .createDataFlowRelation();
36951                                                                                                                combinedQueryRelation.setEffectType(effectType);
36952                                                                                                                combinedQueryRelation.setTarget(
36953                                                                                                                                new ResultColumnRelationshipElement(targetColumn, columnName));
36954                                                                                                                combinedQueryRelation.addSource(
36955                                                                                                                                new ResultColumnRelationshipElement(sourceColumn));
36956                                                                                                                relation.addSource(
36957                                                                                                                                new ResultColumnRelationshipElement(targetColumn, columnName));
36958                                                                                                        } else if (!flag) {
36959                                                                                                                ResultColumn targetColumn = modelFactory
36960                                                                                                                                .createSelectSetResultColumn(queryTable, sourceColumn);
36961
36962                                                                                                                DataFlowRelationship combinedQueryRelation = modelFactory
36963                                                                                                                                .createDataFlowRelation();
36964                                                                                                                combinedQueryRelation.setEffectType(effectType);
36965                                                                                                                combinedQueryRelation.setTarget(
36966                                                                                                                                new ResultColumnRelationshipElement(targetColumn));
36967                                                                                                                combinedQueryRelation.addSource(
36968                                                                                                                                new ResultColumnRelationshipElement(sourceColumn));
36969
36970                                                                                                                relation.addSource(
36971                                                                                                                                new ResultColumnRelationshipElement(targetColumn));
36972                                                                                                        }
36973                                                                                                }
36974                                                                                                flag = true;
36975                                                                                                break;
36976                                                                                        }
36977                                                                                }
36978                                                                        }
36979
36980                                                                        if (flag) {
36981                                                                                break;
36982                                                                        } else if (columnIndex < selectSetResultSetModel.getColumns().size()
36983                                                                                        && columnIndex != -1) {
36984                                                                                ResultColumn sourceColumn = selectSetResultSetModel.getColumns()
36985                                                                                                .get(columnIndex);
36986                                                                                if (cteColumns != null) {
36987                                                                                        boolean flag1 = false;
36988                                                                                        for (ResultColumn targetColumn : queryTable.getColumns()) {
36989                                                                                                if (DlineageUtil.sameColumnName(targetColumn.getName(), columnName)) {
36990                                                                                                        relation.addSource(
36991                                                                                                                        new ResultColumnRelationshipElement(targetColumn));
36992                                                                                                        flag1 = true;
36993                                                                                                        break;
36994                                                                                                }
36995                                                                                        }
36996                                                                                        if (!flag1 && columnIndex < cteColumns.size()){
36997                                                                                                ResultColumn targetColumn = queryTable.getColumns()
36998                                                                                                                .get(columnIndex);
36999                                                                                                relation.addSource(
37000                                                                                                                new ResultColumnRelationshipElement(targetColumn));
37001                                                                                        }
37002                                                                                } else {
37003                                                                                        ResultColumn targetColumn = modelFactory
37004                                                                                                        .createSelectSetResultColumn(queryTable, sourceColumn);
37005
37006                                                                                        DataFlowRelationship combinedQueryRelation = modelFactory
37007                                                                                                        .createDataFlowRelation();
37008                                                                                        combinedQueryRelation.setEffectType(effectType);
37009                                                                                        combinedQueryRelation
37010                                                                                                        .setTarget(new ResultColumnRelationshipElement(targetColumn));
37011                                                                                        combinedQueryRelation
37012                                                                                                        .addSource(new ResultColumnRelationshipElement(sourceColumn));
37013
37014                                                                                        relation.addSource(new ResultColumnRelationshipElement(targetColumn));
37015                                                                                }
37016                                                                                flag = true;
37017                                                                                break;
37018                                                                        }
37019
37020                                                                        if (flag) {
37021                                                                                break;
37022                                                                        }
37023                                                                }
37024                                                        } else if (cteColumns != null) {
37025                                                                if (isWildcardReference(columnName)) {
37026                                                                        Map<String, Pair<String, TExpression>> replaceAsIdentifierMap = new HashMap<String, Pair<String, TExpression>>();
37027                                                                        Map<String, TObjectName> replaceColumnMap = new HashMap<String, TObjectName>();
37028                                                                        if(modelObject instanceof ResultColumn && ((ResultColumn)modelObject).getColumnObject() instanceof TResultColumn) {
37029                                                                                TResultColumn resultColumn =  (TResultColumn )((ResultColumn)modelObject).getColumnObject();
37030                                                                                if(resultColumn.getReplaceExprAsIdentifiers()!=null && resultColumn.getReplaceExprAsIdentifiers().size()>0) {
37031                                                                                        for(TReplaceExprAsIdentifier replace: resultColumn.getReplaceExprAsIdentifiers()) {
37032                                                                                                replaceAsIdentifierMap.put(replace.getIdentifier().toString(), new Pair<String, TExpression>(resultColumn.getExpr().getExceptReplaceClause().toString(), replace.getExpr()));
37033                                                                                                replaceColumnMap.put(replace.getIdentifier().toString(), replace.getIdentifier());
37034                                                                                        }
37035                                                                                        ResultSet resultSet = ((ResultColumn)modelObject).getResultSet();
37036                                                                                        
37037                                                                                        if (columnName.getSourceColumn() != null) {
37038                                                                                                Object model = modelManager.getModel(columnName.getSourceColumn());
37039                                                                                                if (model instanceof ResultColumn && ((ResultColumn)model).getResultSet().isDetermined()) {
37040                                                                                                        resultSet.getColumns().clear();
37041                                                                                                }
37042                                                                                        } else if (columnName.getSourceTable() != null) {
37043                                                                                                Object tableModel = modelManager.getModel(columnName.getSourceTable());
37044                                                                                                if (tableModel instanceof Table && ((Table)tableModel).isDetermined()) {
37045                                                                                                        resultSet.getColumns().clear();
37046                                                                                                }
37047                                                                                        }
37048                                                                                }
37049                                                                        }
37050                                                                        
37051                                                                        for (int j = 0; j < cteColumns.size(); j++) {
37052                                                                                ResultColumn targetColumn = queryTable.getColumns().get(j);
37053
37054                                                                                if (exceptColumnList != null) {
37055                                                                                        boolean flag = false;
37056                                                                                        for (TObjectName objectName : exceptColumnList) {
37057                                                                                                if (DlineageUtil.sameColumnName(objectName, targetColumn.getName())) {
37058                                                                                                        flag = true;
37059                                                                                                        break;
37060                                                                                                }
37061                                                                                        }
37062                                                                                        if (flag) {
37063                                                                                                continue;
37064                                                                                        }
37065                                                                                }
37066
37067                                                                                if (replaceAsIdentifierMap.containsKey(targetColumn.getName())) {
37068                                                                                        Pair<String, TExpression> expr = replaceAsIdentifierMap.get(targetColumn.getName());
37069                                                                                        ResultSet resultSet = ((ResultColumn)modelObject).getResultSet();
37070                                                                                        ResultColumn resultColumn = modelFactory.createResultColumn(resultSet, replaceColumnMap.get(targetColumn.getName()));
37071                                                                                        Transform transform = new Transform();
37072                                                                                        transform.setType(Transform.EXPRESSION);
37073                                                                                        TObjectName expression = new TObjectName();
37074                                                                                        expression.setString(expr.first);
37075                                                                                transform.setCode(expression);
37076                                                                                        resultColumn.setTransform(transform);
37077                                                                                        analyzeResultColumnExpressionRelation(resultColumn, expr.second);
37078                                                                                } else {
37079                                                                                        if(!replaceAsIdentifierMap.isEmpty()) {
37080                                                                                                ResultSet resultSet = ((ResultColumn) modelObject).getResultSet();
37081                                                                                                TObjectName resultColumnName = new TObjectName();
37082                                                                                                resultColumnName.setString(targetColumn.getName());
37083                                                                                                ResultColumn resultColumn = modelFactory.createResultColumn(resultSet,
37084                                                                                                                resultColumnName);
37085                                                                                                DataFlowRelationship relation1 = modelFactory.createDataFlowRelation();
37086                                                                                                relation1.setEffectType(effectType);
37087                                                                                                relation1.setProcess(process);
37088                                                                                                relation1.setTarget(new ResultColumnRelationshipElement(resultColumn));
37089                                                                                                relation1.addSource(new ResultColumnRelationshipElement(targetColumn));
37090                                                                                        }
37091                                                                                        else {
37092                                                                                                relation.addSource(
37093                                                                                                        new ResultColumnRelationshipElement(targetColumn));
37094                                                                                        }
37095                                                                                }
37096                                                                        }
37097                                                                        break;
37098                                                                } else {
37099                                                                        boolean flag = false;
37100
37101                                                                        for (int j = 0; j < cteColumns.size(); j++) {
37102                                                                                TObjectName sourceColumn = cteColumns.getObjectName(j);
37103
37104                                                                                if (DlineageUtil.sameColumnName(sourceColumn, columnName)) {
37105                                                                                        ResultColumn targetColumn = queryTable.getColumns().get(j);
37106
37107                                                                                        relation.addSource(new ResultColumnRelationshipElement(targetColumn));
37108                                                                                        flag = true;
37109                                                                                        break;
37110                                                                                }
37111                                                                        }
37112
37113                                                                        if (flag) {
37114                                                                                break;
37115                                                                        }
37116                                                                }
37117                                                        }
37118
37119                                                        if (columnName.getSourceColumn() != null) {
37120                                                                Object model = modelManager.getModel(columnName.getSourceColumn());
37121                                                                if (model instanceof ResultColumn) {
37122                                                                        ResultColumn resultColumn = (ResultColumn) model;
37123                                                                        relation.addSource(new ResultColumnRelationshipElement(resultColumn));
37124                                                                }
37125                                                        } else if (columnName.getSourceTable() != null) {
37126                                                                Object tableModel = modelManager.getModel(columnName.getSourceTable());
37127                                                                if (tableModel instanceof Table) {
37128                                                                        Object model = modelManager
37129                                                                                        .getModel(new Pair<Table, TObjectName>((Table) tableModel, columnName));
37130                                                                        if (model instanceof TableColumn) {
37131                                                                                relation.addSource(new TableColumnRelationshipElement((TableColumn) model));
37132                                                                        }
37133                                                                }
37134                                                        }
37135                                                } else {
37136                                                        List<ResultColumn> columns = queryTable.getColumns();
37137                                                        if (isWildcardReference(columnName)) {
37138                                                                Map<String, Pair<String, TExpression>> replaceAsIdentifierMap = new HashMap<String, Pair<String, TExpression>>();
37139                                                                Map<String, TObjectName> replaceColumnMap = new HashMap<String, TObjectName>();
37140                                                                if(modelObject instanceof ResultColumn && ((ResultColumn)modelObject).getColumnObject() instanceof TResultColumn) {
37141                                                                        TResultColumn resultColumn =  (TResultColumn )((ResultColumn)modelObject).getColumnObject();
37142                                                                        if(resultColumn.getReplaceExprAsIdentifiers()!=null && resultColumn.getReplaceExprAsIdentifiers().size()>0) {
37143                                                                                for(TReplaceExprAsIdentifier replace: resultColumn.getReplaceExprAsIdentifiers()) {
37144                                                                                        replaceAsIdentifierMap.put(replace.getIdentifier().toString(), new Pair<String, TExpression>(resultColumn.getExpr().getExceptReplaceClause().toString(), replace.getExpr()));
37145                                                                                        replaceColumnMap.put(replace.getIdentifier().toString(), replace.getIdentifier());
37146                                                                                }
37147                                                                                ResultSet resultSet = ((ResultColumn)modelObject).getResultSet();
37148                                                                                if(queryTable.isDetermined()) {
37149                                                                                        resultSet.getColumns().clear();
37150                                                                                }
37151                                                                        }
37152                                                                }
37153                                                                
37154                                                                int index = 0;
37155                                                                for (int j = 0; j < queryTable.getColumns().size(); j++) {
37156                                                                        ResultColumn targetColumn = queryTable.getColumns().get(j);
37157                                                                        if (exceptColumnList != null) {
37158                                                                                boolean flag = false;
37159                                                                                for (TObjectName objectName : exceptColumnList) {
37160                                                                                        if (DlineageUtil.sameColumnName(objectName, targetColumn.getName())) {
37161                                                                                                flag = true;
37162                                                                                                break;
37163                                                                                        }
37164                                                                                }
37165                                                                                if (flag) {
37166                                                                                        continue;
37167                                                                                }
37168                                                                        }
37169                                                                        
37170                                                                        if (replaceAsIdentifierMap.containsKey(targetColumn.getName())) {
37171                                                                                Pair<String, TExpression> expr = replaceAsIdentifierMap.get(targetColumn.getName());
37172                                                                                ResultSet resultSet = ((ResultColumn)modelObject).getResultSet();
37173                                                                                ResultColumn resultColumn = modelFactory.createResultColumn(resultSet, replaceColumnMap.get(targetColumn.getName()));
37174                                                                                Transform transform = new Transform();
37175                                                                                transform.setType(Transform.EXPRESSION);
37176                                                                                TObjectName expression = new TObjectName();
37177                                                                                expression.setString(expr.first);
37178                                                                        transform.setCode(expression);
37179                                                                                resultColumn.setTransform(transform);
37180                                                                                analyzeResultColumnExpressionRelation(resultColumn, expr.second);
37181                                                                        } else {
37182                                                                                if(!replaceAsIdentifierMap.isEmpty()) {
37183                                                                                        ResultSet resultSet = ((ResultColumn) modelObject).getResultSet();
37184                                                                                        TObjectName resultColumnName = new TObjectName();
37185                                                                                        resultColumnName.setString(targetColumn.getName());
37186                                                                                        ResultColumn resultColumn = modelFactory.createResultColumn(resultSet,
37187                                                                                                        resultColumnName);
37188                                                                                        DataFlowRelationship relation1 = modelFactory.createDataFlowRelation();
37189                                                                                        relation1.setEffectType(effectType);
37190                                                                                        relation1.setProcess(process);
37191                                                                                        relation1.setTarget(new ResultColumnRelationshipElement(resultColumn));
37192                                                                                        relation1.addSource(new ResultColumnRelationshipElement(targetColumn));
37193                                                                                }
37194                                                                                else {
37195                                                                                        relation.addSource(
37196                                                                                                new ResultColumnRelationshipElement(targetColumn));
37197                                                                                }
37198                                                                        }
37199                                                                        index++;
37200                                                                }
37201                                                        } else {
37202                                                                if (table.getCTE() != null) {
37203                                                                        
37204                                                                        if (modelObject instanceof TableColumn) {
37205                                                                                Table modelTable = ((TableColumn) modelObject).getTable();
37206                                                                                if (modelTable.getSubType() == SubType.unnest) {
37207                                                                                        boolean find = false;
37208                                                                                        for (k = 0; k < columns.size(); k++) {
37209                                                                                                ResultColumn column = columns.get(k);
37210                                                                                                if (column.isStruct()) {
37211                                                                                                        List<String> names = SQLUtil.parseNames(column.getName());
37212                                                                                                        for (String name : names) {
37213                                                                                                                if (DlineageUtil.sameColumnName(name, columnName)) {
37214                                                                                                                        DataFlowRelationship unnestRelation = modelFactory.createDataFlowRelation();
37215                                                                                                                        unnestRelation.setEffectType(effectType);
37216                                                                                                                        unnestRelation.setProcess(process);
37217                                                                                                                        unnestRelation.addSource(new ResultColumnRelationshipElement(
37218                                                                                                                                        column, columnName));
37219                                                                                                                        TObjectName unnestTableColumnName = new TObjectName();
37220                                                                                                                        unnestTableColumnName.setString(names.get(names.size()-1));
37221                                                                                                                        TableColumn unnestTableColumn = modelFactory.createTableColumn(modelTable, unnestTableColumnName, true);
37222                                                                                                                        unnestRelation.setTarget(new TableColumnRelationshipElement(unnestTableColumn));
37223                                                                                                                        find = true;
37224                                                                                                                }
37225                                                                                                        }
37226                                                                                                        List<String> names1 = SQLUtil.parseNames(column.getName());
37227                                                                                                        if (names.size() == 1 && names1.size() >= 1) {
37228                                                                                                                for (String name : names1) {
37229                                                                                                                        if (DlineageUtil.sameColumnName(name, column.getName())) {
37230                                                                                                                                DataFlowRelationship unnestRelation = modelFactory.createDataFlowRelation();
37231                                                                                                                                unnestRelation.setEffectType(effectType);
37232                                                                                                                                unnestRelation.setProcess(process);
37233                                                                                                                                unnestRelation.addSource(new ResultColumnRelationshipElement(
37234                                                                                                                                                column, columnName));
37235                                                                                                                                TObjectName unnestTableColumnName = new TObjectName();
37236                                                                                                                                unnestTableColumnName.setString(names1.get(names.size()-1));
37237                                                                                                                                TableColumn unnestTableColumn = modelFactory.createTableColumn(modelTable, unnestTableColumnName, true);
37238                                                                                                                                unnestRelation.setTarget(new TableColumnRelationshipElement(unnestTableColumn));
37239                                                                                                                                find = true;
37240                                                                                                                        }
37241                                                                                                                }
37242                                                                                                        }
37243                                                                                                }
37244                                                                                        }
37245                                                                                        if (find) {
37246                                                                                                modelTable.getColumns().remove(modelObject);
37247                                                                                                break;
37248                                                                                        }
37249                                                                                }
37250                                                                        }
37251                                                                        
37252                                                                        ColumnNameIndex nameIdx = getColumnNameIndex(columns);
37253                                                                        if (nameIdx.byIdentifier != null && DlineageUtil.columnKey(columnName) != null) {
37254                                                                                // Fast path: simple column list (no star/struct). A direct index lookup
37255                                                                                // reproduces the linear scan below for such lists: first-match-wins,
37256                                                                                // skip when the match IS modelObject, and stop at the first match.
37257                                                                                ResultColumn match = nameIdx.byIdentifier.get(DlineageUtil.columnKey(columnName));
37258                                                                                if (match != null && !match.equals(modelObject)) {
37259                                                                                        relation.addSource(
37260                                                                                                        new ResultColumnRelationshipElement(match, columnName));
37261                                                                                }
37262                                                                        } else {
37263                                                                        for (k = 0; k < columns.size(); k++) {
37264                                                                                ResultColumn column = columns.get(k);
37265                                                                                if (column.isStarColumn()) {
37266                                                                                        if (!containsStarColumn(column, columnName)) {
37267                                                                                                column.bindStarLinkColumn(columnName);
37268                                                                                        }
37269                                                                                        relation.addSource(new ResultColumnRelationshipElement(column, columnName));
37270                                                                                } else if (referencesColumn(columnName, column)) {
37271                                                                                        if (!column.equals(modelObject)) {
37272                                                                                                relation.addSource(
37273                                                                                                                new ResultColumnRelationshipElement(column, columnName));
37274                                                                                        }
37275                                                                                        break;
37276                                                                                } else if(column.isStruct()) {
37277                                                                                        List<String> names = SQLUtil.parseNames(column.getName());
37278                                                                                        for(String name: names) {
37279                                                                                                if (DlineageUtil.sameColumnName(name, columnName)) {
37280                                                                                                        relation.addSource(
37281                                                                                                                        new ResultColumnRelationshipElement(column, columnName));
37282                                                                                                }
37283                                                                                        }
37284                                                                                        List<String> names1 = SQLUtil.parseNames(column.getName());
37285                                                                                        if (names.size() == 1 && names1.size() >= 1) {
37286                                                                                                for(String name: names1) {
37287                                                                                                        if (DlineageUtil.sameColumnName(name, column.getName())) {
37288                                                                                                                relation.addSource(
37289                                                                                                                                new ResultColumnRelationshipElement(column, columnName));
37290                                                                                                        }
37291                                                                                                }
37292                                                                                        }
37293                                                                                }
37294                                                                        }
37295                                                                        }
37296                                                                } else if ((table.getAliasClause() != null
37297                                                                                && table.getAliasClause().getColumns() != null)
37298                                                                                // a VALUES table without a column list has the vendor's names (Mantis 4823); a
37299                                                                                // vendor that names none has none to read, whatever the model calls them
37300                                                                                || (table.getTableType() == ETableSource.rowList
37301                                                                                                && TValueClause.defaultColumnName(option.getVendor(), 1) != null)) {
37302                                                                        boolean found = false;
37303                                                                        for (k = 0; k < columns.size(); k++) {
37304                                                                                ResultColumn column = columns.get(k);
37305                                                                                List<String> splits = SQLUtil.parseNames(columnName.toString());
37306                                                                                if (column.isStarColumn()) {
37307                                                                                        readThroughStar(column, columnName, relation);
37308                                                                                        found = true;
37309                                                                                } else if (splits.size() > 1 && EDbVendor.dbvbigquery == getOption().getVendor()) {
37310                                                                                        if (referencesColumnSegment(splits.get(0), column)) {
37311                                                                                                if (!column.equals(modelObject)) {
37312                                                                                                        relation.addSource(new ResultColumnRelationshipElement(column,
37313                                                                                                                        columnName));
37314                                                                                                }
37315                                                                                                found = true;
37316                                                                                                break;
37317                                                                                        }
37318                                                                                } else if (referencesColumn(columnName, column)) {
37319                                                                                        if (!column.equals(modelObject)) {
37320                                                                                                relation.addSource(
37321                                                                                                                new ResultColumnRelationshipElement(column, columnName));
37322                                                                                        }
37323                                                                                        found = true;
37324                                                                                        break;
37325                                                                                }
37326                                                                        }
37327                                                                        if (!found) {
37328                                                                                readPastAliasList(table, columnName, relation);
37329                                                                        }
37330                                                                } else if (table.getSubquery() != null || (table.getTableExpr() != null
37331                                                                                && table.getTableExpr().getSubQuery() != null)) {
37332                                                                        TSelectSqlStatement select = table.getSubquery();
37333                                                                        if (select == null) {
37334                                                                                select = table.getTableExpr().getSubQuery();
37335                                                                        }
37336                                                                        if (columnName.getSourceTable() != null) {
37337                                                                                Object tableModel = modelManager.getModel(columnName.getSourceTable());
37338                                                                                appendResultColumnRelationSource(modelObject, relation, columnIndex, columnName,
37339                                                                                                tableModel);
37340                                                                        } else if (columnName.getObjectToken() != null
37341                                                                                        && !SQLUtil.isEmpty(table.getAliasName())) {
37342                                                                                if (DlineageUtil.compareTableIdentifier(columnName.getObjectToken().toString(),
37343                                                                                                table.getAliasName())) {
37344                                                                                        Object tableModel = modelManager.getModel(table);
37345                                                                                        appendResultColumnRelationSource(modelObject, relation, columnIndex,
37346                                                                                                        columnName, tableModel);
37347                                                                                }
37348                                                                        } else if(columns!=null) {
37349                                                                                for (k = 0; k < columns.size(); k++) {
37350                                                                                        ResultColumn column = columns.get(k);
37351                                                                                        List<String> splits = SQLUtil.parseNames(columnName.toString());        
37352                                                                                        if (column.isStarColumn()) {
37353                                                                                                if (!containsStarColumn(column, columnName)) {
37354                                                                                                        column.bindStarLinkColumn(columnName);
37355                                                                                                }
37356                                                                                                relation.addSource(new ResultColumnRelationshipElement(column, columnName));
37357                                                                                        } else if (splits.size() > 1 && EDbVendor.dbvbigquery == getOption().getVendor()) {
37358                                                                                                if (referencesColumnSegment(splits.get(0), column)) {
37359                                                                                                        if (!column.equals(modelObject)) {
37360                                                                                                                relation.addSource(new ResultColumnRelationshipElement(column,
37361                                                                                                                                columnName));
37362                                                                                                        }
37363                                                                                                        break;
37364                                                                                                }
37365                                                                                        } else if (referencesColumn(columnName, column)) {
37366                                                                                                if (!column.equals(modelObject)) {
37367                                                                                                        relation.addSource(
37368                                                                                                                        new ResultColumnRelationshipElement(column, columnName));
37369                                                                                                }
37370                                                                                                break;
37371                                                                                        }
37372                                                                                }
37373                                                                        }
37374                                                                } else if (table.getOutputMerge() != null) {
37375                                                                        if (columnName.getSourceColumn() != null) {
37376                                                                                Object model = modelManager.getModel(columnName.getSourceColumn());
37377                                                                                if (model instanceof ResultColumn) {
37378                                                                                        ResultColumn resultColumn = (ResultColumn) model;
37379                                                                                        if (resultColumn.isStarColumn()
37380                                                                                                        && !containsStarColumn(resultColumn, columnName)) {
37381                                                                                                resultColumn.bindStarLinkColumn(columnName);
37382                                                                                        }
37383                                                                                        relation.addSource(
37384                                                                                                        new ResultColumnRelationshipElement(resultColumn, columnName));
37385                                                                                }
37386                                                                        } else if (columnName.getSourceTable() != null) {
37387                                                                                Object tableModel = modelManager.getModel(columnName.getSourceTable());
37388                                                                                appendResultColumnRelationSource(modelObject, relation, columnIndex, columnName,
37389                                                                                                tableModel);
37390                                                                        } else if (columnName.getObjectToken() != null
37391                                                                                        && !SQLUtil.isEmpty(table.getAliasName())) {
37392                                                                                if (DlineageUtil.compareTableIdentifier(columnName.getObjectToken().toString(),
37393                                                                                                table.getAliasName())) {
37394                                                                                        Object tableModel = modelManager.getModel(table);
37395                                                                                        appendResultColumnRelationSource(modelObject, relation, columnIndex,
37396                                                                                                        columnName, tableModel);
37397                                                                                }
37398                                                                        }
37399                                                                }
37400                                                        }
37401                                                }
37402                                        }
37403                                }
37404                        }
37405                        if (relation.getSources().size() == 0 && isKeyword(columnName)) {
37406                                Table constantTable = modelFactory.createConstantsTable(stmtStack.peek());
37407                                TableColumn constantColumn = modelFactory.createTableColumn(constantTable, columnName, true);
37408                                relation.addSource(new ConstantRelationshipElement(constantColumn));
37409                        }
37410
37411                        addRelationRowsImpacts(relation, effectType);
37412                }
37413                } finally {
37414                        if (sqllrRefs != null) {
37415                                sqllrRefs.closeFrame(sqllrFrame);
37416                        }
37417                }
37418                return relation;
37419        }
37420
37421        /**
37422         * The row lineage of {@code relation}'s sources: an fdr edge into its target's rows from the rows of each source
37423         * whose relation has row lineage of its own, as every column read publishes.
37424         */
37425        private void addRelationRowsImpacts(DataFlowRelationship relation, EffectType effectType) {
37426                if (relation.getSources().size() > 0) {
37427                        for (RelationshipElement<?> sourceItem: relation.getSources()) {
37428                                Object source = sourceItem.getElement();
37429                                ImpactRelationship impactRelation = null;
37430                                if (source instanceof ResultColumn
37431                                                && !((ResultColumn) source).getResultSet().getRelationRows().getHoldRelations().isEmpty()) {
37432                                        impactRelation = modelFactory.createImpactRelation();
37433                                        impactRelation.addSource(new RelationRowsRelationshipElement<ResultSetRelationRows>(
37434                                                        ((ResultColumn) source).getResultSet().getRelationRows()));
37435                                        impactRelation.setEffectType(effectType);
37436                                } else if (source instanceof TableColumn
37437                                                && !((TableColumn) source).getTable().getRelationRows().getHoldRelations().isEmpty()) {
37438                                        impactRelation = modelFactory.createImpactRelation();
37439                                        impactRelation.addSource(new RelationRowsRelationshipElement<TableRelationRows>(
37440                                                        ((TableColumn) source).getTable().getRelationRows()));
37441                                        impactRelation.setEffectType(effectType);;
37442                                }
37443
37444                                if (impactRelation == null) {
37445                                        continue;
37446                                }
37447
37448                                if (relation.getTarget() != null) {
37449                                        Object target = relation.getTarget().getElement();
37450                                        if (target instanceof ResultColumn) {
37451                                                impactRelation.setTarget(new RelationRowsRelationshipElement<ResultSetRelationRows>(
37452                                                                ((ResultColumn) target).getResultSet().getRelationRows()));
37453                                        } else if (target instanceof TableColumn) {
37454                                                impactRelation.setTarget(new RelationRowsRelationshipElement<TableRelationRows>(
37455                                                                ((TableColumn) target).getTable().getRelationRows()));
37456                                        }
37457                                }
37458
37459                                if (impactRelation.getSources() != null && !impactRelation.getSources().isEmpty()
37460                                                && impactRelation.getTarget() != null
37461                                                && impactRelation.getSources().iterator().next().getElement() == impactRelation.getTarget().getElement()) {
37462                                        modelManager.removeRelation(impactRelation);
37463                                }
37464                        }
37465                }
37466        }
37467
37468        private boolean isNotInProcedure(Table table) {
37469        TStoredProcedureSqlStatement stmt = getProcedureParent(stmtStack.peek());
37470                Procedure procedure = this.modelFactory.createProcedure(stmt);
37471                if(procedure!=null && procedure.getName().equals(table.getParent())){
37472                        return false;
37473                }
37474                return true;
37475        }
37476
37477        private boolean hasJoin(TCustomSqlStatement stmt) {
37478                if (stmt.getJoins() == null || stmt.getJoins().size() == 0)
37479                        return false;
37480                if (stmt.getJoins().size() > 1) {
37481                        return true;
37482                }
37483                TJoinItemList joinItems = stmt.getJoins().getJoin(0).getJoinItems();
37484                if (joinItems == null || joinItems.size() == 0) {
37485                        return false;
37486                }
37487                return true;
37488        }
37489
37490        private boolean isInQuery(TSelectSqlStatement query, TResultColumn column) {
37491                if(query == null)
37492                        return false;
37493                TResultColumnList columns = query.getResultColumnList();
37494                if (columns != null) {
37495                        for (int i = 0; i < columns.size(); i++) {
37496                                if (columns.getResultColumn(i).equals(column)) {
37497                                        return true;
37498                                }
37499                        }
37500                }
37501                return false;
37502        }
37503
37504        private void appendResultColumnRelationSource(Object modelObject, DataFlowRelationship relation, int columnIndex,
37505                        TObjectName columnName, Object tableModel) {
37506                if (tableModel instanceof Table) {
37507                        Object model = modelManager.getModel(new Pair<Table, TObjectName>((Table) tableModel, columnName));
37508                        if (model instanceof TableColumn) {
37509                                relation.addSource(new TableColumnRelationshipElement((TableColumn) model));
37510                        }
37511                } else if (tableModel instanceof ResultSet) {
37512                        List<ResultColumn> queryColumns = ((ResultSet) tableModel).getColumns();
37513                        boolean flag = false;
37514                        for (int l = 0; l < queryColumns.size(); l++) {
37515                                ResultColumn column = queryColumns.get(l);
37516                                if (referencesColumn(columnName, column)) {
37517                                        if (!column.equals(modelObject)) {
37518                                                relation.addSource(new ResultColumnRelationshipElement(column, columnName));
37519                                                flag = true;
37520                                        }
37521                                        break;
37522                                }
37523                        }
37524                        if (!flag) {
37525                                for (int l = 0; l < queryColumns.size(); l++) {
37526                                        ResultColumn column = queryColumns.get(l);
37527                                        if (column.isStarColumn()) {
37528                                                if (!containsStarColumn(column, columnName)) {
37529                                                        column.bindStarLinkColumn(columnName);
37530                                                }
37531                                                relation.addSource(new ResultColumnRelationshipElement(column, columnName));
37532                                                flag = true;
37533                                                break;
37534                                        }
37535                                }
37536                        }
37537                        if (!flag && columnIndex < queryColumns.size() && columnIndex != -1
37538                                        && (DlineageUtil.isWildcard(columnName)
37539                                                        || queryColumns.get(columnIndex).getIdentifierKey() == null)) {
37540                                relation.addSource(new ResultColumnRelationshipElement(queryColumns.get(columnIndex), columnName));
37541                        }
37542                }
37543        }
37544
37545        private boolean isApplyJoin(TCustomSqlStatement stmt) {
37546                if (stmt.getJoins() == null || stmt.getJoins().size() == 0)
37547                        return false;
37548                TJoinItemList joinItems = stmt.getJoins().getJoin(0).getJoinItems();
37549                if (joinItems == null || joinItems.size() == 0) {
37550                        return false;
37551                }
37552                if (joinItems.getJoinItem(0).getJoinType() == EJoinType.crossapply
37553                                || joinItems.getJoinItem(0).getJoinType() == EJoinType.outerapply)
37554                        return true;
37555                return false;
37556        }
37557
37558        private boolean containsStarColumn(ResultColumn resultColumn, TObjectName columnName) {
37559                return resultColumn.containsStarLinkColumn(columnName);
37560        }
37561
37562        private void analyzeAggregate(TFunctionCall function, TExpression expr) {
37563                // SQL-LR: a nested analysis never inherits the reference of the loop that called it
37564                SqllrObservations sqllr = modelManager.getSqllrObservations();
37565                int sqllrBarrier = sqllr == null ? -1 : sqllr.openFrame();
37566                try {
37567                        analyzeAggregateInSqllrBarrier(function, expr);
37568                } finally {
37569                        if (sqllr != null) {
37570                                sqllr.closeFrame(sqllrBarrier);
37571                        }
37572                }
37573        }
37574
37575        private void analyzeAggregateInSqllrBarrier(TFunctionCall function, TExpression expr) {
37576                TCustomSqlStatement stmt = stmtStack.peek();
37577                ResultSet resultSet = (ResultSet) modelManager.getModel(stmt.getResultColumnList());
37578                if (resultSet == null) {
37579                        return;
37580                }
37581
37582                if (expr != null) {
37583                        columnsInExpr visitor = new columnsInExpr();
37584                        expr.inOrderTraverse(visitor);
37585                        List<TObjectName> objectNames = visitor.getObjectNames();
37586                        SqllrObservations sqllrRefs = modelManager.getSqllrObservations();
37587                        int sqllrFrame = sqllrRefs == null ? -1 : sqllrRefs.openFrame();
37588                        try { // SQL-LR: each element added in this loop is recorded with the reference of its iteration
37589                        for (int j = 0; j < objectNames.size(); j++) {
37590                                TObjectName columnName = objectNames.get(j);
37591                                if (sqllrRefs != null) {
37592                                        sqllrRefs.reference(sqllrFrame, columnName);
37593                                }
37594
37595                                if (columnName.getDbObjectType() == EDbObjectType.variable) {
37596                                        continue;
37597                                }
37598
37599                                if (columnName.getColumnNameOnly().startsWith("@")
37600                                                && (option.getVendor() == EDbVendor.dbvmssql || option.getVendor() == EDbVendor.dbvazuresql)) {
37601                                        continue;
37602                                }
37603
37604                                if (columnName.getColumnNameOnly().startsWith(":")
37605                                                && (option.getVendor() == EDbVendor.dbvhana || option.getVendor() == EDbVendor.dbvteradata)) {
37606                                        continue;
37607                                }
37608
37609                                Object targetModel0 = modelManager.getModel(function.getFunctionName());
37610                                if (targetModel0 == null && function.getFunctionType() == EFunctionType.array_agg_t && modelManager.getModel(function) instanceof Function) {
37611                                        Function functionModel = (Function) modelManager.getModel(function);
37612                                        if (functionModel.getColumns().size() == 1) {
37613                                                targetModel0 = ((Function) functionModel).getColumns().get(0);
37614                                        }
37615                                }
37616                                if (!(targetModel0 instanceof ResultColumn)) {
37617                                        continue;
37618                                }
37619                                ResultColumn targetResultColumn0 = (ResultColumn) targetModel0;
37620                                AbstractRelationship relation = modelFactory.createRecordSetRelation();
37621                                relation.setEffectType(EffectType.function);
37622                                relation.setFunction(function.getFunctionName().toString());
37623                                relation.setTarget(new ResultColumnRelationshipElement(targetResultColumn0));
37624                                TTable table = structFieldReadTable(modelManager.getTable(stmt, columnName), columnName, stmt.tables);
37625                                if (table != null) {
37626                                        if (modelManager.getModel(table) instanceof Table) {
37627                                                Table tableModel = (Table) modelManager.getModel(table);
37628                                                if (tableModel != null) {
37629                                                        TableColumn columnModel = modelFactory.createTableColumn(tableModel, columnName, false);
37630                                                        if (columnModel != null) {
37631                                                                relation.addSource(
37632                                                                                new TableColumnRelationshipElement(columnModel, columnName.getLocation(),
37633                                                                                                columnName));
37634                                                        }
37635                                                }
37636                                        } else if (modelManager.getModel(table) instanceof QueryTable) {
37637                                                Object model = modelManager.getModel(columnName.getSourceColumn());
37638                                                if (!(model instanceof ResultColumn)) {
37639                                                        model = structFieldReadSource(structFieldReadBaseColumn(columnName,
37640                                                                        ((QueryTable) modelManager.getModel(table)).getColumns()), columnName);
37641                                                }
37642                                                if (model instanceof ResultColumn) {
37643                                                        ResultColumn resultColumn = (ResultColumn) model;
37644                                                        if (resultColumn != null) {
37645                                                                relation.addSource(
37646                                                                                new ResultColumnRelationshipElement(resultColumn, columnName.getLocation(), columnName));
37647                                                        }
37648                                                }
37649                                        } else if (functionOutputColumn(modelManager.getModel(table)) != null) {
37650                                                // A table function's column is read through its output column, as the select list reads it
37651                                                // (Mantis 4797).
37652                                                relation.addSource(new ResultColumnRelationshipElement(
37653                                                                functionOutputColumn(modelManager.getModel(table)), columnName.getLocation(), columnName));
37654                                        }
37655                                }
37656                        }
37657                        } finally {
37658                                if (sqllrRefs != null) {
37659                                        sqllrRefs.closeFrame(sqllrFrame);
37660                                }
37661                        }
37662
37663                        List<TParseTreeNode> functions = visitor.getFunctions();
37664                        for (int j = 0; j < functions.size(); j++) {
37665                                TParseTreeNode functionObj = functions.get(j);
37666                                Object functionModel = modelManager.getModel(functionObj);
37667                                if (functionModel == null) {
37668                                        functionModel = createFunction(functionObj);
37669                                }
37670                                if (functionModel instanceof Function) {
37671                                        Object targetModel1 = modelManager.getModel(function.getFunctionName());
37672                                        if (targetModel1 == null && function.getFunctionType() == EFunctionType.array_agg_t && modelManager.getModel(function) instanceof Function) {
37673                                                Function functionModel1 = (Function) modelManager.getModel(function);
37674                                                if (functionModel1.getColumns().size() == 1) {
37675                                                        targetModel1 = ((Function) functionModel1).getColumns().get(0);
37676                                                }
37677                                        }
37678                                        if (!(targetModel1 instanceof ResultColumn)) {
37679                                                continue;
37680                                        }
37681                                        ResultColumn targetRC_func = (ResultColumn) targetModel1;
37682                                        AbstractRelationship relation;
37683                                        if ("COUNT".equalsIgnoreCase(builtInFunctionName(function))) { // non-identifier-compare: built-in function name
37684                                                // relation = modelFactory.createDataFlowRelation();
37685                                                relation = modelFactory.createRecordSetRelation();
37686                                        } else {
37687                                                relation = modelFactory.createRecordSetRelation();
37688                                        }
37689                                        relation.setEffectType(EffectType.function);
37690                                        relation.setFunction(function.getFunctionName().toString());
37691                                        relation.setTarget(new ResultColumnRelationshipElement(targetRC_func));
37692
37693                                        if (functionObj instanceof TFunctionCall) {
37694                                                ResultColumn resultColumn = (ResultColumn) modelManager
37695                                                                .getModel(((TFunctionCall) functionObj).getFunctionName());
37696                                                if (resultColumn != null) {
37697                                                        ResultColumnRelationshipElement element = new ResultColumnRelationshipElement(resultColumn,
37698                                                                        ((TFunctionCall) functionObj).getFunctionName().getLocation());
37699                                                        relation.addSource(element);
37700                                                }
37701                                        }
37702                                        if (functionObj instanceof TCaseExpression) {
37703                                                ResultColumn resultColumn = (ResultColumn) modelManager
37704                                                                .getModel(((TCaseExpression) functionObj).getWhenClauseItemList());
37705                                                if (resultColumn != null) {
37706                                                        ResultColumnRelationshipElement element = new ResultColumnRelationshipElement(resultColumn);
37707                                                        relation.addSource(element);
37708                                                }
37709                                        }
37710                                } else if (functionModel instanceof Table) {
37711                                        Object targetModel2 = modelManager.getModel(function.getFunctionName());
37712                                        if (targetModel2 == null && function.getFunctionType() == EFunctionType.array_agg_t && modelManager.getModel(function) instanceof Function) {
37713                                                Function functionModel1 = (Function) modelManager.getModel(function);
37714                                                if (functionModel1.getColumns().size() == 1) {
37715                                                        targetModel2 = ((Function) functionModel1).getColumns().get(0);
37716                                                }
37717                                        }
37718                                        if (!(targetModel2 instanceof ResultColumn)) {
37719                                                continue;
37720                                        }
37721                                        ResultColumn targetRC_table = (ResultColumn) targetModel2;
37722                                        TableColumn tableColumn = modelFactory.createTableColumn((Table) functionModel,
37723                                                        function.getFunctionName(), false);
37724                                        AbstractRelationship relation;
37725                                        if ("COUNT".equalsIgnoreCase(builtInFunctionName(function))) { // non-identifier-compare: built-in function name
37726                                                // relation = modelFactory.createDataFlowRelation();
37727                                                relation = modelFactory.createRecordSetRelation();
37728                                        } else {
37729                                                relation = modelFactory.createRecordSetRelation();
37730                                        }
37731                                        relation.setEffectType(EffectType.function);
37732                                        relation.setFunction(function.getFunctionName().toString());
37733                                        relation.setTarget(new ResultColumnRelationshipElement(targetRC_table));
37734                                        TableColumnRelationshipElement element = new TableColumnRelationshipElement(tableColumn);
37735                                        relation.addSource(element);
37736                                }
37737                        }
37738                }
37739
37740                if (expr == null || "COUNT".equalsIgnoreCase(builtInFunctionName(function))) { // non-identifier-compare: built-in function name
37741                        if ("COUNT".equalsIgnoreCase(builtInFunctionName(function)) // non-identifier-compare: built-in function name
37742                                        // https://e.gitee.com/gudusoft/issues/list?issue=I4L5EO
37743                                        // 对于非 count() 函数,当有group by clause时, RelationRows 不参与indirect dataflow,
37744                                        // 让位与group by clause中的column.
37745                                        || ((TSelectSqlStatement) stmt).getGroupByClause() == null) {
37746                                TTableList tables = stmt.getTables();
37747                                if (tables != null) {
37748                                        for (int i = 0; i < tables.size(); i++) {
37749                                                TTable table = tables.getTable(i);
37750                                                if (modelManager.getModel(table) == null && table.getSubquery() == null) {
37751                                                        modelFactory.createTable(table);
37752                                                }
37753                                                if (modelManager.getModel(table) instanceof Table) {
37754                                                        Object targetModel3 = modelManager.getModel(function.getFunctionName());
37755                                                        if (targetModel3 == null && function.getFunctionType() == EFunctionType.array_agg_t && modelManager.getModel(function) instanceof Function) {
37756                                                                Function functionModel = (Function) modelManager.getModel(function);
37757                                                                if (functionModel.getColumns().size() == 1) {
37758                                                                        targetModel3 = functionModel.getColumns().get(0);
37759                                                                }
37760                                                        }
37761                                                        if (!(targetModel3 instanceof ResultColumn)) {
37762                                                                continue;
37763                                                        }
37764                                                        ResultColumn targetRC_tbl = (ResultColumn) targetModel3;
37765                                                        Table tableModel = (Table) modelManager.getModel(table);
37766                                                        AbstractRelationship relation;
37767                                                        if ("COUNT".equalsIgnoreCase(builtInFunctionName(function))) { // non-identifier-compare: built-in function name
37768                                                                // relation = modelFactory.createDataFlowRelation();
37769                                                                relation = modelFactory.createRecordSetRelation();
37770                                                        } else {
37771                                                                relation = modelFactory.createRecordSetRelation();
37772                                                        }
37773                                                        relation.setEffectType(EffectType.function);
37774                                                        relation.setFunction(function.getFunctionName().toString());
37775                                                        relation.setTarget(new ResultColumnRelationshipElement(targetRC_tbl));
37776
37777                                                        if (relation instanceof DataFlowRelationship && option.isShowCountTableColumn()) {
37778                                                                List<TExpression> expressions = new ArrayList<TExpression>();
37779                                                                getFunctionExpressions(expressions, new ArrayList<TExpression>(), function);
37780                                                                for (int j = 0; j < expressions.size(); j++) {
37781                                                                        columnsInExpr visitor = new columnsInExpr();
37782                                                                        expressions.get(j).inOrderTraverse(visitor);
37783                                                                        List<TObjectName> objectNames = visitor.getObjectNames();
37784                                                                        if (objectNames != null) {
37785                                                                                for (TObjectName columnName : objectNames) {
37786                                                                                        TTable tempTable = modelManager.getTable(stmt, columnName);
37787                                                                                        if (table.equals(tempTable)) {
37788                                                                                                TableColumn tableColumn = modelFactory.createTableColumn(tableModel,
37789                                                                                                                columnName, false);
37790                                                                                                TableColumnRelationshipElement element = new TableColumnRelationshipElement(
37791                                                                                                                tableColumn);
37792                                                                                                relation.addSource(element);
37793                                                                                        }
37794                                                                                }
37795                                                                        }
37796                                                                }
37797                                                        }
37798                                                        if (relation.getSources().size() == 0) {
37799                                                                RelationRowsRelationshipElement element = new RelationRowsRelationshipElement<TableRelationRows>(
37800                                                                                tableModel.getRelationRows());
37801                                                                relation.addSource(element);
37802                                                        }
37803                                                } else if (modelManager.getModel(table) instanceof QueryTable) {
37804                                                        Object targetModel4 = modelManager.getModel(function.getFunctionName());
37805                                                        if (targetModel4 == null && function.getFunctionType() == EFunctionType.array_agg_t && modelManager.getModel(function) instanceof Function) {
37806                                                                Function functionModel = (Function) modelManager.getModel(function);
37807                                                                targetModel4 = functionModel.getColumns().get(0);
37808                                                        }
37809                                                        if (!(targetModel4 instanceof ResultColumn)) {
37810                                                                continue;
37811                                                        }
37812                                                        ResultColumn targetRC_qt = (ResultColumn) targetModel4;
37813                                                        QueryTable tableModel = (QueryTable) modelManager.getModel(table);
37814                                                        AbstractRelationship relation;
37815                                                        if ("COUNT".equalsIgnoreCase(builtInFunctionName(function))) { // non-identifier-compare: built-in function name
37816                                                                // relation = modelFactory.createDataFlowRelation();
37817                                                                relation = modelFactory.createRecordSetRelation();
37818                                                        } else {
37819                                                                relation = modelFactory.createRecordSetRelation();
37820                                                        }
37821                                                        relation.setEffectType(EffectType.function);
37822                                                        relation.setFunction(function.getFunctionName().toString());
37823                                                        relation.setTarget(new ResultColumnRelationshipElement(targetRC_qt));
37824                                                        RelationRowsRelationshipElement element = new RelationRowsRelationshipElement<ResultSetRelationRows>(
37825                                                                        tableModel.getRelationRows());
37826                                                        relation.addSource(element);
37827                                                } else if (functionOutputColumn(modelManager.getModel(table)) != null) {
37828                                                        // a table function: its rows filter the count as a table's rows do (Mantis 4815)
37829                                                        Object targetModel5 = modelManager.getModel(function.getFunctionName());
37830                                                        if (targetModel5 == null && function.getFunctionType() == EFunctionType.array_agg_t && modelManager.getModel(function) instanceof Function) {
37831                                                                Function functionModel = (Function) modelManager.getModel(function);
37832                                                                targetModel5 = functionModel.getColumns().get(0);
37833                                                        }
37834                                                        if (!(targetModel5 instanceof ResultColumn)) {
37835                                                                continue;
37836                                                        }
37837                                                        ResultSet functionModel = functionOutputColumn(modelManager.getModel(table)).getResultSet();
37838                                                        AbstractRelationship relation = modelFactory.createRecordSetRelation();
37839                                                        relation.setEffectType(EffectType.function);
37840                                                        relation.setFunction(function.getFunctionName().toString());
37841                                                        relation.setTarget(new ResultColumnRelationshipElement((ResultColumn) targetModel5));
37842                                                        relation.addSource(new RelationRowsRelationshipElement<ResultSetRelationRows>(
37843                                                                        functionModel.getRelationRows()));
37844                                                }
37845                                        }
37846                                }
37847                        }
37848
37849                        if (stmt.getWhereClause() == null || stmt.getWhereClause().getCondition() == null) {
37850                                return;
37851                        }
37852
37853                        columnsInExpr visitor = new columnsInExpr();
37854                        stmt.getWhereClause().getCondition().inOrderTraverse(visitor);
37855                        List<TObjectName> objectNames = visitor.getObjectNames();
37856                        SqllrObservations sqllrRefs = modelManager.getSqllrObservations();
37857                        int sqllrFrame = sqllrRefs == null ? -1 : sqllrRefs.openFrame();
37858                        try { // SQL-LR: each element added in this loop is recorded with the reference of its iteration
37859                        for (int j = 0; j < objectNames.size(); j++) {
37860                                TObjectName columnName = objectNames.get(j);
37861                                if (sqllrRefs != null) {
37862                                        sqllrRefs.reference(sqllrFrame, columnName);
37863                                }
37864                                if (columnName.getDbObjectType() == EDbObjectType.variable) {
37865                                        Variable tableModel;
37866                                        if (columnName.toString().indexOf(".") != -1) {
37867                                                List<String> splits = SQLUtil.parseNames(columnName.toString());
37868                                                tableModel = modelFactory.createReferencedVariable(splits.get(splits.size() - 2), true);
37869                                        } else {
37870                                                tableModel = modelFactory.createReferencedVariable(columnName);
37871                                        }
37872                                        tableModel.setCreateTable(true);
37873                                        tableModel.setSubType(SubType.record);
37874                                        TObjectName variableProperties = DlineageUtil.syntheticStarName();
37875                                        modelFactory.createTableColumn(tableModel, variableProperties, true);
37876                                }
37877
37878//                              if (columnName.getColumnNameOnly().startsWith("@")
37879//                                              && (option.getVendor() == EDbVendor.dbvmssql || option.getVendor() == EDbVendor.dbvazuresql)) {
37880//                                      continue;
37881//                              }
37882//
37883//                              if (columnName.getColumnNameOnly().startsWith(":") && (option.getVendor() == EDbVendor.dbvhana || option.getVendor() == EDbVendor.dbvteradata)) {
37884//                                      continue;
37885//                              }
37886
37887                                Object targetModel5 = modelManager.getModel(function.getFunctionName());
37888                                if (targetModel5 == null && function.getFunctionType() == EFunctionType.array_agg_t && modelManager.getModel(function) instanceof Function) {
37889                                        Function functionModel = (Function) modelManager.getModel(function);
37890                                        if (functionModel.getColumns().size() == 1) {
37891                                                targetModel5 = functionModel.getColumns().get(0);
37892                                        }
37893                                }
37894                                if (!(targetModel5 instanceof ResultColumn)) {
37895                                        continue;
37896                                }
37897                                AbstractRelationship relation = modelFactory.createRecordSetRelation();
37898                                relation.setEffectType(EffectType.function);
37899                                relation.setFunction(function.getFunctionName().toString());
37900                                relation.setTarget(new ResultColumnRelationshipElement((ResultColumn) targetModel5));
37901
37902                                TTable table = structFieldReadTable(modelManager.getTable(stmt, columnName), columnName, stmt.tables);
37903                                if (table != null) {
37904                                        if (modelManager.getModel(table) instanceof Table) {
37905                                                Table tableModel = (Table) modelManager.getModel(table);
37906                                                if (tableModel != null) {
37907                                                        TableColumn columnModel = modelFactory.createTableColumn(tableModel, columnName, false);
37908                                                        if(columnModel == null) {
37909                                                                continue;
37910                                                        }
37911                                                        relation.addSource(
37912                                                                        new TableColumnRelationshipElement(columnModel, columnName.getLocation(),
37913                                                                                        columnName));
37914                                                }
37915                                        } else if (modelManager.getModel(table) instanceof QueryTable) {
37916                                                Object model = modelManager.getModel(columnName.getSourceColumn());
37917                                                if (!(model instanceof ResultColumn)) {
37918                                                        model = structFieldReadSource(structFieldReadBaseColumn(columnName,
37919                                                                        ((QueryTable) modelManager.getModel(table)).getColumns()), columnName);
37920                                                }
37921                                                if (model instanceof ResultColumn) {
37922                                                        ResultColumn resultColumn = (ResultColumn) model;
37923                                                        if (resultColumn != null) {
37924                                                                relation.addSource(
37925                                                                                new ResultColumnRelationshipElement(resultColumn, columnName.getLocation(), columnName));
37926                                                        }
37927                                                }
37928                                        } else if (functionOutputColumn(modelManager.getModel(table)) != null) {
37929                                                // A table function's column is read through its output column, as the select list reads it
37930                                                // (Mantis 4797).
37931                                                relation.addSource(new ResultColumnRelationshipElement(
37932                                                                functionOutputColumn(modelManager.getModel(table)), columnName.getLocation(), columnName));
37933                                        }
37934                                }
37935                        }
37936                        } finally {
37937                                if (sqllrRefs != null) {
37938                                        sqllrRefs.closeFrame(sqllrFrame);
37939                                }
37940                        }
37941                }
37942        }
37943
37944        /**
37945         * The column a table function's rows are read through: the one output column of the {@link Function} model a
37946         * table function becomes, which the select list maps the function table's columns to. Null for any other model.
37947         */
37948        private static ResultColumn functionOutputColumn(Object model) {
37949                if (!(model instanceof Function)) {
37950                        return null;
37951                }
37952                List<ResultColumn> columns = ((Function) model).getColumns();
37953                return (columns == null || columns.isEmpty()) ? null : columns.get(0);
37954        }
37955
37956        /** @return the impact relation made for the condition, {@code null} when there is none */
37957        /**
37958         * The clause a column operand of a WHERE, ON or HAVING filter carries ({@code columnName.getLocation()}: where,
37959         * joinCondition, having), for the filter's function, CASE and table-function operands, which have no clause of
37960         * their own: a function name's location is {@code unknown}, and a CASE had none (Mantis 4865). Null for a condition
37961         * analyzed without a clause (IF, a CASE in a select list, MERGE WHEN), whose operands are left as they are.
37962         */
37963        private static ESqlClause filterOperandClause(JoinClauseType joinClauseType) {
37964                if (joinClauseType == null) {
37965                        return null;
37966                }
37967                switch (joinClauseType) {
37968                case where:
37969                        return ESqlClause.where;
37970                case on:
37971                        return ESqlClause.joinCondition;
37972                case having:
37973                        return ESqlClause.having;
37974                default:
37975                        return null;
37976                }
37977        }
37978
37979        private ImpactRelationship analyzeFilterCondition(Object modelObject, TExpression expr, EJoinType joinType,
37980                        JoinClauseType joinClauseType, EffectType effectType) {
37981                // SQL-LR: a nested analysis never inherits the reference of the loop that called it
37982                SqllrObservations sqllr = modelManager.getSqllrObservations();
37983                int sqllrBarrier = sqllr == null ? -1 : sqllr.openFrame();
37984                try {
37985                        return analyzeFilterConditionInSqllrBarrier(modelObject, expr, joinType, joinClauseType, effectType);
37986                } finally {
37987                        if (sqllr != null) {
37988                                sqllr.closeFrame(sqllrBarrier);
37989                        }
37990                }
37991        }
37992
37993        private ImpactRelationship analyzeFilterConditionInSqllrBarrier(Object modelObject, TExpression expr, EJoinType joinType,
37994                        JoinClauseType joinClauseType, EffectType effectType) {
37995                if (expr == null) {
37996                        return null;
37997                }
37998
37999                TCustomSqlStatement stmt = stmtStack.peek();
38000
38001                columnsInExpr visitor = new columnsInExpr();
38002                expr.inOrderTraverse(visitor);
38003
38004                List<TObjectName> objectNames = visitor.getObjectNames();
38005                List<TParseTreeNode> functions = visitor.getFunctions();
38006                List<TResultColumn> resultColumns = visitor.getResultColumns();
38007                List<TParseTreeNode> constants = visitor.getConstants();
38008
38009                ImpactRelationship relation = modelFactory.createImpactRelation();
38010                relation.setEffectType(effectType);
38011                relation.setJoinClauseType(joinClauseType);
38012                if (modelObject instanceof ResultColumn) {
38013                        relation.setTarget(new ResultColumnRelationshipElement((ResultColumn) modelObject));
38014                } else if (modelObject instanceof Table) {
38015                        Table targetTable = (Table) modelObject;
38016                        relation.setTarget(new RelationRowsRelationshipElement<TableRelationRows>(
38017                                        targetTable.getRelationRows()));
38018                } else if (modelObject instanceof ResultSet) {
38019                        ResultSet targetResultSet = (ResultSet) modelObject;
38020                        relation.setTarget(new RelationRowsRelationshipElement<ResultSetRelationRows>(
38021                                        targetResultSet.getRelationRows()));
38022                } else {
38023                        ResultSet resultSet = (ResultSet) modelManager.getModel(stmt.getResultColumnList());
38024                        if (resultSet == null && stmt instanceof TUpdateSqlStatement) {
38025                                resultSet = (ResultSet) modelManager.getModel(stmt);
38026                        }
38027                        if (resultSet == null && stmt instanceof TMergeSqlStatement) {
38028                                TSelectSqlStatement subquery = ((TMergeSqlStatement) stmt).getUsingTable().getSubquery();
38029                                if (subquery != null) {
38030                                        resultSet = (ResultSet) modelManager.getModel(((TMergeSqlStatement) stmt).getUsingTable());
38031                                }
38032                                else {
38033                                        resultSet = modelFactory.createQueryTable(((TMergeSqlStatement) stmt).getUsingTable());
38034                                }
38035                        }
38036                        if (resultSet != null) {
38037                                relation.setTarget(
38038                                                new RelationRowsRelationshipElement<ResultSetRelationRows>(resultSet.getRelationRows()));
38039                        }
38040                        if (stmt instanceof TDeleteSqlStatement) {
38041                                Object deleteTarget = modelManager.getModel(((TDeleteSqlStatement) stmt).getTargetTable());
38042                                if (deleteTarget instanceof Table) {
38043                                        Table table = (Table) deleteTarget;
38044                                        relation.setTarget(new RelationRowsRelationshipElement<TableRelationRows>(table.getRelationRows()));
38045                                } else if (deleteTarget instanceof ResultSet) {
38046                                        ResultSet resultSetTarget = (ResultSet) deleteTarget;
38047                                        relation.setTarget(new RelationRowsRelationshipElement<ResultSetRelationRows>(
38048                                                        resultSetTarget.getRelationRows()));
38049                                }
38050                        }
38051                }
38052                if (relation.getTarget() != null) {
38053
38054                        if (constants != null && constants.size() > 0) {
38055                                if (option.isShowConstantTable()) {
38056                                        Table constantTable = modelFactory.createConstantsTable(stmtStack.peek());
38057                                        for (int i = 0; i < constants.size(); i++) {
38058                                                TParseTreeNode constant = constants.get(i);
38059                                                if (constant instanceof TConstant) {
38060                                                        TableColumn constantColumn = modelFactory.createTableColumn(constantTable,
38061                                                                        (TConstant) constant);
38062                                                        relation.addSource(new ConstantRelationshipElement(constantColumn));
38063                                                } else if (constant instanceof TObjectName) {
38064                                                        TableColumn constantColumn = modelFactory.createTableColumn(constantTable,
38065                                                                        (TObjectName) constant, false);
38066                                                        if(constantColumn == null) {
38067                                                                continue;
38068                                                        }
38069                                                        relation.addSource(new ConstantRelationshipElement(constantColumn));
38070                                                }
38071                                        }
38072                                }
38073                        }
38074
38075                        SqllrObservations sqllrRefs = modelManager.getSqllrObservations();
38076                        int sqllrFrame = sqllrRefs == null ? -1 : sqllrRefs.openFrame(modelObject);
38077                        try { // SQL-LR: each element added in this loop is recorded with the reference of its iteration
38078                        for (int j = 0; j < objectNames.size(); j++) {
38079                                TObjectName columnName = objectNames.get(j);
38080                                if (sqllrRefs != null) {
38081                                        sqllrRefs.reference(sqllrFrame, columnName);
38082                                }
38083                                ResultColumn boundAlias = getBoundAliasColumn(columnName, modelObject);
38084                                if (boundAlias != null) {
38085                                        relation.addSource(new ResultColumnRelationshipElement(boundAlias));
38086                                        continue;
38087                                }
38088                                if (columnName.getDbObjectType() == EDbObjectType.variable) {
38089                                        Variable variable = modelFactory.createReferencedVariable(columnName);
38090                                        variable.setSubType(SubType.record);
38091                                        if (variable.getColumns().isEmpty()) {
38092                                                TObjectName variableProperties = DlineageUtil.syntheticStarName();
38093                                                modelFactory.createTableColumn(variable, variableProperties, true);
38094                                        }
38095                                        relation.addSource(new TableColumnRelationshipElement(variable.getColumns().get(0),
38096                                                        columnName.getLocation(), columnName));
38097                                        continue;
38098                                }
38099
38100                                if (columnName.getColumnNameOnly().startsWith("@")
38101                                                && (option.getVendor() == EDbVendor.dbvmssql || option.getVendor() == EDbVendor.dbvazuresql)) {
38102                                        continue;
38103                                }
38104
38105                                if (columnName.getColumnNameOnly().startsWith(":")
38106                                                && (option.getVendor() == EDbVendor.dbvhana || option.getVendor() == EDbVendor.dbvteradata)) {
38107                                        continue;
38108                                }
38109
38110                                TTable table = modelManager.getTable(stmt, columnName);
38111
38112                                if (table == null) {
38113                                        table = columnName.getSourceTable();
38114                                }
38115
38116                                if (table == null && stmt.tables != null) {
38117                                        for (int k = 0; k < stmt.tables.size(); k++) {
38118                                                if (table != null)
38119                                                        break;
38120
38121                                                TTable tTable = stmt.tables.getTable(k);
38122                                                if (tTable.getTableType().name().startsWith("open")) {
38123                                                        continue;
38124                                                } else if (getTableLinkedColumns(tTable) != null && getTableLinkedColumns(tTable).size() > 0) {
38125                                                        for (int z = 0; z < getTableLinkedColumns(tTable).size(); z++) {
38126                                                                TObjectName refer = getTableLinkedColumns(tTable).getObjectName(z);
38127                                                                if (DlineageUtil.isStarName((refer)))
38128                                                                        continue;
38129                                                                if (DlineageUtil.sameColumnName(refer, columnName)) {
38130                                                                        table = tTable;
38131                                                                        break;
38132                                                                }
38133                                                        }
38134                                                } else if (columnName.getTableToken() != null
38135                                                                && (SQLUtil.sameName(option.getVendor(), ESQLDataObjectType.dotTable, columnName.getTableToken().getAstext(), tTable.getName())
38136                                                                                || SQLUtil.sameName(option.getVendor(), ESQLDataObjectType.dotTable, columnName.getTableToken().getAstext(), tTable.getAliasName()))) {
38137                                                        table = tTable;
38138                                                        break;
38139                                                }
38140                                        }
38141
38142                                        if (table == null) {
38143                                                for (int k = 0; k < stmt.tables.size(); k++) {
38144                                                        if (table != null)
38145                                                                break;
38146
38147                                                        TTable tTable = stmt.tables.getTable(k);
38148                                                        Object model = ModelBindingManager.get().getModel(tTable);
38149                                                        if (model instanceof Table) {
38150                                                                Table tableModel = (Table) model;
38151                                                                for (int z = 0; tableModel.getColumns() != null
38152                                                                                && z < tableModel.getColumns().size(); z++) {
38153                                                                        TableColumn refer = tableModel.getColumns().get(z);
38154                                                                        if (DlineageUtil.sameColumnName(refer.getName(), columnName)) {
38155                                                                                table = tTable;
38156                                                                                break;
38157                                                                        }
38158                                                                        if (refer.hasStarLinkColumn()) {
38159                                                                                for (TObjectName linkColumn : refer.getStarLinkColumnList()) {
38160                                                                                        if (DlineageUtil.sameColumnName(linkColumn, columnName)) {
38161                                                                                                table = tTable;
38162                                                                                                break;
38163                                                                                        }
38164                                                                                }
38165                                                                        }
38166                                                                }
38167                                                        } else if (model instanceof QueryTable) {
38168                                                                QueryTable tableModel = (QueryTable) model;
38169                                                                for (int z = 0; tableModel.getColumns() != null
38170                                                                                && z < tableModel.getColumns().size(); z++) {
38171                                                                        ResultColumn refer = tableModel.getColumns().get(z);
38172                                                                        if (DlineageUtil.columnMatches(refer, columnName)) {
38173                                                                                table = tTable;
38174                                                                                break;
38175                                                                        }
38176                                                                        if (refer.hasStarLinkColumn()) {
38177                                                                                for (TObjectName linkColumn : refer.getStarLinkColumnList()) {
38178                                                                                        if (DlineageUtil.sameColumnName(linkColumn, columnName)) {
38179                                                                                                table = tTable;
38180                                                                                                break;
38181                                                                                        }
38182                                                                                }
38183                                                                        }
38184                                                                }
38185                                                        }
38186                                                }
38187                                        }
38188                                }
38189
38190                                // STRUCT tree, as in a select list: a field read TSQLResolver2 could not place belongs to the one relation
38191                                // that declares its base column, and a field read that several relations could own has no proven owner
38192                                // (plan rule R3)
38193                                table = structFieldReadTable(table, columnName, stmt.tables);
38194
38195                                if (table == null && stmt.tables != null && stmt.tables.size() != 0
38196                                                && !(isBuiltInFunctionName(columnName) && isFromFunction(columnName))) {
38197
38198                                        if (modelManager.getModel(stmt) instanceof ResultSet) {
38199                                                ResultSet resultSetModel = (ResultSet) modelManager.getModel(stmt);
38200                                                boolean find = false;
38201                                                for (ResultColumn resultColumn : resultSetModel.getColumns()) {
38202                                                        if(resultColumn.equals(modelObject)) {
38203                                                                continue;
38204                                                        }
38205                                                        if (!TSQLEnv.isAliasReferenceForbidden.get(option.getVendor())) {
38206                                                                if (isAliasReference(columnName, resultColumn)) {
38207                                                                        if (resultColumn.getColumnObject() != null) {
38208                                                                                int startToken = resultColumn.getColumnObject().getStartToken().posinlist;
38209                                                                                int endToken = resultColumn.getColumnObject().getEndToken().posinlist;
38210                                                                                if (columnName.getStartToken().posinlist >= startToken
38211                                                                                                && columnName.getEndToken().posinlist <= endToken) {
38212                                                                                        continue;
38213                                                                                }
38214                                                                        }
38215                                                                        relation.addSource(new ResultColumnRelationshipElement(resultColumn));
38216                                                                        find = true;
38217                                                                        break;
38218                                                                }
38219                                                        }
38220                                                }
38221                                                if (find) {
38222                                                        continue;
38223                                                }
38224                                        }
38225
38226                                        TObjectName pseudoTableName = new TObjectName();
38227                                        // Use qualified prefix from column name if available (e.g., sch.pk_constv2 from sch.pk_constv2.c_cdsl)
38228                                        // Otherwise fall back to default pseudo table name
38229                                        String qualifiedPrefix = getQualifiedPrefixFromColumn(columnName);
38230                                        pseudoTableName.setString(qualifiedPrefix != null ? qualifiedPrefix : "pseudo_table_include_orphan_column");
38231                                        Table pseudoTable = modelFactory.createTableByName(pseudoTableName);
38232                                        pseudoTable.setPseudo(true);
38233                                        TableColumn pseudoTableColumn = modelFactory.createTableColumn(pseudoTable, columnName, true);
38234
38235                                        // If not linking to first table and column has qualified prefix (3-part name like sch.pkg.col),
38236                                        // add the pseudo table column as source
38237                                        if (!isLinkOrphanColumnToFirstTable() && pseudoTableColumn != null && qualifiedPrefix != null) {
38238                                                SqllrObservations decided = SqllrObservations.current();
38239                                                if (decided != null) {
38240                                                        decided.decision(SqllrObservations.DECISION_ORPHAN_PSEUDO_TABLE, columnName, pseudoTable,
38241                                                                        "an orphan column with a qualified prefix");
38242                                                }
38243                                                relation.addSource(new TableColumnRelationshipElement(pseudoTableColumn));
38244                                        }
38245
38246                                        if (isLinkOrphanColumnToFirstTable()) {
38247                                                TTable orphanTable = stmt.tables.getTable(0);
38248                                                SqllrObservations decided = SqllrObservations.current();
38249                                                if (decided != null) {
38250                                                        decided.decision(SqllrObservations.DECISION_ORPHAN_FIRST_TABLE, columnName, orphanTable,
38251                                                                        "linkOrphanColumnToFirstTable");
38252                                                }
38253                                                table = stmt.tables.getTable(0);
38254                                                Object tableModel = modelManager.getModel(table);
38255                                                if (tableModel == null) {
38256                                                        tableModel = modelFactory.createTable(orphanTable);
38257                                                }
38258                                                if (tableModel instanceof Table) {
38259                                                        modelFactory.createTableColumn((Table) tableModel, columnName, false);
38260                                                        ErrorInfo errorInfo = new ErrorInfo();
38261                                                        errorInfo.setErrorType(ErrorInfo.LINK_ORPHAN_COLUMN);
38262                                                        errorInfo.setErrorMessage("Link orphan column [" + columnName.toString()
38263                                                                        + "] to the first table [" + orphanTable.getFullNameWithAliasString() + "]");
38264                                                        errorInfo.setStartPosition(new Pair3<Long, Long, String>(columnName.getStartToken().lineNo,
38265                                                                        columnName.getStartToken().columnNo, ModelBindingManager.getGlobalHash()));
38266                                                        errorInfo.setEndPosition(new Pair3<Long, Long, String>(columnName.getEndToken().lineNo,
38267                                                                        columnName.getEndToken().columnNo + columnName.getEndToken().getAstext().length(),
38268                                                                        ModelBindingManager.getGlobalHash()));
38269                                                        errorInfo.fillInfo(this);
38270                                                        errorInfos.add(errorInfo);
38271                                                }
38272                                        }
38273                                }
38274
38275                                if (table != null) {
38276                                        if (modelManager.getModel(table) instanceof Table) {
38277                                                Table tableModel = (Table) modelManager.getModel(table);
38278                                                if (tableModel != null) {
38279                                                        TableColumn columnModel = modelFactory.createTableColumn(tableModel, columnName, false);
38280                                                        if(columnModel!=null) {
38281                                                                // Carry the operand's own reference site: for created
38282                                                                // (e.g. #temp) tables the column model is shared with
38283                                                                // the definition site, whose coordinate is not where
38284                                                                // this ON/WHERE operand sits.
38285                                                                TableColumnRelationshipElement element = new TableColumnRelationshipElement(columnModel,
38286                                                                                columnName.getLocation(), columnName);
38287                                                                relation.addSource(element);
38288                                                        }
38289                                                }
38290                                        } else if (modelManager.getModel(table) instanceof QueryTable) {
38291                                                QueryTable tableModel = (QueryTable)modelManager.getModel(table);
38292                                                if (table.getSubquery() != null && table.getSubquery().isCombinedQuery()) {
38293                                                        TSelectSqlStatement subquery = table.getSubquery();
38294                                                        List<ResultSet> resultSets = new ArrayList<ResultSet>();
38295                                                        if (!subquery.getLeftStmt().isCombinedQuery()) {
38296                                                                ResultSet sourceResultSet = (ResultSet) modelManager
38297                                                                                .getModel(subquery.getLeftStmt().getResultColumnList());
38298                                                                resultSets.add(sourceResultSet);
38299                                                        } else {
38300                                                                ResultSet sourceResultSet = (ResultSet) modelManager.getModel(subquery.getLeftStmt());
38301                                                                resultSets.add(sourceResultSet);
38302                                                        }
38303
38304                                                        if (!subquery.getRightStmt().isCombinedQuery()) {
38305                                                                ResultSet sourceResultSet = (ResultSet) modelManager
38306                                                                                .getModel(subquery.getRightStmt().getResultColumnList());
38307                                                                resultSets.add(sourceResultSet);
38308                                                        } else {
38309                                                                ResultSet sourceResultSet = (ResultSet) modelManager.getModel(subquery.getRightStmt());
38310                                                                resultSets.add(sourceResultSet);
38311                                                        }
38312
38313                                                        int sourcesBefore = relation.getSources().size();
38314                                                        for (ResultSet sourceResultSet : resultSets) {
38315                                                                if (sourceResultSet != null && columnName.getSourceColumn() != null) {
38316                                                                        for (int k = 0; k < sourceResultSet.getColumns().size(); k++) {
38317                                                                                if (DlineageUtil.sameColumnName(sourceResultSet.getColumns().get(k).getName(), columnName.getSourceColumn().getColumnNameOnly())) {
38318                                                                                        ResultColumn linkedColumn = sourceResultSet.getColumns().get(k);
38319                                                                                        int link = linkedColumn.starLinkIndexOf(columnName);
38320                                                                                        if (link >= 0) {
38321                                                                                                ResultColumn column = modelFactory.createResultColumn(sourceResultSet,
38322                                                                                                                linkedColumn.getStarLinkColumnName(link), true);
38323                                                                                                ResultColumnRelationshipElement setopElement = new ResultColumnRelationshipElement(column);
38324                                                                                                setopElement.setReferencePosition(columnName);
38325                                                                                                relation.addSource(setopElement);
38326                                                                                        } else {
38327                                                                                                ResultColumnRelationshipElement setopElement = new ResultColumnRelationshipElement(
38328                                                                                                                sourceResultSet.getColumns().get(k));
38329                                                                                                setopElement.setReferencePosition(columnName);
38330                                                                                                relation.addSource(setopElement);
38331                                                                                        }
38332                                                                                }
38333                                                                        }
38334                                                                }
38335                                                        }
38336                                                        // The resolver bound the column to the set-operation derived table without a parse-time source
38337                                                        // column (UNION ALL ... AS r ON r.type = c.cos_type): the derived table's own column is the
38338                                                        // row influence (Mantis 4819), as a VALUES derived table's is.
38339                                                        if (relation.getSources().size() == sourcesBefore && tableModel.getColumns() != null) {
38340                                                                for (ResultColumn ownColumn : tableModel.getColumns()) {
38341                                                                        if (DlineageUtil.columnMatches(ownColumn, columnName)) {
38342                                                                                relation.addSource(new ResultColumnRelationshipElement(ownColumn,
38343                                                                                                columnName.getLocation(), columnName));
38344                                                                                break;
38345                                                                        }
38346                                                                }
38347                                                        }
38348                                                } else {
38349                                                        Object model = modelManager.getModel(columnName.getSourceColumn());
38350                                                        if (model instanceof ResultColumn) {
38351                                                                ResultColumn resultColumn = (ResultColumn) model;
38352                                                                if (resultColumn != null) {
38353                                                                        if (resultColumn.hasStarLinkColumn()) {
38354                                                                                int link = resultColumn.starLinkIndexOf(columnName);
38355                                                                                if (link >= 0) {
38356                                                                                        ResultColumn column = modelFactory.createResultColumn(
38357                                                                                                        resultColumn.getResultSet(), resultColumn.getStarLinkColumnName(link), true);
38358                                                                                        ResultColumnRelationshipElement starElement = new ResultColumnRelationshipElement(column);
38359                                                                                        starElement.setReferencePosition(columnName);
38360                                                                                        relation.addSource(starElement);
38361                                                                                } else {
38362                                                                                        resultColumn.bindStarLinkColumn(columnName);
38363                                                                                        ResultColumn column = modelFactory
38364                                                                                                        .createResultColumn(resultColumn.getResultSet(), columnName, true);
38365                                                                                        ResultColumnRelationshipElement starElement = new ResultColumnRelationshipElement(column);
38366                                                                                        starElement.setReferencePosition(columnName);
38367                                                                                        relation.addSource(starElement);
38368                                                                                }
38369                                                                        } else {
38370                                                                                // Carry the operand's own reference site: the
38371                                                                                // derived table's / CTE's result column model
38372                                                                                // carries its select-list definition span, not
38373                                                                                // where this ON/WHERE operand sits.
38374                                                                                ResultColumnRelationshipElement element = new ResultColumnRelationshipElement(
38375                                                                                                resultColumn, columnName.getLocation(), columnName);
38376                                                                                relation.addSource(element);
38377                                                                        }
38378                                                                }
38379                                                        }
38380                                                        else{
38381                                                                boolean find = false;
38382                                                                // STRUCT tree: customer.address.city reads the column customer of the CTE or subquery;
38383                                                                // matched by its last segment it found none and invented a column
38384                                                                ResultColumn fieldReadBase = structFieldReadSource(
38385                                                                                structFieldReadBaseColumn(columnName, tableModel.getColumns()), columnName);
38386                                                                if (fieldReadBase != null) {
38387                                                                        relation.addSource(new ResultColumnRelationshipElement(fieldReadBase,
38388                                                                                        columnName.getLocation(), columnName));
38389                                                                        find = true;
38390                                                                }
38391                                                                for (int i = 0; !find && i < tableModel.getColumns().size(); i++) {
38392                                                                        ResultColumn resultColumn = tableModel.getColumns().get(i);
38393                                                                        if (DlineageUtil.columnMatches(resultColumn, columnName)) {
38394                                                                                // An explicit CTE column list resolves here;
38395                                                                                // the model span is the column-list entry, not
38396                                                                                // the operand.
38397                                                                                ResultColumnRelationshipElement element = new ResultColumnRelationshipElement(
38398                                                                                                resultColumn, columnName.getLocation(), columnName);
38399                                                                                relation.addSource(element);
38400                                                                                find = true;
38401                                                                                break;
38402                                                                        }
38403                                                                        else if (resultColumn.isStarColumn()) {
38404                                                                                resultColumn.bindStarLinkColumn(columnName);
38405                                                                        }
38406                                                                }
38407                                                                if(!find){
38408                                                                        ResultColumn resultColumn = new ResultColumn(tableModel, columnName);
38409                                                                        ResultColumnRelationshipElement element = new ResultColumnRelationshipElement(
38410                                                                                                resultColumn, columnName.getLocation(), columnName);
38411                                                                                relation.addSource(element);
38412                                                                }
38413                                                        }
38414                                                }
38415                                        } else if (functionOutputColumn(modelManager.getModel(table)) != null) {
38416                                                // A table function's column is read through its output column, as the select list reads it
38417                                                // (Mantis 4797).
38418                                                relation.addSource(new ResultColumnRelationshipElement(
38419                                                                functionOutputColumn(modelManager.getModel(table)), columnName.getLocation(), columnName));
38420                                        }
38421                                }
38422                        }
38423                        } finally {
38424                                if (sqllrRefs != null) {
38425                                        sqllrRefs.closeFrame(sqllrFrame);
38426                                }
38427                        }
38428
38429                        ESqlClause operandClause = filterOperandClause(joinClauseType);
38430                        for (int j = 0; j < functions.size(); j++) {
38431                                TParseTreeNode functionObj = functions.get(j);
38432                                Object functionModel = modelManager.getModel(functionObj);
38433                                if (functionModel == null) {
38434                                        functionModel = createFunction(functionObj);
38435                                }
38436                                if (functionModel instanceof Function) {
38437                                        if (functionObj instanceof TFunctionCall) {
38438                                                ResultColumn resultColumn = (ResultColumn) modelManager
38439                                                                .getModel(((TFunctionCall) functionObj).getFunctionName());
38440                                                if (resultColumn != null) {
38441                                                        ResultColumnRelationshipElement element = new ResultColumnRelationshipElement(resultColumn,
38442                                                                        operandClause != null ? operandClause
38443                                                                                        : ((TFunctionCall) functionObj).getFunctionName().getLocation());
38444                                                        relation.addSource(element);
38445                                                }
38446                                        }
38447                                        if (functionObj instanceof TCaseExpression) {
38448                                                ResultColumn resultColumn = (ResultColumn) modelManager
38449                                                                .getModel(((TCaseExpression) functionObj).getWhenClauseItemList());
38450                                                if (resultColumn != null) {
38451                                                        ResultColumnRelationshipElement element = new ResultColumnRelationshipElement(resultColumn,
38452                                                                        operandClause);
38453                                                        relation.addSource(element);
38454                                                }
38455                                        }
38456                                } else if (functionModel instanceof Table && ((TFunctionCall) functionObj).getFunctionName()!=null) {
38457                                        TableColumn tableColumn = modelFactory.createTableColumn((Table) functionModel,
38458                                                        ((TFunctionCall) functionObj).getFunctionName(), false);
38459                                        if (tableColumn != null) {
38460                                                TableColumnRelationshipElement element = new TableColumnRelationshipElement(tableColumn,
38461                                                                operandClause);
38462                                                relation.addSource(element);
38463                                        }
38464                                }
38465                        }
38466
38467                        for (int j = 0; j < resultColumns.size(); j++) {
38468                                TResultColumn resultColumn = resultColumns.get(j);
38469                                if (modelManager.getModel(resultColumn) instanceof ResultColumn) {
38470                                        ResultColumn resultColumnModel = (ResultColumn) modelManager.getModel(resultColumn);
38471                                        relation.addSource(new ResultColumnRelationshipElement(resultColumnModel, ESqlClause.selectList));
38472                                }
38473                        }
38474                }
38475
38476                // a HAVING predicate filters groups: two columns compared there are not a join
38477                if (isShowJoin() && joinClauseType != null && joinClauseType != JoinClauseType.having) {
38478                        joinInExpr joinVisitor = new joinInExpr(joinType, joinClauseType, effectType);
38479                        expr.inOrderTraverse(joinVisitor);
38480                }
38481                // an aggregate in HAVING reads its group as one in the select list does: the GROUP BY keys and the rows
38482                if (joinClauseType == JoinClauseType.having) {
38483                        analyzeRecordSetRelation(functions, effectType, true);
38484                }
38485                return relation;
38486        }
38487
38488        public void dispose() {
38489        if (commentBindingCollector != null) { commentBindingCollector.clear(); commentBindingCollector = null; }
38490                usingColumnsByStatement.clear();
38491                scopeBuildResult = null;
38492                authoritativeEvidenceCollector.reset();
38493                cteSelectRelationKeys.clear();
38494                accessedSubqueries.clear();
38495                accessedStatements.clear();
38496                stmtStack.clear();
38497                viewDDLMap.clear();
38498                procedureDDLAliases.clear();
38499                deferredExecuteBindings.clear();
38500                structObjectMap.clear();
38501                appendResultSets.clear();
38502                appendStarColumns.clear();
38503                appendTableStarColumns.clear();
38504                columnNameIndexCache.clear();
38505                resultSetColumnLookupCache.clear();
38506                tableColumnLookupCache.clear();
38507                modelManager.DISPLAY_ID.clear();
38508                modelManager.DISPLAY_NAME.clear();
38509                tableIds.clear();
38510                ModelBindingManager.remove();
38511        }
38512
38513        class joinTreatColumnsInExpr implements IExpressionVisitor {
38514
38515                private List<TObjectName> objectNames = new ArrayList<TObjectName>();
38516                
38517                private TTable table;
38518
38519                public joinTreatColumnsInExpr(TTable table) {
38520                        this.table = table;
38521                }
38522
38523                public List<TObjectName> getObjectNames() {
38524                        return objectNames;
38525                }
38526
38527                boolean is_compare_condition(EExpressionType t) {
38528                        return t == EExpressionType.simple_comparison_t;
38529                }
38530
38531                @Override
38532                public boolean exprVisit(TParseTreeNode pNode, boolean isLeafNode) {
38533                        TExpression expr = (TExpression) pNode;
38534                        if (is_compare_condition(expr.getExpressionType())) {
38535                                TExpression leftExpr = expr.getLeftOperand();
38536                                columnsInExpr leftVisitor = new columnsInExpr();
38537                                leftExpr.inOrderTraverse(leftVisitor);
38538                                List<TObjectName> leftObjectNames = leftVisitor.getObjectNames();
38539
38540                                TExpression rightExpr = expr.getRightOperand();
38541                                columnsInExpr rightVisitor = new columnsInExpr();
38542                                rightExpr.inOrderTraverse(rightVisitor);
38543                                List<TObjectName> rightObjectNames = rightVisitor.getObjectNames();
38544
38545                                if (!leftObjectNames.isEmpty() && !rightObjectNames.isEmpty()) {
38546                                        for (TObjectName column : leftObjectNames) {
38547                                                if (column.getSourceTable() != null && column.getSourceTable().equals(table)) {
38548                                                        objectNames.add(column);
38549                                                        return false;
38550                                                }
38551                                        }
38552                                        for (TObjectName column : rightObjectNames) {
38553                                                if (column.getSourceTable() != null && column.getSourceTable().equals(table)) {
38554                                                        objectNames.add(column);
38555                                                        return false;
38556                                                }
38557                                        }
38558                                }
38559                                return false;
38560                        }
38561                        return true;
38562                }
38563        }
38564        
38565        class columnsInExpr implements IExpressionVisitor {
38566
38567                private List<TParseTreeNode> constants = new ArrayList<TParseTreeNode>();
38568                private List<TObjectName> objectNames = new ArrayList<TObjectName>();
38569                private List<TParseTreeNode> functions = new ArrayList<TParseTreeNode>();
38570                private List<TResultColumn> resultColumns = new ArrayList<TResultColumn>();
38571                private List<TSelectSqlStatement> subquerys = new ArrayList<TSelectSqlStatement>();
38572                private boolean skipFunction = false;
38573                // ClickHouse expression-CTE aliases already substituted during this
38574                // traversal — guards against cyclic alias definitions.
38575                private Set<TCTE> expandedExpressionCtes = new HashSet<TCTE>();
38576                // Alias-chain expansion is depth-bounded hybrid: direct recursion
38577                // preserves depth-first substitution order (source order of the
38578                // collected columns) up to CTE_EXPANSION_MAX_DEPTH; deeper chains
38579                // queue their definitions on an explicit deque drained by the
38580                // outermost frame — a 2000-alias chain must not overflow the -Xss2m
38581                // stack (same class as the repo's UNION/expression traversal rule).
38582                private static final int CTE_EXPANSION_MAX_DEPTH = 64;
38583                private Deque<TExpression> pendingCteExpressions = new ArrayDeque<TExpression>();
38584                private int cteExpansionDepth = 0;
38585                // subscript expressions traversed, by identity: a name, its call and its expression can share them (Mantis 4817)
38586                private Set<TExpression> subscriptsTraversed = Collections.newSetFromMap(new IdentityHashMap<TExpression, Boolean>());
38587
38588                private void traverseSubscripts(List<TExpression> subscripts) {
38589                        for (TExpression subscript : subscripts) {
38590                                if (subscript != null && subscriptsTraversed.add(subscript)) {
38591                                        subscript.inOrderTraverse(this);
38592                                }
38593                        }
38594                }
38595
38596                /**
38597                 * Collects an expression operand as a column reference — unless it is a
38598                 * ClickHouse expression-CTE alias (WITH <expr> AS ident), in which case
38599                 * the CTE's definition is traversed instead so its real column
38600                 * dependencies flow to the consumer. Every branch of exprVisit that
38601                 * adds an operand's TObjectName directly must go through this method,
38602                 * or alias references in that expression shape leak out as columns.
38603                 */
38604                private void addColumnOrExpandCteAlias(TObjectName object) {
38605                        if (object.getExpressionCteRef() == null
38606                                        || object.getSourceTable() != null
38607                                        || object.getExpressionCteRef().getExpression() == null) {
38608                                objectNames.add(object);
38609                                return;
38610                        }
38611                        if (!expandedExpressionCtes.add(object.getExpressionCteRef())) {
38612                                return; // cyclic or already substituted
38613                        }
38614                        TExpression definition = object.getExpressionCteRef().getExpression();
38615                        if (cteExpansionDepth >= CTE_EXPANSION_MAX_DEPTH) {
38616                                pendingCteExpressions.add(definition);
38617                                return; // drained below when the outermost frame unwinds
38618                        }
38619                        cteExpansionDepth++;
38620                        try {
38621                                definition.inOrderTraverse(this);
38622                        } finally {
38623                                cteExpansionDepth--;
38624                        }
38625                        if (cteExpansionDepth == 0) {
38626                                while (!pendingCteExpressions.isEmpty()) {
38627                                        TExpression pending = pendingCteExpressions.poll();
38628                                        cteExpansionDepth++;
38629                                        try {
38630                                                pending.inOrderTraverse(this);
38631                                        } finally {
38632                                                cteExpansionDepth--;
38633                                        }
38634                                }
38635                        }
38636                }
38637
38638                public void setSkipFunction(boolean skipFunction) {
38639                        this.skipFunction = skipFunction;
38640                }
38641
38642                public List<TParseTreeNode> getFunctions() {
38643                        return functions;
38644                }
38645
38646                public List<TSelectSqlStatement> getSubquerys() {
38647                        return subquerys;
38648                }
38649
38650                public List<TParseTreeNode> getConstants() {
38651                        return constants;
38652                }
38653
38654                public List<TObjectName> getObjectNames() {
38655                        return objectNames;
38656                }
38657
38658                public List<TResultColumn> getResultColumns() {
38659                        return resultColumns;
38660                }
38661
38662                @Override
38663                public boolean exprVisit(TParseTreeNode pNode, boolean isLeafNode) {
38664                        TExpression lcexpr = (TExpression) pNode;
38665                        // Handle named argument expressions (e.g., "INPUT => value" in Snowflake FLATTEN)
38666                        // The left operand is the parameter name, NOT a column reference.
38667                        // Only traverse the right operand (the value).
38668                        if (lcexpr.getExpressionType() == EExpressionType.assignment_t) {
38669                                // Skip left operand (parameter name) - only traverse right operand (value)
38670                                if (lcexpr.getRightOperand() != null) {
38671                                        lcexpr.getRightOperand().inOrderTraverse(this);
38672                                }
38673                                return false; // Don't continue default traversal
38674                        }
38675                        if (lcexpr.getExpressionType() == EExpressionType.simple_constant_t) {
38676                                if (lcexpr.getConstantOperand() != null) {
38677                                        TExpression intervalValue = lcexpr.getConstantOperand().getInt64_expression();
38678                                        if(intervalValue!=null
38679                                                        && intervalValue.getExpressionType() == EExpressionType.function_t) {
38680                                                intervalValue.inOrderTraverse(this);
38681                                        }
38682                                        else {
38683                                                constants.add(lcexpr.getConstantOperand());
38684                                                if (intervalValue != null && intervalValue.getExpressionType() != EExpressionType.simple_constant_t) {
38685                                                        // INTERVAL t.n DAY (BigQuery, MySQL): the value of the interval reads columns (Mantis 4918);
38686                                                        // its literals stay the one interval constant added above
38687                                                        int constantCount = constants.size();
38688                                                        intervalValue.inOrderTraverse(this);
38689                                                        constants.subList(constantCount, constants.size()).clear();
38690                                                }
38691                                        }
38692                                }
38693                        } else if (lcexpr.getExpressionType() == EExpressionType.interval_t) {
38694                                // (a - b) SECOND(4) (Teradata): the qualified value is a leaf of the traversal, so the columns it reads
38695                                // are visited here (Mantis 4918); its literals are dropped, so INTERVAL 1 DAY or INTERVAL (-1) DAY
38696                                // still collects nothing, as before
38697                                TExpression intervalValue = lcexpr.getIntervalExpr() == null ? null : lcexpr.getIntervalExpr().getExpr();
38698                                if (intervalValue != null && intervalValue.getExpressionType() != EExpressionType.simple_constant_t) {
38699                                        int constantCount = constants.size();
38700                                        intervalValue.inOrderTraverse(this);
38701                                        constants.subList(constantCount, constants.size()).clear();
38702                                }
38703                        } else if (lcexpr.getExpressionType() == EExpressionType.row_constructor_t
38704                                        || (lcexpr.getExpressionType() == EExpressionType.array_constructor_t
38705                                                        && lcexpr.getExprList() != null)) {
38706                                // ROW(a, b): every value of the row is a source (Mantis 4834); ARRAY[a, b] (PostgreSQL, the Trino
38707                                // family, Redshift, Snowflake): every element is a source, as BigQuery's [a, b] (plan section 4.5,
38708                                // Mantis 4878). The expression is a leaf of the traversal, so its values are visited here;
38709                                // ARRAY(SELECT ...) has no list and keeps its subquery path
38710                                if (lcexpr.getExprList() != null) {
38711                                        for (int j = 0; j < lcexpr.getExprList().size(); j++) {
38712                                                TExpression expr = lcexpr.getExprList().getExpression(j);
38713                                                if (expr != null)
38714                                                        expr.inOrderTraverse(this);
38715                                        }
38716                                }
38717                        } else if (lcexpr.getExpressionType() == EExpressionType.array_t) {
38718                                if(lcexpr.getObjectOperand()!=null) {
38719                                        TObjectName object = lcexpr.getObjectOperand();
38720                                        addColumnOrExpandCteAlias(object);
38721                                } else if (lcexpr.getExprList() != null) {
38722                                        for (int j = 0; j < lcexpr.getExprList().size(); j++) {
38723                                                TExpression expr = lcexpr.getExprList().getExpression(j);
38724                                                if (expr != null)
38725                                                        expr.inOrderTraverse(this);
38726                                        }
38727                                }
38728                        } else if (lcexpr.getExpressionType() == EExpressionType.simple_object_name_t) {
38729                                if (lcexpr.getObjectOperand() != null && !(isBuiltInFunctionName(lcexpr.getObjectOperand())
38730                                                && isFromFunction(lcexpr.getObjectOperand()))) {
38731                                        TObjectName object = lcexpr.getObjectOperand();
38732                                        // Skip named argument parameter names (e.g., INPUT in "INPUT => value")
38733                                        // and lambda parameters (x in "x -> x + 1", marked by TSQLResolver2).
38734                                        // These are function parameter names, NOT column references
38735                                        if (object.getObjectType() == TObjectName.ttobjNamedArgParameter
38736                                                        || object.getObjectType() == TObjectName.ttobjLambdaParameter) {
38737                                                // Skip - this is a named argument or lambda parameter name
38738                                        } else if (object.getDbObjectType() == EDbObjectType.column
38739                                                        || object.getDbObjectType() == EDbObjectType.column_alias
38740                                                        || object.getDbObjectType() == EDbObjectType.alias
38741                                                        || object.getDbObjectType() == EDbObjectType.unknown
38742                                                        || object.getDbObjectType() == EDbObjectType.variable
38743                                                        || isTableScopedPseudoColumn(object)) {
38744                                                addColumnOrExpandCteAlias(object);
38745                                        } else if (object.getDbObjectType() == EDbObjectType.notAColumn
38746                                                        || object.getDbObjectType() == EDbObjectType.date_time_part ) {
38747                                                constants.add(object);
38748                                        }
38749                                        // arr[n]: the subscript selects the element, so n is a source too (Mantis 4817)
38750                                        traverseSubscripts(object.getSubscriptExpressions());
38751                                        traverseSubscripts(lcexpr.getSubscriptExpressions());
38752                                }
38753                        } else if (lcexpr.getExpressionType() == EExpressionType.between_t) {
38754                                if (lcexpr.getBetweenOperand() != null && lcexpr.getBetweenOperand().getObjectOperand() != null) {
38755                                        TObjectName object = lcexpr.getBetweenOperand().getObjectOperand();
38756                                        if (object.getDbObjectType() == EDbObjectType.column
38757                                                        || object.getDbObjectType() == EDbObjectType.column_alias
38758                                                        || object.getDbObjectType() == EDbObjectType.alias
38759                                                        || object.getDbObjectType() == EDbObjectType.unknown
38760                                                        || object.getDbObjectType() == EDbObjectType.variable) {
38761                                                addColumnOrExpandCteAlias(object);
38762                                        }
38763                                }
38764                        } else if (lcexpr.getExpressionType() == EExpressionType.new_structured_type_t) {
38765                                // NEW JSON(t.e, LATIN) and NEW JSON(...).JSONEXTRACTVALUE('$.a') (Teradata): the constructor and its
38766                                // methods are a list the traversal does not enter, so they are visited here (Mantis 4919)
38767                                if (lcexpr.getExprList() != null) {
38768                                        for (int j = 0; j < lcexpr.getExprList().size(); j++) {
38769                                                TExpression expr = lcexpr.getExprList().getExpression(j);
38770                                                if (expr != null)
38771                                                        expr.inOrderTraverse(this);
38772                                        }
38773                                }
38774                        } else if (lcexpr.getExpressionType() == EExpressionType.object_access_t) {
38775                                if (lcexpr.getObjectAccess() != null) {
38776                                        TObjectNameList objects = lcexpr.getObjectAccess().getAttributes();
38777                                        TFunctionCall function = lcexpr.getObjectAccess().getObjectExpr().getFunctionCall();
38778                                        if (objects != null && function != null) {
38779                                                for (TObjectName object : objects) {
38780                                                        TGSqlParser sqlparser = createSqlParser(option.getVendor());
38781                                                        sqlparser.sqltext = "select " + function.getFunctionName().toString() + "."
38782                                                                        + object.getColumnNameOnly() + " from " + function.getFunctionName().toString();
38783                                                        if (parseWithCommentBindings(sqlparser) == 0) {
38784                                                                TObjectName objectName = sqlparser.sqlstatements.get(0).getResultColumnList()
38785                                                                                .getResultColumn(0).getFieldAttr();
38786                                                                objectNames.add(objectName);
38787                                                        }
38788                                                }
38789                                        } else {
38790                                                // (NEW JSON(t.e)).JSONEXTRACTVALUE('$.a'), XMLTYPE(t.c).getStringVal(): a method reads the value
38791                                                // of the object it is called on (Mantis 4919)
38792                                                lcexpr.getObjectAccess().getObjectExpr().inOrderTraverse(this);
38793                                        }
38794                                }
38795                        } else if (lcexpr.getExpressionType() == EExpressionType.function_t || lcexpr.getExpressionType() == EExpressionType.fieldselection_t) {
38796                                TFunctionCall func = lcexpr.getFunctionCall();
38797                                if (func == null) {
38798                                        return true;
38799                                }
38800                                if (skipFunction) {
38801                                        if (func.getArgs() != null) {
38802                                                for (int k = 0; k < func.getArgs().size(); k++) {
38803                                                        TExpression expr = func.getArgs().getExpression(k);
38804                                                        if (expr != null)
38805                                                                expr.inOrderTraverse(this);
38806                                                }
38807                                        }
38808
38809                                        if (func.getTrimArgument() != null) {
38810                                                TTrimArgument args = func.getTrimArgument();
38811                                                TExpression expr = args.getStringExpression();
38812                                                if (expr != null) {
38813                                                        expr.inOrderTraverse(this);
38814                                                }
38815                                                expr = args.getTrimCharacter();
38816                                                if (expr != null) {
38817                                                        expr.inOrderTraverse(this);
38818                                                }
38819                                        }
38820
38821                                        if (func.getAgainstExpr() != null) {
38822                                                func.getAgainstExpr().inOrderTraverse(this);
38823                                        }
38824//                                      if (func.getBetweenExpr() != null) {
38825//                                              func.getBetweenExpr().inOrderTraverse(this);
38826//                                      }
38827                                        if (func.getExpr1() != null) {
38828                                                func.getExpr1().inOrderTraverse(this);
38829                                        }
38830                                        if (func.getExpr2() != null) {
38831                                                func.getExpr2().inOrderTraverse(this);
38832                                        }
38833                                        if (func.getExpr3() != null) {
38834                                                func.getExpr3().inOrderTraverse(this);
38835                                        }
38836                                        if (func.getParameter() != null) {
38837                                                func.getParameter().inOrderTraverse(this);
38838                                        }
38839                                        // split(s, ':')[n]: the subscript after the call reads n (Mantis 4817)
38840                                        traverseSubscripts(func.getSubscriptExpressions());
38841                                        traverseSubscripts(lcexpr.getSubscriptExpressions());
38842                                } else {
38843                                        functions.add(func);
38844                                        // the subscript after the call: the selected element depends on it (Mantis 4817); the call's own
38845                                        // subscripts become the function's sources through getFunctionExpressions
38846                                        traverseSubscripts(lcexpr.getSubscriptExpressions());
38847                                }
38848
38849                        } else if (lcexpr.getExpressionType() == EExpressionType.case_t) {
38850                                TCaseExpression expr = lcexpr.getCaseExpression();
38851                                if (skipFunction) {
38852                                        TExpression defaultExpr = expr.getElse_expr();
38853                                        if (defaultExpr != null) {
38854                                                defaultExpr.inOrderTraverse(this);
38855                                        }
38856                                        TWhenClauseItemList list = expr.getWhenClauseItemList();
38857                                        for (int i = 0; i < list.size(); i++) {
38858                                                TWhenClauseItem element = (TWhenClauseItem) list.getElement(i);
38859                                                (((TWhenClauseItem) element).getReturn_expr()).inOrderTraverse(this);
38860
38861                                        }
38862                                } else {
38863                                        functions.add(expr);
38864                                }
38865                        } else if (lcexpr.getSubQuery() != null) {
38866                                TSelectSqlStatement select = lcexpr.getSubQuery();
38867                                analyzeSelectStmt(select);
38868                                subquerys.add(select);
38869                                if (select.getResultColumnList() != null && select.getResultColumnList().size() > 0) {
38870                                        for (TResultColumn column : select.getResultColumnList()) {
38871                                                resultColumns.add(column);
38872                                        }
38873                                }
38874                        }
38875                        return true;
38876                }
38877        }
38878
38879        class joinInExpr implements IExpressionVisitor {
38880
38881                private EJoinType joinType;
38882                private JoinClauseType joinClauseType;
38883                private EffectType effectType;
38884
38885                public joinInExpr(EJoinType joinType, JoinClauseType joinClauseType, EffectType effectType) {
38886                        this.joinType = joinType;
38887                        this.joinClauseType = joinClauseType;
38888                        this.effectType = effectType;
38889                }
38890
38891                boolean is_compare_condition(EExpressionType t) {
38892                        return ((t == EExpressionType.simple_comparison_t) || (t == EExpressionType.group_comparison_t)
38893                                        || (t == EExpressionType.in_t) || (t == EExpressionType.pattern_matching_t)
38894                                        || (t == EExpressionType.left_join_t) || (t == EExpressionType.right_join_t));
38895                }
38896
38897                @Override
38898                public boolean exprVisit(TParseTreeNode pNode, boolean isLeafNode) {
38899                        TExpression expr = (TExpression) pNode;
38900                        if (is_compare_condition(expr.getExpressionType())) {
38901                                TExpression leftExpr = expr.getLeftOperand();
38902                                columnsInExpr leftVisitor = new columnsInExpr();
38903                                leftExpr.inOrderTraverse(leftVisitor);
38904                                List<TObjectName> leftObjectNames = leftVisitor.getObjectNames();
38905                                List<TParseTreeNode> leftObjects = leftVisitor.getFunctions();
38906                                leftObjects.addAll(leftObjectNames);
38907                                
38908                                TExpression rightExpr = expr.getRightOperand();
38909                                columnsInExpr rightVisitor = new columnsInExpr();
38910                                rightExpr.inOrderTraverse(rightVisitor);
38911                                List<TObjectName> rightObjectNames = rightVisitor.getObjectNames();
38912                                List<TParseTreeNode> rightObjects = rightVisitor.getFunctions();
38913                                rightObjects.addAll(rightObjectNames);
38914
38915                                if (!leftObjects.isEmpty() && !rightObjects.isEmpty()) {
38916                                        TCustomSqlStatement stmt = stmtStack.peek();
38917
38918                                        for (int i = 0; i < leftObjects.size(); i++) {
38919                                                TParseTreeNode leftObject = leftObjects.get(i);
38920                                                TTable leftTable = null;
38921                                                TFunctionCall leftFunction = null;
38922                                                TObjectName leftObjectName = null;
38923                                                if (leftObject instanceof TObjectName) {
38924                                                        leftObjectName = (TObjectName)leftObject;
38925
38926                                                        if (leftObjectName.getDbObjectType() == EDbObjectType.variable) {
38927                                                                continue;
38928                                                        }
38929
38930                                                        if (leftObjectName.getColumnNameOnly().startsWith("@")
38931                                                                        && (option.getVendor() == EDbVendor.dbvmssql
38932                                                                                        || option.getVendor() == EDbVendor.dbvazuresql)) {
38933                                                                continue;
38934                                                        }
38935
38936                                                        if (leftObjectName.getColumnNameOnly().startsWith(":")
38937                                                                        && (option.getVendor() == EDbVendor.dbvhana
38938                                                                                        || option.getVendor() == EDbVendor.dbvteradata)) {
38939                                                                continue;
38940                                                        }
38941
38942                                                        leftTable = modelManager.getTable(stmt, leftObjectName);
38943
38944                                                        if (leftTable == null) {
38945                                                                leftTable = leftObjectName.getSourceTable();
38946                                                        }
38947
38948                                                        if (leftTable == null) {
38949                                                                leftTable = modelManager.guessTable(stmt, leftObjectName);
38950                                                        }
38951                                                }
38952                                                else if(leftObject instanceof TFunctionCall){
38953                                                        leftFunction = (TFunctionCall)leftObject;
38954                                                }
38955
38956                                                if (leftTable != null || leftFunction != null) {
38957                                                        for (int j = 0; j < rightObjects.size(); j++) {
38958                                                                JoinRelationship joinRelation = modelFactory.createJoinRelation();
38959                                                                joinRelation.setEffectType(effectType);
38960                                                                if (joinType != null) {
38961                                                                        joinRelation.setJoinType(joinType);
38962                                                                } else {
38963                                                                        if (expr.getLeftOperand().isOracleOuterJoin()) {
38964                                                                                joinRelation.setJoinType(right);
38965                                                                        } else if (expr.getRightOperand().isOracleOuterJoin()) {
38966                                                                                joinRelation.setJoinType(EJoinType.left);
38967                                                                        } else if (expr.getExpressionType() == EExpressionType.left_join_t) {
38968                                                                                joinRelation.setJoinType(EJoinType.left);
38969                                                                        } else if (expr.getExpressionType() == EExpressionType.right_join_t) {
38970                                                                                joinRelation.setJoinType(right);
38971                                                                        } else {
38972                                                                                joinRelation.setJoinType(EJoinType.inner);
38973                                                                        }
38974                                                                }
38975
38976                                                                joinRelation.setJoinClauseType(joinClauseType);
38977                                                                joinRelation.setJoinCondition(expr.toString());
38978
38979
38980                                                                if (leftTable != null) {
38981                                                                        if (functionOutputColumn(modelManager.getModel(leftTable)) != null) {
38982                                                                                // a table function's column is its output column (Mantis 4815)
38983                                                                                joinRelation.addSource(new ResultColumnRelationshipElement(
38984                                                                                                functionOutputColumn(modelManager.getModel(leftTable))));
38985                                                                        } else if (modelManager.getModel(leftTable) instanceof Table) {
38986                                                                                Table tableModel = (Table) modelManager.getModel(leftTable);
38987                                                                                if (tableModel != null) {
38988                                                                                        TableColumn columnModel = modelFactory.createTableColumn(tableModel,
38989                                                                                                        leftObjectName, false);
38990                                                                                        if (columnModel != null) {
38991                                                                                                joinRelation.addSource(new TableColumnRelationshipElement(columnModel));
38992                                                                                        }
38993                                                                                }
38994                                                                        } else if (modelManager.getModel(leftTable) instanceof QueryTable) {
38995                                                                                QueryTable table = (QueryTable) modelManager.getModel(leftTable);
38996                                                                                TSelectSqlStatement subquery = table.getTableObject().getSubquery();
38997                                                                                if (subquery != null && subquery.isCombinedQuery()) {
38998                                                                                        ResultColumn resultColumn = matchResultColumn(table.getColumns(),
38999                                                                                                        leftObjectName);
39000                                                                                        if (resultColumn != null) {
39001                                                                                                joinRelation
39002                                                                                                                .addSource(new ResultColumnRelationshipElement(resultColumn));
39003                                                                                        }
39004                                                                                } else if (leftObjectName.getSourceColumn() != null) {
39005                                                                                        Object model = modelManager.getModel(leftObjectName);
39006                                                                                        if (model == null) {
39007                                                                                                model = modelFactory.createResultColumn(table, leftObjectName);
39008                                                                                        }
39009                                                                                        if (model instanceof ResultColumn) {
39010                                                                                                ResultColumn resultColumn = (ResultColumn) model;
39011                                                                                                if (resultColumn != null) {
39012                                                                                                        joinRelation.addSource(
39013                                                                                                                        new ResultColumnRelationshipElement(resultColumn));
39014                                                                                                }
39015                                                                                        } else if (model instanceof LinkedHashMap) {
39016                                                                                                String columnName = getColumnNameOnly(leftObjectName.toString());
39017                                                                                                LinkedHashMap<String, ResultColumn> resultColumns = (LinkedHashMap<String, ResultColumn>) model;
39018                                                                                                if (resultColumns.containsKey(columnName)) {
39019                                                                                                        ResultColumn resultColumn = resultColumns.get(columnName);
39020                                                                                                        joinRelation.addSource(
39021                                                                                                                        new ResultColumnRelationshipElement(resultColumn));
39022                                                                                                }
39023                                                                                        }
39024                                                                                } else {
39025                                                                                        ResultColumn resultColumn = matchResultColumn(table.getColumns(),
39026                                                                                                        leftObjectName);
39027                                                                                        if (resultColumn != null) {
39028                                                                                                joinRelation
39029                                                                                                                .addSource(new ResultColumnRelationshipElement(resultColumn));
39030                                                                                        }
39031                                                                                }
39032                                                                        }
39033                                                                }
39034                                                                else if(leftFunction!=null) {
39035                                                                        Object functionObj = createFunction(leftFunction);
39036                                                                        if(functionObj instanceof Function) {
39037                                                                                Function function = (Function)functionObj;
39038                                                                                joinRelation.addSource(new ResultColumnRelationshipElement(function.getColumns().get(0)));
39039                                                                        }
39040                                                                }
39041
39042                                                                TParseTreeNode rightObject = rightObjects.get(j);
39043                                                                if(rightObject instanceof TObjectName) {
39044                                                                        TObjectName rightObjectName = (TObjectName)rightObject;
39045
39046                                                                        if (rightObjectName.getDbObjectType() == EDbObjectType.variable) {
39047                                                                                continue;
39048                                                                        }
39049
39050                                                                        if (rightObjectName.getColumnNameOnly().startsWith("@")
39051                                                                                        && (option.getVendor() == EDbVendor.dbvmssql
39052                                                                                                        || option.getVendor() == EDbVendor.dbvazuresql)) {
39053                                                                                continue;
39054                                                                        }
39055
39056                                                                        if (rightObjectName.getColumnNameOnly().startsWith(":")
39057                                                                                        && (option.getVendor() == EDbVendor.dbvhana
39058                                                                                                        || option.getVendor() == EDbVendor.dbvteradata)) {
39059                                                                                continue;
39060                                                                        }
39061
39062                                                                        TTable rightTable = modelManager.getTable(stmt, rightObjectName);
39063                                                                        if (rightTable == null) {
39064                                                                                rightTable = rightObjectName.getSourceTable();
39065                                                                        }
39066
39067                                                                        if (rightTable == null) {
39068                                                                                rightTable = modelManager.guessTable(stmt, rightObjectName);
39069                                                                        }
39070
39071                                                                        if (functionOutputColumn(modelManager.getModel(rightTable)) != null) {
39072                                                                                // a table function's column is its output column (Mantis 4815)
39073                                                                                joinRelation.setTarget(new ResultColumnRelationshipElement(
39074                                                                                                functionOutputColumn(modelManager.getModel(rightTable))));
39075                                                                        } else if (modelManager.getModel(rightTable) instanceof Table) {
39076                                                                                Table tableModel = (Table) modelManager.getModel(rightTable);
39077                                                                                if (tableModel != null) {
39078                                                                                        TableColumn columnModel = modelFactory.createTableColumn(tableModel,
39079                                                                                                        rightObjectName, false);
39080                                                                                        if(columnModel != null) {
39081                                                                                                joinRelation.setTarget(new TableColumnRelationshipElement(columnModel));
39082                                                                                        }
39083                                                                                }
39084                                                                        } else if (modelManager.getModel(rightTable) instanceof QueryTable) {
39085                                                                                QueryTable table = (QueryTable) modelManager.getModel(rightTable);
39086                                                                                TSelectSqlStatement subquery = table.getTableObject().getSubquery();
39087                                                                                if (subquery != null && subquery.isCombinedQuery()) {
39088                                                                                        ResultColumn resultColumn = matchResultColumn(table.getColumns(),
39089                                                                                                        rightObjectName);
39090                                                                                        if (resultColumn != null) {
39091                                                                                                joinRelation.setTarget(new ResultColumnRelationshipElement(resultColumn));
39092                                                                                        }
39093                                                                                } else if (rightObjectName.getSourceColumn() != null) {
39094                                                                                        Object model = modelManager.getModel(rightObjectName);
39095                                                                                        if (model == null) {
39096                                                                                                model = modelManager
39097                                                                                                                .getModel(rightObjectName.getSourceColumn());
39098                                                                                        }
39099                                                                                        if (model instanceof ResultColumn) {
39100                                                                                                joinRelation.setTarget(new ResultColumnRelationshipElement((ResultColumn)model));
39101                                                                                        }
39102                                                                                        else if (model instanceof LinkedHashMap) {
39103                                                                                                String columnName = getColumnNameOnly(rightObjectName.toString());
39104                                                                                                LinkedHashMap<String, ResultColumn> resultColumns = (LinkedHashMap<String, ResultColumn>)model;
39105                                                                                                if (resultColumns.containsKey(columnName)) {
39106                                                                                                        ResultColumn resultColumn = resultColumns.get(columnName);
39107                                                                                                        joinRelation.setTarget(new ResultColumnRelationshipElement(resultColumn));
39108                                                                                                }
39109                                                                                        }
39110                                                                                } else {
39111                                                                                        ResultColumn resultColumn = matchResultColumn(table.getColumns(),
39112                                                                                                        rightObjectName);
39113                                                                                        if (resultColumn != null) {
39114                                                                                                joinRelation.setTarget(new ResultColumnRelationshipElement(resultColumn));
39115                                                                                        }
39116                                                                                }
39117                                                                        }
39118                                                                }
39119                                                                else if(rightObject instanceof TFunctionCall) {
39120                                                                        Object functionObj = createFunction(rightObject);
39121                                                                        if(functionObj instanceof Function) {
39122                                                                                Function function = (Function)functionObj;
39123                                                                                joinRelation.setTarget(new ResultColumnRelationshipElement(function.getColumns().get(0)));
39124                                                                        }
39125                                                                }
39126                                                        }
39127                                                }
39128                                        }
39129                                }
39130                        }
39131                        return true;
39132                }
39133        }
39134
39135        @Deprecated
39136        public static Dataflow getSqlflowJSONModel(dataflow dataflow) {
39137                EDbVendor vendor = ModelBindingManager.getGlobalVendor();
39138                if(vendor == null) {
39139                        throw new IllegalArgumentException("getSqlflowJSONModel(dataflow dataflow) is deprecated, please call method getSqlflowJSONModel(dataflow dataflow, EDbVendor vendor).");
39140                }
39141                return getSqlflowJSONModel(vendor, dataflow, false);
39142        }
39143
39144        public static Dataflow getSqlflowJSONModel(dataflow dataflow, EDbVendor vendor) {
39145                return getSqlflowJSONModel(vendor, dataflow, false);
39146        }
39147
39148        public static Dataflow getSqlflowJSONModel(EDbVendor vendor, dataflow dataflow, boolean normalizeIdentifier) {
39149                Dataflow model = new Dataflow();
39150        if (dataflow != null) model.setCommentBindings(dataflow.getCommentBindings());
39151                
39152                if (dataflow.getErrors() != null && !dataflow.getErrors().isEmpty()) {
39153                        List<Error> errorList = new ArrayList<Error>();
39154                        for (error error : dataflow.getErrors()) {
39155                                Error err = new Error();
39156                                err.setErrorMessage(error.getErrorMessage());
39157                                err.setErrorType(error.getErrorType());
39158                                err.setCoordinates(Coordinate.parse(error.getCoordinate()));
39159                                err.setFile(err.getFile());
39160                                err.setOriginCoordinates(Coordinate.parse(error.getOriginCoordinate()));
39161                                errorList.add(err);
39162                        }
39163                        model.setErrors(errorList.toArray(new Error[0]));
39164                }
39165
39166                Sqlflow sqlflow = MetadataUtil.convertDataflowToMetadata(vendor, dataflow);
39167                sqlflow.setErrorMessages(null);
39168                model.setDbobjs(sqlflow);
39169                model.setOrientation(dataflow.getOrientation());
39170
39171
39172                List<gudusoft.gsqlparser.dlineage.dataflow.model.json.Process> processes = new ArrayList<gudusoft.gsqlparser.dlineage.dataflow.model.json.Process>();
39173                if(dataflow.getProcesses()!=null){
39174                        for(process process: dataflow.getProcesses()){
39175                                gudusoft.gsqlparser.dlineage.dataflow.model.json.Process processModel = new gudusoft.gsqlparser.dlineage.dataflow.model.json.Process();
39176                                processModel.setId(process.getId());
39177                                processModel.setName(process.getName());
39178                                processModel.setProcedureId(process.getProcedureId());
39179                                processModel.setProcedureName(process.getProcedureName());
39180                                processModel.setType(process.getType());
39181                                processModel.setCoordinate(process.getCoordinate());
39182                                processModel.setDatabase(process.getDatabase());
39183                                processModel.setSchema(process.getSchema());
39184                                processModel.setServer(process.getServer());
39185                                processModel.setQueryHashId(process.getQueryHashId());
39186                                if (process.getTransforms() != null && !process.getTransforms().isEmpty()) {
39187                                        List<gudusoft.gsqlparser.dlineage.dataflow.model.json.Transform> transforms = new ArrayList<gudusoft.gsqlparser.dlineage.dataflow.model.json.Transform>();
39188                                        for (transform transform : process.getTransforms()) {
39189                                                gudusoft.gsqlparser.dlineage.dataflow.model.json.Transform item = new gudusoft.gsqlparser.dlineage.dataflow.model.json.Transform();
39190                                                item.setCode(transform.getCode());
39191                                                item.setType(transform.getType());
39192                                                item.setCoordinate(transform.getCoordinate(true));
39193                                                transforms.add(item);
39194                                        }
39195                                        processModel.setTransforms(transforms
39196                                                        .toArray(new gudusoft.gsqlparser.dlineage.dataflow.model.json.Transform[0]));
39197                                }
39198                                processes.add(processModel);
39199                        }
39200                }
39201                model.setProcesses(processes.toArray(new gudusoft.gsqlparser.dlineage.dataflow.model.json.Process[0]));
39202
39203                Map<String, String> tableIdToProcedureId = new HashMap<>();
39204                if (dataflow.getTables() != null) {
39205                        for (table t : dataflow.getTables()) {
39206                                if (t.getProcedureId() != null) {
39207                                        tableIdToProcedureId.put(t.getId(), t.getProcedureId());
39208                                }
39209                        }
39210                }
39211
39212                List<gudusoft.gsqlparser.dlineage.dataflow.model.json.Relationship> relations = new ArrayList<gudusoft.gsqlparser.dlineage.dataflow.model.json.Relationship>();
39213                if (dataflow.getRelationships() != null) {
39214                        for (relationship relation : dataflow.getRelationships()) {
39215                                gudusoft.gsqlparser.dlineage.dataflow.model.json.Relationship relationModel;
39216                                if (relation.getType().equals("join")) {
39217                                        gudusoft.gsqlparser.dlineage.dataflow.model.json.JoinRelationship joinRelationModel = new gudusoft.gsqlparser.dlineage.dataflow.model.json.JoinRelationship();
39218                                        joinRelationModel.setCondition(relation.getCondition());
39219                                        joinRelationModel.setJoinType(relation.getJoinType());
39220                                        joinRelationModel.setClause(relation.getClause());
39221                                        relationModel = joinRelationModel;
39222                                } else {
39223                                        relationModel = new gudusoft.gsqlparser.dlineage.dataflow.model.json.Relationship();
39224                                }
39225
39226                                relationModel.setId(relation.getId());
39227                                relationModel.setProcessId(relation.getProcessId());
39228                                relationModel.setProcessType(relation.getProcessType());
39229                                relationModel.setType(relation.getType());
39230                                relationModel.setEffectType(relation.getEffectType());
39231                                relationModel.setPartition(relation.getPartition());
39232                                relationModel.setFunction(relation.getFunction());
39233                                relationModel.setProcedureId(relation.getProcedureId());
39234                                relationModel.setSqlHash(relation.getSqlHash());
39235                                relationModel.setCondition(relation.getCondition());
39236                                relationModel.setSqlComment(relation.getSqlComment());
39237                                relationModel.setTimestampMax(relation.getTimestampMax());
39238                                relationModel.setTimestampMin(relation.getTimestampMin());
39239                                if (Boolean.TRUE.equals(relation.getBuiltIn())) {
39240                                        relationModel.setBuiltIn(relation.getBuiltIn());
39241                                }
39242                                relationModel.setCallStmt(relation.getCallStmt());
39243                                relationModel.setCallCoordinate(relation.getCallCoordinate());
39244                                
39245                                if (relation.getTarget() != null && relation.getSources() != null && !relation.getSources().isEmpty()) {
39246                                        {
39247                                                gudusoft.gsqlparser.dlineage.dataflow.model.json.RelationshipElement targetModel = new gudusoft.gsqlparser.dlineage.dataflow.model.json.RelationshipElement();
39248                                                targetColumn target = relation.getTarget();
39249                                                if (normalizeIdentifier) {
39250                                                        targetModel.setColumn(LegacyDlineageWireNamesV1.columnName(vendor, target.getColumn()));
39251                                                        targetModel.setParentName(
39252                                                                        LegacyDlineageWireNamesV1.tableName(vendor, target.getParent_name()));
39253                                                        targetModel.setTargetName(
39254                                                                        LegacyDlineageWireNamesV1.columnName(vendor, target.getTarget_name()));
39255                                                } else {
39256                                                        targetModel.setColumn(target.getColumn());
39257                                                        targetModel.setParentName(target.getParent_name());
39258                                                        targetModel.setTargetName(target.getTarget_name());
39259                                                }
39260                                                targetModel.setId(target.getId());
39261                                                targetModel.setTargetId(target.getTarget_id());
39262                                                targetModel.setParentId(target.getParent_id());
39263                                                targetModel.setCoordinates(Coordinate.parse(target.getCoordinate()));
39264                                                targetModel.setFunction(target.getFunction());
39265                                                targetModel.setType(target.getType());
39266                                                String targetProcId = tableIdToProcedureId.get(target.getParent_id());
39267                                                if (targetProcId != null) {
39268                                                        targetModel.setProcedureId(targetProcId);
39269                                                }
39270                                                relationModel.setTarget(targetModel);
39271                                        }
39272
39273                                        List<gudusoft.gsqlparser.dlineage.dataflow.model.json.RelationshipElement> sourceModels = new ArrayList<gudusoft.gsqlparser.dlineage.dataflow.model.json.RelationshipElement>();
39274                                        for (sourceColumn source : relation.getSources()) {
39275                                                gudusoft.gsqlparser.dlineage.dataflow.model.json.RelationshipElement sourceModel = new gudusoft.gsqlparser.dlineage.dataflow.model.json.RelationshipElement();
39276                                                if (normalizeIdentifier) {
39277                                                        sourceModel.setColumn(LegacyDlineageWireNamesV1.columnName(vendor, source.getColumn()));
39278                                                        sourceModel.setParentName(
39279                                                                        LegacyDlineageWireNamesV1.tableName(vendor, source.getParent_name()));
39280                                                        sourceModel.setSourceName(
39281                                                                        LegacyDlineageWireNamesV1.columnName(vendor, source.getSource_name()));
39282                                                } else {
39283                                                        sourceModel.setColumn(source.getColumn());
39284                                                        sourceModel.setParentName(source.getParent_name());
39285                                                        sourceModel.setSourceName(source.getSource_name());
39286                                                }
39287                                                sourceModel.setColumnType(source.getColumn_type());
39288                                                sourceModel.setId(source.getId());
39289                                                sourceModel.setParentId(source.getParent_id());
39290                                                sourceModel.setSourceId(source.getSource_id());
39291                                                sourceModel.setCoordinates(Coordinate.parse(source.getCoordinate()));
39292                                                sourceModel.setClauseType(source.getClauseType());
39293                                                sourceModel.setType(source.getType());
39294                                                String sourceProcId = tableIdToProcedureId.get(source.getParent_id());
39295                                                if (sourceProcId != null) {
39296                                                        sourceModel.setProcedureId(sourceProcId);
39297                                                }
39298                                                sourceModels.add(sourceModel);
39299                                                if (source.getTransforms() != null && !source.getTransforms().isEmpty()) {
39300                                                        List<gudusoft.gsqlparser.dlineage.dataflow.model.json.Transform> transforms = new ArrayList<gudusoft.gsqlparser.dlineage.dataflow.model.json.Transform>();
39301                                                        for (transform transform : source.getTransforms()) {
39302                                                                gudusoft.gsqlparser.dlineage.dataflow.model.json.Transform item = new gudusoft.gsqlparser.dlineage.dataflow.model.json.Transform();
39303                                                                item.setCode(transform.getCode());
39304                                                                item.setType(transform.getType());
39305                                                                item.setCoordinate(transform.getCoordinate(true));
39306                                                                transforms.add(item);
39307                                                        }
39308                                                        sourceModel.setTransforms(transforms
39309                                                                        .toArray(new gudusoft.gsqlparser.dlineage.dataflow.model.json.Transform[0]));
39310                                                }
39311                                                
39312                                                if (source.getCandidateParents() != null && !source.getCandidateParents().isEmpty()) {
39313                                                        List<gudusoft.gsqlparser.dlineage.dataflow.model.json.CandidateTable> candidateParents = new ArrayList<gudusoft.gsqlparser.dlineage.dataflow.model.json.CandidateTable>();
39314                                                        for (candidateTable candidateTable : source.getCandidateParents()) {
39315                                                                gudusoft.gsqlparser.dlineage.dataflow.model.json.CandidateTable item = new gudusoft.gsqlparser.dlineage.dataflow.model.json.CandidateTable();
39316                                                                item.setId(candidateTable.getId());
39317                                                                if (normalizeIdentifier) {
39318                                                                        item.setName(
39319                                                                                        LegacyDlineageWireNamesV1.tableName(vendor, candidateTable.getName()));
39320                                                                } else {
39321                                                                        item.setName(candidateTable.getName());
39322                                                                }
39323                                                                candidateParents.add(item);
39324                                                        }
39325                                                        sourceModel.setCandidateParents(candidateParents
39326                                                                        .toArray(new gudusoft.gsqlparser.dlineage.dataflow.model.json.CandidateTable[0]));
39327                                                }
39328                                        }
39329                                        relationModel.setSources(sourceModels
39330                                                        .toArray(new gudusoft.gsqlparser.dlineage.dataflow.model.json.RelationshipElement[0]));
39331                                        relations.add(relationModel);
39332                                } else if (relation.getCaller() != null && relation.getCallees() != null
39333                                                && !relation.getCallees().isEmpty()) {
39334                                        {
39335                                                gudusoft.gsqlparser.dlineage.dataflow.model.json.RelationshipElement targetModel = new gudusoft.gsqlparser.dlineage.dataflow.model.json.RelationshipElement();
39336                                                targetColumn target = relation.getCaller();
39337                                                if (normalizeIdentifier) {
39338                                                        targetModel.setName(LegacyDlineageWireNamesV1.columnName(vendor, target.getName()));
39339                                                } else {
39340                                                        targetModel.setName(target.getName());
39341                                                }
39342                                                targetModel.setId(target.getId());
39343                                                targetModel.setCoordinates(Coordinate.parse(target.getCoordinate()));
39344                                                targetModel.setType(target.getType());
39345                                                relationModel.setCaller(targetModel);
39346                                        }
39347
39348                                        List<gudusoft.gsqlparser.dlineage.dataflow.model.json.RelationshipElement> sourceModels = new ArrayList<gudusoft.gsqlparser.dlineage.dataflow.model.json.RelationshipElement>();
39349                                        for (sourceColumn source : relation.getCallees()) {
39350                                                gudusoft.gsqlparser.dlineage.dataflow.model.json.RelationshipElement sourceModel = new gudusoft.gsqlparser.dlineage.dataflow.model.json.RelationshipElement();
39351                                                if (normalizeIdentifier) {
39352                                                        sourceModel.setName(LegacyDlineageWireNamesV1.columnName(vendor, source.getName()));
39353                                                } else {
39354                                                        sourceModel.setName(source.getName());
39355                                                }
39356                                                sourceModel.setId(source.getId());
39357                                                sourceModel.setCoordinates(Coordinate.parse(source.getCoordinate()));
39358                                                sourceModel.setType(source.getType());
39359                                                sourceModels.add(sourceModel);
39360                                        }
39361                                        relationModel.setCallees(sourceModels
39362                                                        .toArray(new gudusoft.gsqlparser.dlineage.dataflow.model.json.RelationshipElement[0]));
39363                                        relations.add(relationModel);
39364                                }
39365                        }
39366                }
39367                model.setRelationships(relations.toArray(new gudusoft.gsqlparser.dlineage.dataflow.model.json.Relationship[0]));
39368                return model;
39369        }
39370
39371        public static String getVersion() {
39372                // Return the authoritative product version instead of a stale hardcoded
39373                // constant so version-reporting stays in sync with each release (MantisBT
39374                // / QS-009). TBaseType.versionid is the single source of truth (also used
39375                // by the JAR manifest and release notes).
39376                return TBaseType.versionid;
39377        }
39378
39379        public static String getReleaseDate() {
39380                return TBaseType.releaseDate;
39381        }
39382
39383        public static void main(String[] args) {
39384                if (args.length < 1) {
39385                        System.out.println(
39386                                        "Usage: java DataFlowAnalyzer [/f <path_to_sql_file>] [/d <path_to_directory_includes_sql_files>] [/s [/text]] [/json] [/traceView] [/t <database type>] [/o <output file path>][/version]");
39387                        System.out.println("/f: Option, specify the sql file path to analyze fdd relation.");
39388                        System.out.println("/d: Option, specify the sql directory path to analyze fdd relation.");
39389                        System.out.println("/j: Option, analyze the join relation.");
39390                        System.out.println("/s: Option, simple output, ignore the intermediate results.");
39391                        System.out.println("/i: Option, ignore all result sets.");
39392                        System.out.println("/traceView: Option, analyze the source tables of views.");
39393                        System.out.println("/text: Option, print the plain text format output.");
39394                        System.out.println("/json: Option, print the json format output.");
39395                        System.out.println(
39396                                        "/t: Option, set the database type. Support oracle, mysql, mssql, db2, netezza, teradata, informix, sybase, postgresql, hive, greenplum and redshift, the default type is oracle");
39397                        System.out.println("/o: Option, write the output stream to the specified file.");
39398                        System.out.println("/log: Option, generate a dataflow.log file to log information.");
39399                        return;
39400                }
39401
39402                File sqlFiles = null;
39403
39404                List<String> argList = Arrays.asList(args);
39405
39406                if (argList.indexOf("/version") != -1) {
39407                        System.out.println("Version: " + DataFlowAnalyzer.getVersion());
39408                        System.out.println("Release Date: " + DataFlowAnalyzer.getReleaseDate());
39409                        return;
39410                }
39411
39412                if (argList.indexOf("/f") != -1 && argList.size() > argList.indexOf("/f") + 1) {
39413                        sqlFiles = new File(args[argList.indexOf("/f") + 1]);
39414                        if (!sqlFiles.exists() || !sqlFiles.isFile()) {
39415                                System.out.println(sqlFiles + " is not a valid file.");
39416                                return;
39417                        }
39418                } else if (argList.indexOf("/d") != -1 && argList.size() > argList.indexOf("/d") + 1) {
39419                        sqlFiles = new File(args[argList.indexOf("/d") + 1]);
39420                        if (!sqlFiles.exists() || !sqlFiles.isDirectory()) {
39421                                System.out.println(sqlFiles + " is not a valid directory.");
39422                                return;
39423                        }
39424                } else {
39425                        System.out.println("Please specify a sql file path or directory path to analyze dlineage.");
39426                        return;
39427                }
39428
39429                EDbVendor vendor = EDbVendor.dbvoracle;
39430
39431                int index = argList.indexOf("/t");
39432
39433                if (index != -1 && args.length > index + 1) {
39434                        vendor = TGSqlParser.getDBVendorByName(args[index + 1]);
39435                }
39436
39437                String outputFile = null;
39438
39439                index = argList.indexOf("/o");
39440
39441                if (index != -1 && args.length > index + 1) {
39442                        outputFile = args[index + 1];
39443                }
39444
39445                FileOutputStream writer = null;
39446                if (outputFile != null) {
39447                        try {
39448                                writer = new FileOutputStream(outputFile);
39449                                System.setOut(new PrintStream(writer));
39450                        } catch (FileNotFoundException e) {
39451                                logger.error("output file is not found.", e);
39452                        }
39453                }
39454
39455                boolean simple = argList.indexOf("/s") != -1;
39456                boolean ignoreResultSets = argList.indexOf("/i") != -1;
39457                boolean showJoin = argList.indexOf("/j") != -1;
39458                boolean textFormat = false;
39459                boolean jsonFormat = false;
39460                if (simple) {
39461                        textFormat = argList.indexOf("/text") != -1;
39462                }
39463
39464                boolean traceView = argList.indexOf("/traceView") != -1;
39465                if (traceView) {
39466                        simple = true;
39467                }
39468
39469                jsonFormat = argList.indexOf("/json") != -1;
39470
39471                DataFlowAnalyzer dlineage = new DataFlowAnalyzer(sqlFiles, vendor, simple);
39472
39473                dlineage.setShowJoin(showJoin);
39474                dlineage.setIgnoreRecordSet(ignoreResultSets);
39475                // dlineage.setShowImplicitSchema(true);
39476
39477                if (simple && !jsonFormat) {
39478                        dlineage.setTextFormat(textFormat);
39479                }
39480
39481                String result = dlineage.generateDataFlow();
39482
39483//              dataflow dataflow = ProcessUtility.generateTableLevelLineage(dlineage, dlineage.getDataFlow());
39484//              System.out.println(result);
39485
39486                if (jsonFormat) {
39487                        // Map jsonResult = new LinkedHashMap();
39488                        Dataflow model = getSqlflowJSONModel(vendor, dlineage.getDataFlow(), true);
39489                        // jsonResult.put("data", BeanUtils.bean2Map(model));
39490                        result = JSON.toJSONString(model);
39491                } else if (traceView) {
39492                        result = dlineage.traceView();
39493                }
39494
39495                if (result != null) {
39496                        System.out.println(result);
39497
39498                        if (writer != null && result.length() < 1024 * 1024) {
39499                                System.err.println(result);
39500                        }
39501                }
39502
39503                try {
39504                        if (writer != null) {
39505                                writer.close();
39506                        }
39507                } catch (IOException e) {
39508                        logger.error("close writer failed.", e);
39509                }
39510
39511                boolean log = argList.indexOf("/log") != -1;
39512
39513                PrintStream systemSteam = System.err;
39514                ByteArrayOutputStream sw = new ByteArrayOutputStream();
39515                PrintStream pw = new PrintStream(sw);
39516                System.setErr(pw);
39517
39518
39519                List<ErrorInfo> errors = dlineage.getErrorMessages();
39520                if (!errors.isEmpty()) {
39521                        System.err.println("Error log:\n");
39522                        for (int i = 0; i < errors.size(); i++) {
39523                                System.err.println(errors.get(i).getErrorMessage());
39524                        }
39525                }
39526
39527                if (sw != null) {
39528                        String errorMessage = sw.toString().trim();
39529                        if (errorMessage.length() > 0) {
39530                                if (log) {
39531                                        try {
39532                                                pw = new PrintStream(new File(".", "dataflow.log"));
39533                                                pw.print(errorMessage);
39534                                        } catch (FileNotFoundException e) {
39535                                                logger.error("error log file is not found.", e);
39536                                        }
39537                                }
39538
39539                                System.setErr(systemSteam);
39540                                System.err.println(errorMessage);
39541                        }
39542                }
39543        }
39544
39545        public List<ErrorInfo> getErrorMessages() {
39546                return errorInfos;
39547        }
39548
39549        /**
39550         * Every dynamic-SQL execution site (T-SQL {@code EXEC(...)} / {@code sp_executesql}) the analyzer
39551         * encountered, with whether it was statically resolved. Diagnostic only; not consumed by lineage.
39552         * Read-only snapshot; see {@link DynamicSqlSite}.
39553         */
39554        public List<DynamicSqlSite> getDynamicSqlSites() {
39555                return java.util.Collections.unmodifiableList(new ArrayList<DynamicSqlSite>(dynamicSqlSites));
39556        }
39557
39558        /**
39559         * Resolve dynamic-SQL object names by abstractly evaluating a stored procedure
39560         * with a set of parameter bindings.
39561         *
39562         * <p>Given a T-SQL {@code CREATE PROC} parse tree and the values its parameters
39563         * actually had at a call site (e.g. {@code @SourceDB='QSP'},
39564         * {@code @STG_AEG='STG_AEG'}), this evaluates the procedure's dynamic-SQL string
39565         * building (assignment + concatenation + name-building functions), materializes
39566         * the concrete SQL each {@code EXEC} / {@code sp_executesql} site runs, analyzes
39567         * it with ordinary dlineage, and returns the lineage edges tagged with dynamic
39568         * provenance ({@code origin=DYNAMIC_RESOLVED}, source proc, stable dynamic-site
39569         * id, binding hash). Sites that cannot be reduced are reported as honest
39570         * {@code UNRESOLVED} diagnostics — never guessed.
39571         *
39572         * <p>This is abstract string evaluation, NOT execution: no table reads, no
39573         * cursors, no data-dependent control flow. It is purely additive — it builds
39574         * throwaway analyzers for the materialized strings and does not change the
39575         * default dlineage output of this analyzer.
39576         *
39577         * @param procAst         the {@code CREATE PROC} parse tree (currently T-SQL)
39578         * @param vendor          the SQL dialect ({@code dbvmssql})
39579         * @param sqlEnv          catalog env for the resolved analysis (may be null)
39580         * @param currentDatabase the proc's database (resolves unqualified names)
39581         * @param defaultSchema   the proc's default schema (e.g. {@code dbo})
39582         * @param bindings        parameter name → concrete value
39583         * @param options         resource bounds (may be null for defaults)
39584         */
39585        public gudusoft.gsqlparser.dlineage.dynamicsql.DynamicLineageResult resolveDynamicSqlLineage(
39586                        TCustomSqlStatement procAst, EDbVendor vendor, gudusoft.gsqlparser.sqlenv.TSQLEnv sqlEnv,
39587                        String currentDatabase, String defaultSchema,
39588                        Map<String, gudusoft.gsqlparser.dlineage.dynamicsql.SqlValue> bindings,
39589                        gudusoft.gsqlparser.dlineage.dynamicsql.DynamicLineageOptions options) {
39590                return gudusoft.gsqlparser.dlineage.dynamicsql.DynamicSqlLineageResolver.resolve(
39591                                procAst, vendor, sqlEnv, currentDatabase, defaultSchema, bindings, options);
39592        }
39593
39594        public String traceView() {
39595                StringBuilder buffer = new StringBuilder();
39596                dataflow dataflow = this.getDataFlow();
39597                Map<table, Set<table>> traceViewMap = new LinkedHashMap<table, Set<table>>();
39598                if (dataflow != null && dataflow.getViews() != null) {
39599                        List<relationship> relations = dataflow.getRelationships();
39600                        Map<String, table> viewMap = new HashMap<String, table>();
39601                        Map<String, table> tableMap = new HashMap<String, table>();
39602                        for (table view : dataflow.getViews()) {
39603                                viewMap.put(view.getId(), view);
39604                                tableMap.put(view.getId(), view);
39605                        }
39606                        for (table table : dataflow.getTables()) {
39607                                tableMap.put(table.getId(), table);
39608                        }
39609                        for (relationship relation : relations) {
39610                                if (!RelationshipType.fdd.name().equals(relation.getType())) {
39611                                        continue;
39612                                }
39613                                String parentId = relation.getTarget().getParent_id();
39614                                if (viewMap.containsKey(parentId)) {
39615                                        if (!traceViewMap.containsKey(viewMap.get(parentId))) {
39616                                                traceViewMap.put(viewMap.get(parentId), new LinkedHashSet<table>());
39617                                        }
39618
39619                                        for (sourceColumn sourceColumn : relation.getSources()) {
39620                                                traceViewMap.get(viewMap.get(parentId)).add(tableMap.get(sourceColumn.getParent_id()));
39621                                        }
39622                                }
39623                        }
39624
39625                        Map<table, Set<table>> viewTableMap = new LinkedHashMap<table, Set<table>>();
39626                        for (table view : traceViewMap.keySet()) {
39627                                Set<table> tables = new LinkedHashSet<table>();
39628                                traverseViewSourceTables(tables, view, traceViewMap);
39629                                viewTableMap.put(view, tables);
39630                        }
39631
39632                        for (table view : viewTableMap.keySet()) {
39633                                buffer.append(view.getFullName());
39634                                for (table table : viewTableMap.get(view)) {
39635                                        buffer.append(",").append(table.getFullName());
39636                                }
39637                                buffer.append(System.getProperty("line.separator"));
39638                        }
39639                }
39640                return buffer.toString().trim();
39641        }
39642
39643        private void traverseViewSourceTables(Set<table> tables, table view, Map<table, Set<table>> traceViewMap) {
39644                Set<table> sourceTables = traceViewMap.get(view);
39645                for (table sourceTable : sourceTables) {
39646                        if (sourceTable.isTable()) {
39647                                tables.add(sourceTable);
39648                        } else if (sourceTable.isView()) {
39649                                traverseViewSourceTables(tables, sourceTable, traceViewMap);
39650                        }
39651                }
39652        }
39653
39654        protected List<SqlInfo> convertSQL(EDbVendor vendor, String json) {
39655                List<SqlInfo> sqlInfos = new ArrayList<SqlInfo>();
39656                List sqlContents = (List) JSON.parseObject(json);
39657                for (int j = 0; j < sqlContents.size(); j++) {
39658                        Map sqlContent = (Map) sqlContents.get(j);
39659                        String sql = (String) sqlContent.get("sql");
39660                        String fileName = (String) sqlContent.get("fileName");
39661                        String filePath = (String) sqlContent.get("filePath");
39662                        if (sql != null && sql.trim().startsWith("{")) {
39663                                if (sql.indexOf("createdBy") != -1) {
39664                                        String shardBaseDir = filePath != null ? new File(filePath).getParent() : null;
39665                                        // Route on the authoritative `format` first. A sqlflow-sharded
39666                                        // manifest carries the product brand (SQLdep/grabit) in createdBy
39667                                        // but has no single-file `queries` array — reading it through the
39668                                        // brand path silently dropped every source record.
39669                                        if (MetadataReader.isSqlflowSharded(sql)) {
39670                                                if (MetadataReader.isSupportedSqlflowSharded(sql)) {
39671                                                        appendShardedSourceSqlInfos(sql, shardBaseDir, fileName, sqlInfos);
39672                                                }
39673                                                // Unsupported version, or no base dir to locate shards:
39674                                                // nothing is added — never misrouted, never crashes.
39675                                        } else if (sql.toLowerCase(Locale.ROOT).indexOf("sqldep") != -1
39676                                                        || sql.toLowerCase(Locale.ROOT).indexOf("grabit") != -1) {
39677                                                Map queryObject = (Map) JSON.parseObject(sql);
39678                                                List querys = (List) queryObject.get("queries");
39679                                                if (querys != null) {
39680                                                        for (int i = 0; i < querys.size(); i++) {
39681                                                                Map object = (Map) querys.get(i);
39682                                                                SqlInfo info = new SqlInfo();
39683                                                                info.setSql(JSON.toJSONString(object));
39684                                                                info.setFileName(fileName);
39685                                                                info.setFilePath(filePath);
39686                                                                info.setOriginIndex(i);
39687                                                                sqlInfos.add(info);
39688                                                        }
39689                                                        queryObject.remove("queries");
39690                                                        SqlInfo info = new SqlInfo();
39691                                                        info.setSql(JSON.toJSONString(queryObject));
39692                                                        info.setFileName(fileName);
39693                                                        info.setFilePath(filePath);
39694                                                        info.setOriginIndex(querys.size());
39695                                                        sqlInfos.add(info);
39696                                                } else {
39697                                                        SqlInfo info = new SqlInfo();
39698                                                        info.setSql(JSON.toJSONString(queryObject));
39699                                                        info.setFileName(fileName);
39700                                                        info.setFilePath(filePath);
39701                                                        info.setOriginIndex(0);
39702                                                        sqlInfos.add(info);
39703                                                }
39704                                        } else if (sql.toLowerCase(Locale.ROOT).indexOf("sqlflow") != -1) {
39705                                                Map sqlflow = (Map) JSON.parseObject(sql);
39706                                                List<Map> servers = (List<Map>) sqlflow.get("servers");
39707                                                if (servers != null) {
39708                                                        for (Map queryObject : servers) {
39709                                                                String name = (String) queryObject.get("name");
39710                                                                String dbVendor = (String) queryObject.get("dbVendor");
39711                                                                List querys = (List) queryObject.get("queries");
39712                                                                if (querys != null) {
39713                                                                        for (int i = 0; i < querys.size(); i++) {
39714                                                                                Map object = (Map) querys.get(i);
39715                                                                                SqlInfo info = new SqlInfo();
39716                                                                                info.setSql(JSON.toJSONString(object));
39717                                                                                info.setFileName(fileName);
39718                                                                                info.setFilePath(filePath);
39719                                                                                info.setOriginIndex(i);
39720                                                                                info.setDbVendor(dbVendor);
39721                                                                                info.setServer(name);
39722                                                                                sqlInfos.add(info);
39723                                                                        }
39724                                                                        queryObject.remove("queries");
39725                                                                        Map serverObject = new IndexedLinkedHashMap();
39726                                                                        serverObject.put("createdBy", sqlflow.get("createdBy"));
39727                                                                        serverObject.put("servers", Arrays.asList(queryObject));
39728                                                                        SqlInfo info = new SqlInfo();
39729                                                                        info.setSql(JSON.toJSONString(serverObject));
39730                                                                        info.setFileName(fileName);
39731                                                                        info.setFilePath(filePath);
39732                                                                        info.setOriginIndex(querys.size());
39733                                                                        info.setDbVendor(dbVendor);
39734                                                                        info.setServer(filePath);
39735                                                                        sqlInfos.add(info);
39736                                                                } else {
39737                                                                        SqlInfo info = new SqlInfo();
39738                                                                        info.setSql(JSON.toJSONString(queryObject));
39739                                                                        info.setFileName(fileName);
39740                                                                        info.setFilePath(filePath);
39741                                                                        info.setOriginIndex(0);
39742                                                                        sqlInfos.add(info);
39743                                                                }
39744                                                        }
39745                                                }
39746                                                
39747                                                List<Map> errorMessages =  (List<Map>) sqlflow.get("errorMessages");
39748                                                if(errorMessages!=null && !errorMessages.isEmpty()) {
39749                                                        for(Map error: errorMessages){
39750                                                                ErrorInfo errorInfo = new ErrorInfo();
39751                                                                errorInfo.setErrorType(ErrorInfo.METADATA_ERROR);
39752                                                                errorInfo.setErrorMessage((String)error.get("errorMessage"));
39753                                                                errorInfo.setFileName(fileName);
39754                                                                errorInfo.setFilePath(filePath);
39755                                                                errorInfo.setStartPosition(new Pair3<Long, Long, String>(-1L, -1L,
39756                                                                                ModelBindingManager.getGlobalHash()));
39757                                                                errorInfo.setEndPosition(new Pair3<Long, Long, String>(-1L, -1L,
39758                                                                                ModelBindingManager.getGlobalHash()));
39759                                                                errorInfo.setOriginStartPosition(new Pair<Long, Long>(-1L, -1L));
39760                                                                errorInfo.setOriginEndPosition(new Pair<Long, Long>(-1L, -1L));
39761                                                                metadataErrors.add(errorInfo);
39762                                                        }
39763                                                }
39764                                        }
39765                                }
39766                        } else if (sql != null) {
39767                                SqlInfo info = new SqlInfo();
39768                                info.setSql(sql);
39769                info.setSourceOriginKind((String)sqlContent.get("sourceOriginKind"));
39770                                info.setFileName(fileName);
39771                                info.setFilePath(filePath);
39772                                info.setOriginIndex(0);
39773                                sqlInfos.add(info);
39774                        } else if (filePath != null) {
39775                                SqlInfo info = new SqlInfo();
39776                                info.setFileName(fileName);
39777                                info.setFilePath(filePath);
39778                                info.setOriginIndex(0);
39779                                sqlInfos.add(info);
39780                        }
39781                }
39782                return sqlInfos;
39783        }
39784
39785        public void setTextFormat(boolean textFormat) {
39786                option.setTextFormat(textFormat);
39787        }
39788
39789        public boolean isBuiltInFunctionName(TObjectName object) {
39790                if (object == null || object.getGsqlparser() == null)
39791                        return false;
39792                try {
39793                        EDbVendor vendor = object.getGsqlparser().getDbVendor();
39794                        if (vendor == EDbVendor.dbvteradata) {
39795                                boolean result = TERADATA_BUILTIN_FUNCTIONS.contains(object.toString().toUpperCase(Locale.ROOT));
39796                                if (result) {
39797                                        return true;
39798                                }
39799                        }
39800
39801                        List<String> versions = functionChecker.getAvailableDbVersions(vendor);
39802                        if (versions != null && versions.size() > 0) {
39803                                for (int i = 0; i < versions.size(); i++) {
39804                                        boolean result = functionChecker.isBuiltInFunction(object.toString(),
39805                                                        object.getGsqlparser().getDbVendor(), versions.get(i));
39806                                        if (result) {
39807                                                return result;
39808                                        }
39809                                }
39810
39811                                // boolean result =
39812                                // TERADATA_BUILTIN_FUNCTIONS.contains(object.toString());
39813                                // if (result) {
39814                                // return true;
39815                                // }
39816                        }
39817                } catch (Exception e) {
39818                }
39819
39820                return false;
39821        }
39822        
39823        public boolean isBuiltInFunctionName(String functionName) {
39824                if (functionName == null)
39825                        return false;
39826                try {
39827                        EDbVendor vendor = getOption().getVendor();
39828                        if (vendor == EDbVendor.dbvteradata) {
39829                                boolean result = TERADATA_BUILTIN_FUNCTIONS.contains(functionName.toUpperCase(Locale.ROOT));
39830                                if (result) {
39831                                        return true;
39832                                }
39833                        }
39834
39835                        List<String> versions = functionChecker.getAvailableDbVersions(vendor);
39836                        if (versions != null && versions.size() > 0) {
39837                                for (int i = 0; i < versions.size(); i++) {
39838                                        // no caller-side folding: isBuiltInFunction folds with
39839                                        // Locale.ROOT itself; a default-locale toUpperCase here
39840                                        // breaks the lookup on Turkish JVMs
39841                                        boolean result = functionChecker.isBuiltInFunction(functionName,
39842                                                        vendor, versions.get(i));
39843                                        if (result) {
39844                                                return result;
39845                                        }
39846                                }
39847
39848                                // boolean result =
39849                                // TERADATA_BUILTIN_FUNCTIONS.contains(object.toString());
39850                                // if (result) {
39851                                // return true;
39852                                // }
39853                        }
39854                } catch (Exception e) {
39855                }
39856
39857                return false;
39858        }
39859
39860        public boolean isKeyword(TObjectName object) {
39861                if (object == null || object.getGsqlparser() == null)
39862                        return false;
39863                try {
39864                        EDbVendor vendor = object.getGsqlparser().getDbVendor();
39865
39866                        List<String> versions = keywordChecker.getAvailableDbVersions(vendor);
39867                        if (versions != null && versions.size() > 0) {
39868                                for (int i = 0; i < versions.size(); i++) {
39869                                        List<String> segments = SQLUtil.parseNames(object.toString());
39870                                        boolean result = keywordChecker.isKeyword(segments.get(segments.size() - 1),
39871                                                        object.getGsqlparser().getDbVendor(), versions.get(i), true);
39872                                        if (result) {
39873                                                return result;
39874                                        }
39875                                }
39876                        }
39877                } catch (Exception e) {
39878                }
39879
39880                return false;
39881        }
39882        
39883        public boolean isKeyword(String objectName) {
39884                if (objectName == null)
39885                        return false;
39886                try {
39887                        EDbVendor vendor = getOption().getVendor();
39888
39889                        List<String> versions = keywordChecker.getAvailableDbVersions(vendor);
39890                        if (versions != null && versions.size() > 0) {
39891                                for (int i = 0; i < versions.size(); i++) {
39892                                        List<String> segments = SQLUtil.parseNames(objectName);
39893                                        boolean result = keywordChecker.isKeyword(segments.get(segments.size() - 1),
39894                                                        vendor, versions.get(i), false);
39895                                        if (result) {
39896                                                return result;
39897                                        }
39898                                }
39899                        }
39900                } catch (Exception e) {
39901                }
39902
39903                return false;
39904        }
39905
39906        public boolean isAggregateFunction(TFunctionCall func) {
39907                if (func == null)
39908                        return false;
39909                String name = builtInFunctionName(func);
39910                return Arrays
39911                                .asList(new String[] { "AVG", "COUNT", "MAX", "MIN", "SUM", "COLLECT", "CORR", "COVAR_POP",
39912                                                "COVAR_SAMP", "CUME_DIST", "DENSE_RANK", "FIRST", "GROUP_ID", "GROUPING", "GROUPING_ID", "LAST",
39913                                                "LISTAGG", "MEDIAN", "PERCENT_RANK", "PERCENTILE_CONT", "PERCENTILE_DISC", "RANK",
39914                                                "STATS_BINOMIAL_TEST", "STATS_CROSSTAB", "STATS_F_TEST", "STATS_KS_TEST", "STATS_MODE",
39915                                                "STATS_MW_TEST", "STATS_ONE_WAY_ANOVA", "STATS_WSR_TEST", "STDDEV", "STDDEV_POP", "STDDEV_SAMP",
39916                                                "SYS_XMLAGG", "VAR_ POP", "VAR_ SAMP", "VARI ANCE", "XMLAGG", "ARRAY_AGG" })
39917                                .contains(name.toUpperCase(Locale.ROOT));
39918        }
39919
39920        /**
39921         * The name a call is compared with built-in function names by: TD_SYSFNLIB.XMLAGG is Teradata's XMLAGG, as the
39922         * system function library names the built-in (Mantis 4921); any other qualified name stays a user function's.
39923         */
39924        private String builtInFunctionName(TFunctionCall func) {
39925                TObjectName functionName = func.getFunctionName();
39926                return isTeradataSystemFunction(functionName) ? functionName.getObjectString() : functionName.toString();
39927        }
39928
39929        private boolean isTeradataSystemFunction(TObjectName functionName) {
39930                return option.getVendor() == EDbVendor.dbvteradata && functionName.getSchemaString() != null
39931                                && functionName.getObjectString() != null
39932                                && DlineageUtil.sameName(ESQLDataObjectType.dotSchema, functionName.getSchemaString(), "TD_SYSFNLIB");
39933        }
39934
39935        public boolean isConstant(TObjectName object) {
39936                if (object == null || object.getGsqlparser() == null)
39937                        return false;
39938                List<String> constants = Arrays.asList(new String[] { "NEXTVAL", "CURRVAL", "SYSDATE", "CENTURY", "YEAR",
39939                                "MONTH", "DAY", "HOUR", "MINUTE", "SECOND" });
39940                List<String> segments = SQLUtil.parseNames(object.toString());
39941                // sequence.NEXTVAL or sequence.CURRVAL is a sequence reference, not a constant
39942                // This syntax is used by Oracle, Snowflake, and accepted by other vendors for compatibility
39943                String columnNameOnly = object.getColumnNameOnly();
39944                if (segments.size() > 1 && ("NEXTVAL".equalsIgnoreCase(columnNameOnly) || "CURRVAL".equalsIgnoreCase(columnNameOnly))) {
39945                        return false;
39946                }
39947                boolean result = constants.indexOf(segments.get(segments.size() - 1).toUpperCase(Locale.ROOT)) != -1;
39948                if (result) {
39949                        return result;
39950                }
39951                if (isKeyword(object)) {
39952                        return true;
39953                }
39954                return false;
39955        }
39956
39957        private Pair3<Long, Long, Integer> convertCoordinate(Pair3<Long, Long, String> position) {
39958//              if (ModelBindingManager.getGlobalOption()!=null && ModelBindingManager.getGlobalOption().isIgnoreCoordinate()) {
39959//                      return new Pair3<>(-1L, -1L, -1);
39960//              }
39961                return new Pair3<Long, Long, Integer>(position.first, position.second,
39962                                ModelBindingManager.getGlobalSqlInfo().getIndexOf(position.third));
39963        }
39964
39965        /**
39966         * Analyze MDX SELECT statement to generate data lineage.
39967         * Maps MDX cube as source table, measures/dimensions as columns,
39968         * and creates dataflow relationships to the result set.
39969         */
39970        private void analyzeMdxSelectStmt(gudusoft.gsqlparser.stmt.mdx.TMdxSelect stmt) {
39971                // Get cube name from FROM clause
39972                gudusoft.gsqlparser.nodes.mdx.TMdxIdentifierNode cube = stmt.getCube();
39973                if (cube == null) {
39974                        return;
39975                }
39976
39977                String cubeName = getMdxIdentifierName(cube);
39978                Table cubeTable = modelFactory.createTableByName(cubeName, false);
39979
39980                // Collect all MDX identifier references from axes and WHERE clause
39981                List<String> measureNames = new ArrayList<String>();
39982                List<String> dimensionNames = new ArrayList<String>();
39983
39984                // Process axes (COLUMNS, ROWS, etc.)
39985                if (stmt.getAxes() != null) {
39986                        for (int i = 0; i < stmt.getAxes().size(); i++) {
39987                                gudusoft.gsqlparser.nodes.mdx.TMdxAxisNode axis = stmt.getAxes().getElement(i);
39988                                if (axis.getExpNode() != null) {
39989                                        collectMdxReferences(axis.getExpNode(), measureNames, dimensionNames);
39990                                }
39991                        }
39992                }
39993
39994                // Process WHERE clause (slicer dimension)
39995                if (stmt.getWhere() != null && stmt.getWhere().getFilter() != null) {
39996                        collectMdxReferences(stmt.getWhere().getFilter(), measureNames, dimensionNames);
39997                }
39998
39999                // Process WITH MEMBER definitions
40000                if (stmt.getWiths() != null) {
40001                        for (int i = 0; i < stmt.getWiths().size(); i++) {
40002                                gudusoft.gsqlparser.nodes.mdx.TMdxWithNode withNode = stmt.getWiths().getElement(i);
40003                                if (withNode.getNameNode() != null) {
40004                                        String withName = getMdxIdentifierName(withNode.getNameNode());
40005                                        // Calculated members are treated as derived measures
40006                                        if (withName.toLowerCase(Locale.ROOT).startsWith("[measures].")
40007                                                        || withName.toLowerCase(Locale.ROOT).startsWith("measures.")) {
40008                                                measureNames.add(withName);
40009                                        }
40010                                }
40011                                // Also collect references used in the WITH expression
40012                                if (withNode.getExprNode() != null) {
40013                                        collectMdxReferences(withNode.getExprNode(), measureNames, dimensionNames);
40014                                }
40015                        }
40016                }
40017
40018                // Create columns on the cube table for all referenced measures and dimensions
40019                Set<String> addedColumns = new LinkedHashSet<String>();
40020                for (String measure : measureNames) {
40021                        if (addedColumns.add(measure)) {
40022                                modelFactory.createTableColumn(cubeTable, measure);
40023                        }
40024                }
40025                for (String dimension : dimensionNames) {
40026                        if (addedColumns.add(dimension)) {
40027                                modelFactory.createTableColumn(cubeTable, dimension);
40028                        }
40029                }
40030
40031                // Create result set with all referenced columns
40032                ResultSet resultSet = modelFactory.createResultSet(stmt, false);
40033                if (resultSet != null) {
40034                        for (String colName : addedColumns) {
40035                                TableColumn sourceCol = findTableColumnByName(cubeTable, colName);
40036                                if (sourceCol != null) {
40037                                        DataFlowRelationship relation = modelFactory.createDataFlowRelation();
40038                                        relation.setEffectType(EffectType.select);
40039                                        relation.addSource(new TableColumnRelationshipElement(sourceCol));
40040                                        relation.setTarget(new RelationRowsRelationshipElement<ResultSetRelationRows>(
40041                                                        resultSet.getRelationRows()));
40042                                }
40043                        }
40044                }
40045        }
40046
40047        private TableColumn findTableColumnByName(Table table, String name) {
40048                for (TableColumn col : table.getColumns()) {
40049                        if (col.getName().equals(name)) {
40050                                return col;
40051                        }
40052                }
40053                return null;
40054        }
40055
40056        /**
40057         * Extract the display name from an MDX identifier node.
40058         * E.g., [Measures].[Departures NEAT] -> [Measures].[Departures NEAT]
40059         */
40060        private String getMdxIdentifierName(gudusoft.gsqlparser.nodes.mdx.TMdxIdentifierNode idNode) {
40061                StringBuilder sb = new StringBuilder();
40062                for (int i = 0; i < idNode.getSegmentList().size(); i++) {
40063                        if (i > 0) sb.append(".");
40064                        gudusoft.gsqlparser.nodes.mdx.IMdxIdentifierSegment seg = idNode.getSegmentList().getElement(i);
40065                        if (seg.getQuoting() == gudusoft.gsqlparser.nodes.mdx.EMdxQuoting.QUOTED) {
40066                                sb.append("[").append(seg.getName()).append("]");
40067                        } else {
40068                                sb.append(seg.getName());
40069                        }
40070                }
40071                return sb.toString();
40072        }
40073
40074        /**
40075         * Recursively collect measure and dimension references from MDX expression tree.
40076         * Uses iterative DFS to avoid StackOverflow on deeply nested expressions.
40077         */
40078        private void collectMdxReferences(gudusoft.gsqlparser.nodes.mdx.TMdxExpNode expr,
40079                        List<String> measures, List<String> dimensions) {
40080                Deque<gudusoft.gsqlparser.nodes.mdx.TMdxExpNode> stack = new ArrayDeque<gudusoft.gsqlparser.nodes.mdx.TMdxExpNode>();
40081                stack.push(expr);
40082
40083                while (!stack.isEmpty()) {
40084                        gudusoft.gsqlparser.nodes.mdx.TMdxExpNode current = stack.pop();
40085                        if (current == null) continue;
40086
40087                        if (current instanceof gudusoft.gsqlparser.nodes.mdx.TMdxIdentifierNode) {
40088                                gudusoft.gsqlparser.nodes.mdx.TMdxIdentifierNode idNode =
40089                                                (gudusoft.gsqlparser.nodes.mdx.TMdxIdentifierNode) current;
40090                                String name = getMdxIdentifierName(idNode);
40091                                if (name.toLowerCase(Locale.ROOT).startsWith("[measures].")
40092                                                || name.toLowerCase(Locale.ROOT).startsWith("measures.")) {
40093                                        measures.add(name);
40094                                } else if (idNode.getSegmentList().size() > 1) {
40095                                        // Multi-segment identifiers that aren't measures are dimensions
40096                                        dimensions.add(name);
40097                                }
40098                        } else if (current instanceof gudusoft.gsqlparser.nodes.mdx.TMdxSetNode) {
40099                                gudusoft.gsqlparser.nodes.mdx.TMdxSetNode setNode =
40100                                                (gudusoft.gsqlparser.nodes.mdx.TMdxSetNode) current;
40101                                if (setNode.getTupleList() != null) {
40102                                        for (int i = 0; i < setNode.getTupleList().size(); i++) {
40103                                                stack.push(setNode.getTupleList().getElement(i));
40104                                        }
40105                                }
40106                        } else if (current instanceof gudusoft.gsqlparser.nodes.mdx.TMdxTupleNode) {
40107                                gudusoft.gsqlparser.nodes.mdx.TMdxTupleNode tupleNode =
40108                                                (gudusoft.gsqlparser.nodes.mdx.TMdxTupleNode) current;
40109                                if (tupleNode.getExprList() != null) {
40110                                        for (int i = 0; i < tupleNode.getExprList().size(); i++) {
40111                                                stack.push(tupleNode.getExprList().getElement(i));
40112                                        }
40113                                }
40114                        } else if (current instanceof gudusoft.gsqlparser.nodes.mdx.TMdxBinOpNode) {
40115                                gudusoft.gsqlparser.nodes.mdx.TMdxBinOpNode binOp =
40116                                                (gudusoft.gsqlparser.nodes.mdx.TMdxBinOpNode) current;
40117                                if (binOp.getRightExprNode() != null) stack.push(binOp.getRightExprNode());
40118                                if (binOp.getLeftExprNode() != null) stack.push(binOp.getLeftExprNode());
40119                        } else if (current instanceof gudusoft.gsqlparser.nodes.mdx.TMdxFunctionNode) {
40120                                gudusoft.gsqlparser.nodes.mdx.TMdxFunctionNode funcNode =
40121                                                (gudusoft.gsqlparser.nodes.mdx.TMdxFunctionNode) current;
40122                                if (funcNode.getArguments() != null) {
40123                                        for (int i = 0; i < funcNode.getArguments().size(); i++) {
40124                                                stack.push(funcNode.getArguments().getElement(i));
40125                                        }
40126                                }
40127                        }
40128                }
40129        }
40130
40131        /**
40132         * Analyze a Power Query M-language document for data lineage.
40133         *
40134         * Delegates to TPowerQueryAnalyzer to extract navigation chains and
40135         * NativeQuery embedded SQL, then feeds the results back through the
40136         * standard SQL analysis pipeline so the dataflow model contains
40137         * regular table/column lineage.
40138         */
40139        private void analyzePowerQueryDocumentStmt(TPowerQueryDocumentStmt pqStmt) {
40140                TPowerQueryAnalyzer pqAnalyzer = new TPowerQueryAnalyzer(pqStmt);
40141
40142                if (option.getPowerQueryInnerVendor() != null) {
40143                        pqAnalyzer.withExplicitInnerVendor(option.getPowerQueryInnerVendor());
40144                }
40145
40146                PowerQueryLineageResult result = pqAnalyzer.analyze();
40147
40148                if (result.isEmpty()) {
40149                        for (String w : result.getWarnings()) {
40150                                logger.warn("Power Query: " + w);
40151                        }
40152                        return;
40153                }
40154
40155                for (PowerQueryLineageResult.NativeQueryRef nq : result.getNativeQueryReferences()) {
40156                        if (nq.innerParser != null && nq.innerParseReturnCode == 0) {
40157                                for (int i = 0; i < nq.innerParser.sqlstatements.size(); i++) {
40158                                        analyzeCustomSqlStmt(nq.innerParser.sqlstatements.get(i));
40159                                }
40160                        }
40161                }
40162
40163                for (PowerQueryLineageResult.NavigationRef nav : result.getNavigationReferences()) {
40164                        if (nav.syntheticSelect != null && nav.resolvedVendor != null) {
40165                                TGSqlParser synParser = createSqlParser(nav.resolvedVendor);
40166                                synParser.sqltext = nav.syntheticSelect;
40167                                int rc = synParser.parse();
40168                                if (rc == 0) {
40169                                        for (int i = 0; i < synParser.sqlstatements.size(); i++) {
40170                                                analyzeCustomSqlStmt(synParser.sqlstatements.get(i));
40171                                        }
40172                                }
40173                        }
40174                }
40175
40176                for (String w : result.getWarnings()) {
40177                        logger.warn("Power Query: " + w);
40178                }
40179        }
40180
40181
40182        /**
40183         * True for an Oracle pseudocolumn that is scoped to one table, i.e. ROWID.
40184         *
40185         * <p>These report {@link EDbObjectType#notAColumn} so that nothing resolves
40186         * them against a catalog that will never contain them, but they still carry
40187         * a real source table: {@code SELECT a.ROWID AS rn FROM t a} genuinely does
40188         * derive {@code rn} from a row of {@code t}. Letting the notAColumn branch
40189         * claim them would route them to constants and delete that edge
40190         * (Mantis #4675).</p>
40191         */
40192        private boolean isTableScopedPseudoColumn(TObjectName object) {
40193                if (object == null) return false;
40194                if (object.getDbObjectType() != EDbObjectType.notAColumn) return false;
40195                if (object.getSourceTable() == null) return false;
40196                return gudusoft.gsqlparser.util.OraclePseudoColumnUtil
40197                                .isTableScopedPseudoColumn(object.getColumnNameOnly());
40198        }
40199
40200}