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}