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,<n>,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) -> dest column, and for the column-copy form the proven 6046 * src_column -> 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 -> 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 -> 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 ''} -> {@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}