001package gudusoft.gsqlparser.dlineage.dynamicsql;
002
003import java.util.ArrayList;
004import java.util.Collections;
005import java.util.List;
006
007/**
008 * Result for a single dynamic-SQL EXEC site inside a procedure.
009 *
010 * <p>Either it resolved (status {@link Status#RESOLVED}/{@link Status#PARTIAL}/
011 * {@link Status#CONDITIONAL}) and carries a materialized {@link #getResolvedSql()}
012 * plus the lineage {@link #getEdges()} extracted from analyzing it, or it could not
013 * be reduced (status {@link Status#UNRESOLVED}/{@link Status#PARSE_ERROR}) and carries
014 * an honest {@link #getUnresolvedReason()} so the caller keeps it as a diagnostic
015 * rather than guessing.
016 */
017public final class DynamicSiteResult {
018
019    public enum Kind { EXEC_STRING, SP_EXECUTESQL }
020
021    public enum Status {
022        /** Materialized to a fully concrete string; lineage edges produced. */
023        RESOLVED,
024        /** Materialized, but the string still contains placeholder(s) for unbound vars; some edges resolve. */
025        PARTIAL,
026        /**
027         * The EXEC site materialized to a usable string but is gated by an undecidable conditional
028         * (e.g. {@code IF @Debug = 0 EXEC(@sql)} with {@code @Debug} unbound), so any edge it
029         * produces is reached only <em>if the guarded statement executes</em>. The materialized
030         * string may be fully concrete (all edges emitted) or still PARTIAL (placeholder edges
031         * suppressed, only the resolvable subset emitted). The subset can legitimately be empty when
032         * every relationship touched a placeholder; like {@link #RESOLVED}, this status reflects that
033         * the SQL resolved, not that edges are guaranteed. {@link #getUnresolvedReason()} records the
034         * gate, any placeholder suppression, and whether resolvable edges were produced. Edges, if
035         * any, are correct if the branch runs.
036         */
037        CONDITIONAL,
038        /** Could not be reduced to a usable string (NULL, opaque value, bound exceeded, undecidable control flow). */
039        UNRESOLVED,
040        /** Materialized string failed to re-parse. */
041        PARSE_ERROR
042    }
043
044    private final Kind kind;
045    private final Status status;
046    private final String resolvedSql;
047    private final String concreteSqlHash;
048    private final String sourceProc;
049    private final String dynamicSite;
050    private final String bindingHash;
051    private final String unresolvedReason;
052    private final long startLine;
053    private final long startColumn;
054    private final List<DynamicLineageEdge> edges;
055
056    DynamicSiteResult(Kind kind, Status status, String resolvedSql, String concreteSqlHash, String sourceProc,
057            String dynamicSite, String bindingHash, String unresolvedReason, long startLine, long startColumn,
058            List<DynamicLineageEdge> edges) {
059        this.kind = kind;
060        this.status = status;
061        this.resolvedSql = resolvedSql;
062        this.concreteSqlHash = concreteSqlHash;
063        this.sourceProc = sourceProc;
064        this.dynamicSite = dynamicSite;
065        this.bindingHash = bindingHash;
066        this.unresolvedReason = unresolvedReason;
067        this.startLine = startLine;
068        this.startColumn = startColumn;
069        this.edges = edges == null ? new ArrayList<DynamicLineageEdge>() : edges;
070    }
071
072    public Kind getKind() {
073        return kind;
074    }
075
076    public Status getStatus() {
077        return status;
078    }
079
080    public boolean isResolved() {
081        return status == Status.RESOLVED || status == Status.PARTIAL || status == Status.CONDITIONAL;
082    }
083
084    /** The materialized SQL string (null when unresolved with no usable text). */
085    public String getResolvedSql() {
086        return resolvedSql;
087    }
088
089    public String getConcreteSqlHash() {
090        return concreteSqlHash;
091    }
092
093    public String getSourceProc() {
094        return sourceProc;
095    }
096
097    /** Stable id of this EXEC site (proc + ordinal path + source span), robust to reformatting. */
098    public String getDynamicSite() {
099        return dynamicSite;
100    }
101
102    public String getBindingHash() {
103        return bindingHash;
104    }
105
106    public String getUnresolvedReason() {
107        return unresolvedReason;
108    }
109
110    public long getStartLine() {
111        return startLine;
112    }
113
114    public long getStartColumn() {
115        return startColumn;
116    }
117
118    public List<DynamicLineageEdge> getEdges() {
119        return Collections.unmodifiableList(edges);
120    }
121
122    @Override
123    public String toString() {
124        return "DynamicSiteResult{" + kind + ", " + status + ", site=" + dynamicSite
125                + ", edges=" + edges.size()
126                + (unresolvedReason != null ? ", reason=" + unresolvedReason : "") + "}";
127    }
128}