001package gudusoft.gsqlparser.dlineage.dynamicsql; 002 003import java.util.ArrayList; 004import java.util.Collections; 005import java.util.List; 006 007/** 008 * Top-level result of {@code DataFlowAnalyzer.resolveDynamicSqlLineage}: one 009 * {@link DynamicSiteResult} per dynamic-SQL EXEC site discovered in the 010 * procedure, plus convenience views over the resolved edges and the unresolved 011 * sites. 012 */ 013public final class DynamicLineageResult { 014 015 private final String sourceProc; 016 private final String bindingHash; 017 private final List<DynamicSiteResult> sites = new ArrayList<DynamicSiteResult>(); 018 019 DynamicLineageResult(String sourceProc, String bindingHash) { 020 this.sourceProc = sourceProc; 021 this.bindingHash = bindingHash; 022 } 023 024 void addSite(DynamicSiteResult site) { 025 sites.add(site); 026 } 027 028 public String getSourceProc() { 029 return sourceProc; 030 } 031 032 public String getBindingHash() { 033 return bindingHash; 034 } 035 036 public List<DynamicSiteResult> getSites() { 037 return Collections.unmodifiableList(sites); 038 } 039 040 /** All lineage edges across all resolved sites. */ 041 public List<DynamicLineageEdge> getAllEdges() { 042 List<DynamicLineageEdge> all = new ArrayList<DynamicLineageEdge>(); 043 for (DynamicSiteResult s : sites) { 044 all.addAll(s.getEdges()); 045 } 046 return all; 047 } 048 049 /** Projected (data) lineage edges only — dlineage {@code fdd}, excluding join/filter influence. */ 050 public List<DynamicLineageEdge> getProjectedEdges() { 051 List<DynamicLineageEdge> all = new ArrayList<DynamicLineageEdge>(); 052 for (DynamicSiteResult s : sites) { 053 for (DynamicLineageEdge e : s.getEdges()) { 054 if (e.isProjectedData()) { 055 all.add(e); 056 } 057 } 058 } 059 return all; 060 } 061 062 /** Sites that could not be reduced — keep as honest diagnostics, do not guess. */ 063 public List<DynamicSiteResult> getUnresolvedSites() { 064 List<DynamicSiteResult> out = new ArrayList<DynamicSiteResult>(); 065 for (DynamicSiteResult s : sites) { 066 if (!s.isResolved()) { 067 out.add(s); 068 } 069 } 070 return out; 071 } 072 073 /** 074 * Sites whose trust picture is incomplete: the string still has 075 * unresolved fragments, its producer cannot enumerate provenance, or 076 * SHADOW retained candidates from only the parseable part of a mixed 077 * batch. Unlike {@link #getUnresolvedSites()}, this includes sites that 078 * published historical candidates. 079 */ 080 public List<DynamicSiteResult> getIncompleteSites() { 081 List<DynamicSiteResult> out = new ArrayList<DynamicSiteResult>(); 082 for (DynamicSiteResult site : sites) { 083 if (site.hasUnresolvedFragments() 084 || site.getUnprovenRelationshipCount() > 0) { 085 out.add(site); 086 } 087 } 088 return out; 089 } 090 091 /** 092 * Historical availability check. This intentionally includes PARTIAL 093 * SHADOW/LEGACY candidates for compatibility; it does not assert that the 094 * returned relationships are proven. 095 */ 096 public boolean hasResolvedLineage() { 097 for (DynamicSiteResult s : sites) { 098 if (s.isResolved() && !s.getEdges().isEmpty()) { 099 return true; 100 } 101 } 102 return false; 103 } 104 105 /** Total candidate edges observed on incomplete sites in SHADOW mode. */ 106 public int getUnprovenRelationshipCount() { 107 int count = 0; 108 for (DynamicSiteResult site : sites) { 109 count += site.getUnprovenRelationshipCount(); 110 } 111 return count; 112 } 113 114 @Override 115 public String toString() { 116 return "DynamicLineageResult{proc=" + sourceProc + ", sites=" + sites.size() 117 + ", edges=" + getAllEdges().size() + "}"; 118 } 119}