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}