001package gudusoft.gsqlparser.dlineage.dataflow.model;
002
003import gudusoft.gsqlparser.dlineage.dynamicsql.DynamicSqlTrustMode;
004import gudusoft.gsqlparser.dlineage.util.Pair3;
005
006import java.util.ArrayList;
007import java.util.Collections;
008import java.util.List;
009
010/**
011 * A single dynamic-SQL execution site encountered by
012 * {@link gudusoft.gsqlparser.dlineage.DataFlowAnalyzer}, together with whether the analyzer
013 * could statically resolve it. Recording a site never mutates the core lineage model; consumers
014 * may use its proof IDs to project a trustworthy subset of that already-computed model.
015 *
016 * <p>{@link Status} is observation-mode-aware:
017 * <ul>
018 *   <li>{@code RESOLVED} — a complete materialization parsed. In LEGACY mode this also preserves
019 *       the historical classification of some incomplete sites for compatibility.</li>
020 *   <li>{@code PARTIAL} — SHADOW analyzed incomplete text and retained the historical candidate
021 *       relationships; {@link #getUnprovenRelationshipCount()} counts candidates whose
022 *       per-edge proof is not available.</li>
023 *   <li>{@code PARSE_ERROR} — a fully-known dynamic string whose {@code parse()} failed.</li>
024 *   <li>{@code UNRESOLVED} — no usable candidate lineage was produced.</li>
025 * </ul>
026 *
027 * <p>LEGACY's three original states correspond to the internal v2 extraction
028 * states ({@code RESOLVED / AMBIGUOUS / PARSE_ERROR}); {@code PARTIAL} is an
029 * additive SHADOW-only diagnostic state.
030 *
031 * <p>Positions follow the GSP convention: 1-based, end-exclusive, tagged with the per-statement
032 * file hash (same shape as {@link ErrorInfo}).
033 *
034 * <p>For SHADOW consumers that must publish only independently proven
035 * relationships, {@link #getObservedRelationshipIds()} identifies the full
036 * relationship delta attributed to this site and
037 * {@link #getProvenRelationshipIds()} identifies the subset backed by a
038 * complete, hole-free materialization. IDs are scoped to this analyzer result
039 * and correspond to relationship IDs in non-simple {@code dataflow} output.
040 */
041public final class DynamicSqlSite {
042
043        /** The syntactic form of the dynamic-exec site. */
044        public enum Kind {
045                SP_EXECUTESQL,
046                EXEC_STRING,
047                EXECUTE_IMMEDIATE,
048                DBMS_SQL,
049                OTHER
050        }
051
052        /** Completeness/publication state of this dynamic SQL site. */
053        public enum Status {
054                RESOLVED,
055                UNRESOLVED,
056                PARSE_ERROR,
057                /** Incomplete materialization retained for measurement in SHADOW mode. */
058                PARTIAL
059        }
060
061        private final Kind kind;
062        private final Status status;
063        private final String reason;
064        private final String diagnostic;
065        private final boolean executionCertain;
066        private final Pair3<Long, Long, String> startPosition;
067        private final Pair3<Long, Long, String> endPosition;
068        private final int resolvedRelationshipCount;
069        private final int partialRelationshipCount;
070        private final int unprovenRelationshipCount;
071        private final int unresolvedHoleCount;
072        private final boolean holeCountExact;
073        private final DynamicSqlTrustMode trustMode;
074        private final List<Long> observedRelationshipIds;
075        private final List<Long> provenRelationshipIds;
076        private final List<UnresolvedFragment> unresolvedFragments;
077        private final String materializedText;
078
079        public DynamicSqlSite(Kind kind, Status status, String reason,
080                        Pair3<Long, Long, String> startPosition, Pair3<Long, Long, String> endPosition,
081                        int resolvedRelationshipCount, int partialRelationshipCount) {
082                this(kind, status, reason, startPosition, endPosition, resolvedRelationshipCount,
083                                partialRelationshipCount, 0, -1, false, DynamicSqlTrustMode.LEGACY,
084                                Collections.<Long>emptyList(), Collections.<Long>emptyList(), null);
085        }
086
087        public DynamicSqlSite(Kind kind, Status status, String reason,
088                        Pair3<Long, Long, String> startPosition, Pair3<Long, Long, String> endPosition,
089                        int resolvedRelationshipCount, int partialRelationshipCount,
090                        int unprovenRelationshipCount,
091                        int unresolvedHoleCount, boolean holeCountExact, DynamicSqlTrustMode trustMode) {
092                this(kind, status, reason, startPosition, endPosition, resolvedRelationshipCount,
093                                partialRelationshipCount, unprovenRelationshipCount,
094                                unresolvedHoleCount, holeCountExact, trustMode,
095                                Collections.<Long>emptyList(), Collections.<Long>emptyList(), null);
096        }
097
098        public DynamicSqlSite(Kind kind, Status status, String reason,
099                        Pair3<Long, Long, String> startPosition, Pair3<Long, Long, String> endPosition,
100                        int resolvedRelationshipCount, int partialRelationshipCount,
101                        int unprovenRelationshipCount,
102                        int unresolvedHoleCount, boolean holeCountExact, DynamicSqlTrustMode trustMode,
103                        List<Long> observedRelationshipIds, List<Long> provenRelationshipIds) {
104                this(kind, status, reason, startPosition, endPosition,
105                                resolvedRelationshipCount, partialRelationshipCount,
106                                unprovenRelationshipCount, unresolvedHoleCount, holeCountExact,
107                                trustMode, observedRelationshipIds, provenRelationshipIds, null);
108        }
109
110        public DynamicSqlSite(Kind kind, Status status, String reason,
111                        Pair3<Long, Long, String> startPosition, Pair3<Long, Long, String> endPosition,
112                        int resolvedRelationshipCount, int partialRelationshipCount,
113                        int unprovenRelationshipCount,
114                        int unresolvedHoleCount, boolean holeCountExact, DynamicSqlTrustMode trustMode,
115                        List<Long> observedRelationshipIds, List<Long> provenRelationshipIds,
116                        String diagnostic) {
117                this(kind, status, reason, startPosition, endPosition,
118                                resolvedRelationshipCount, partialRelationshipCount,
119                                unprovenRelationshipCount, unresolvedHoleCount, holeCountExact,
120                                trustMode, observedRelationshipIds, provenRelationshipIds, diagnostic,
121                                Collections.<UnresolvedFragment>emptyList(), null);
122        }
123
124        public DynamicSqlSite(Kind kind, Status status, String reason,
125                        Pair3<Long, Long, String> startPosition, Pair3<Long, Long, String> endPosition,
126                        int resolvedRelationshipCount, int partialRelationshipCount,
127                        int unprovenRelationshipCount,
128                        int unresolvedHoleCount, boolean holeCountExact, DynamicSqlTrustMode trustMode,
129                        List<Long> observedRelationshipIds, List<Long> provenRelationshipIds,
130                        String diagnostic, List<UnresolvedFragment> unresolvedFragments,
131                        String materializedText) {
132                this(kind, status, reason, startPosition, endPosition,
133                                resolvedRelationshipCount, partialRelationshipCount,
134                                unprovenRelationshipCount, unresolvedHoleCount, holeCountExact,
135                                trustMode, observedRelationshipIds, provenRelationshipIds, diagnostic,
136                                unresolvedFragments, materializedText, true);
137        }
138
139        public DynamicSqlSite(Kind kind, Status status, String reason,
140                        Pair3<Long, Long, String> startPosition, Pair3<Long, Long, String> endPosition,
141                        int resolvedRelationshipCount, int partialRelationshipCount,
142                        int unprovenRelationshipCount,
143                        int unresolvedHoleCount, boolean holeCountExact, DynamicSqlTrustMode trustMode,
144                        List<Long> observedRelationshipIds, List<Long> provenRelationshipIds,
145                        String diagnostic, List<UnresolvedFragment> unresolvedFragments,
146                        String materializedText, boolean executionCertain) {
147                this.executionCertain = executionCertain;
148                this.kind = kind;
149                this.status = status;
150                this.reason = reason;
151                this.diagnostic = diagnostic;
152                this.startPosition = startPosition;
153                this.endPosition = endPosition;
154                this.resolvedRelationshipCount = resolvedRelationshipCount;
155                this.partialRelationshipCount = partialRelationshipCount;
156                this.unprovenRelationshipCount = unprovenRelationshipCount;
157                this.unresolvedFragments = unresolvedFragments == null || unresolvedFragments.isEmpty()
158                                ? Collections.<UnresolvedFragment>emptyList()
159                                : Collections.unmodifiableList(new ArrayList<UnresolvedFragment>(unresolvedFragments));
160                // A listed fragment set IS the count: the two can never disagree.
161                this.unresolvedHoleCount = this.unresolvedFragments.isEmpty() ? unresolvedHoleCount
162                                : this.unresolvedFragments.size();
163                this.holeCountExact = this.unresolvedFragments.isEmpty() ? holeCountExact : true;
164                this.trustMode = trustMode == null ? DynamicSqlTrustMode.LEGACY : trustMode;
165                this.observedRelationshipIds = immutableIds(observedRelationshipIds);
166                this.provenRelationshipIds = immutableIds(provenRelationshipIds);
167                this.materializedText = materializedText;
168        }
169
170        private static List<Long> immutableIds(List<Long> values) {
171                if (values == null || values.isEmpty()) {
172                        return Collections.emptyList();
173                }
174                return Collections.unmodifiableList(new ArrayList<Long>(values));
175        }
176
177        public Kind getKind() {
178                return kind;
179        }
180
181        public Status getStatus() {
182                return status;
183        }
184
185        /** Human-readable explanation; {@code null} for {@link Status#RESOLVED}. */
186        public String getReason() {
187                return reason;
188        }
189
190        /** Additive analysis note, kept separate from an unresolved reason. */
191        public String getDiagnostic() {
192                return diagnostic;
193        }
194
195        /**
196         * False when the statement may not run on the path that produced this text: it sits in a CATCH block, or
197         * behind a condition or loop whose outcome the analysis cannot decide (the diagnostic says which). The
198         * lineage the site published is what the statement does when it runs.
199         */
200        public boolean isExecutionCertain() {
201                return executionCertain;
202        }
203
204        public Pair3<Long, Long, String> getStartPosition() {
205                return startPosition;
206        }
207
208        public Pair3<Long, Long, String> getEndPosition() {
209                return endPosition;
210        }
211
212        /**
213         * Number of lineage edges this site produced into a <b>real</b> target column (a resolved base-table
214         * column, not a T-SQL variable or placeholder). Intermediate result-set hops are not counted.
215         * {@code > 0} iff the site's dynamic SQL yielded usable lineage.
216         */
217        public int getResolvedRelationshipCount() {
218                return resolvedRelationshipCount;
219        }
220
221        /**
222         * Number of lineage edges this site produced into an <b>unresolved</b> target — a T-SQL variable or
223         * placeholder (e.g. {@code EXEC sp_executesql @sql} folded to {@code INSERT INTO @t SELECT ...}). These
224         * are partial: a source is known but the target object is not.
225         */
226        public int getPartialRelationshipCount() {
227                return partialRelationshipCount;
228        }
229
230        /** Historical candidate relationships SHADOW observed on incomplete text without a per-edge proof. */
231        public int getUnprovenRelationshipCount() {
232                return unprovenRelationshipCount;
233        }
234
235        /**
236         * Number of unresolved splice fragments, or {@code -1} when the producer exposes only a
237         * partial flag. Whenever {@link #getUnresolvedFragments()} is non-empty this IS its size
238         * and {@link #isHoleCountExact()} is true; an exact positive count with an empty list
239         * can only come from a producer that counted without listing (none in GSP itself).
240         */
241        public int getUnresolvedHoleCount() {
242                return unresolvedHoleCount;
243        }
244
245        public boolean isHoleCountExact() {
246                return holeCountExact;
247        }
248
249        /**
250         * The unresolved fragments ("holes") of this site's argument, one per non-literal
251         * fragment of its materialization, in string order: which variable / parameter /
252         * expression the evaluator could not fold, why, where its value would come from, and
253         * the origin span to highlight. Empty (never null) for a fully resolved site and for
254         * a site the evaluator did not see (a dynamic site outside a procedure body, or a
255         * vendor without the abstract evaluator) — check {@link #isHoleCountExact()} to tell
256         * "no holes" from "not known".
257         */
258        public List<UnresolvedFragment> getUnresolvedFragments() {
259                return unresolvedFragments;
260        }
261
262        /**
263         * The argument as the evaluator materialized it, with every hole rendered as its
264         * {@link UnresolvedFragment#getText()} (a compile-time literal argument is its own
265         * text); {@code null} when the evaluator did not see the site or the argument did not
266         * reduce to a string at all - the holes are still listed, but there is no honest
267         * rendering of an unreduced value.
268         */
269        public String getMaterializedText() {
270                return materializedText;
271        }
272
273        public DynamicSqlTrustMode getTrustMode() {
274                return trustMode;
275        }
276
277        /** Relationship IDs attributed to this site, including unproven SHADOW candidates. */
278        public List<Long> getObservedRelationshipIds() {
279                return observedRelationshipIds;
280        }
281
282        /**
283         * Relationship IDs whose analyzed text was complete, parsed, and contained
284         * no unresolved provenance holes or placeholder targets.
285         */
286        public List<Long> getProvenRelationshipIds() {
287                return provenRelationshipIds;
288        }
289
290        /**
291         * True iff this site produced at least one resolved lineage edge (into a real target column).
292         * In SHADOW this may be true for a PARTIAL site because candidate edges remain published for
293         * comparison; check {@link #getStatus()} and {@link #getUnprovenRelationshipCount()} before
294         * treating those edges as proven. A site with only unresolved-target edges or no edge returns false.
295         */
296        public boolean producedLineage() {
297                return resolvedRelationshipCount > 0;
298        }
299}