001package gudusoft.gsqlparser.dlineage.dynamicsql;
002
003import java.util.ArrayList;
004import java.util.Collections;
005import java.util.LinkedHashMap;
006import java.util.LinkedHashSet;
007import java.util.List;
008import java.util.Map;
009import java.util.Set;
010
011/**
012 * B2 (design §2.2): the intraprocedural symbolic summary of one routine —
013 * boundary→boundary reachability edges over its analyzed relation graph,
014 * plus RAW call sites (deliberately WITHOUT binding maps: the
015 * {@link RoutineCallBinder} populates those in slice B3, and only then are
016 * summaries SCC-ready).
017 *
018 * <p>Base summaries are binding-independent: extraction analyzes ONE
019 * definition's own text in an isolated analyzer instance — isolation blocks
020 * cross-file leakage, and per-definition slicing (see
021 * {@link RoutineSummaryExtractor#extractAll}) blocks same-unit callee
022 * inlining, which a fresh analyzer alone does NOT prevent (§2.2 /
023 * codex-r2 N2 / codex-B2-r1 finding 1).
024 */
025public final class RoutineSummary {
026
027    /** §2.6 summary-local coverage. This verdict does not authorize public
028     *  application; call validity has a separate observation-only gate. */
029    public enum Completeness {
030        /** No known gaps in the summary-local extraction/evaluation pass. */
031        COMPLETE,
032        /** Known summary-local gaps, with reasons attached. */
033        PARTIAL,
034        /** Unanalyzable (parse failure etc.); contributes no candidates. */
035        OPAQUE
036    }
037
038    /** One raw, unresolved call site observed in the body (B3 binds these). */
039    public static final class RawCallSite {
040        private final String rawName;
041        private final int argumentCount;
042
043        public RawCallSite(String rawName, int argumentCount) {
044            this.rawName = rawName == null ? "" : rawName;
045            this.argumentCount = argumentCount;
046        }
047
048        public String getRawName() { return rawName; }
049        public int getArgumentCount() { return argumentCount; }
050
051        @Override
052        public String toString() {
053            return rawName + "/" + argumentCount;
054        }
055    }
056
057    private final RoutineIdentity identity;
058    private final Set<RoutineSummaryEdge> edges;
059    private final List<RawCallSite> calls;
060    private final List<CallSiteRef> callSites;
061    private final Map<String, List<String>> resultSetShapes;
062    private final Completeness completeness;
063    private final List<String> completenessReasons;
064
065    public RoutineSummary(RoutineIdentity identity, Set<RoutineSummaryEdge> edges,
066            List<RawCallSite> calls, Completeness completeness,
067            List<String> completenessReasons) {
068        this(identity, edges, calls, null, null, completeness, completenessReasons);
069    }
070
071    public RoutineSummary(RoutineIdentity identity, Set<RoutineSummaryEdge> edges,
072            List<RawCallSite> calls, List<CallSiteRef> callSites,
073            Map<String, List<String>> resultSetShapes, Completeness completeness,
074            List<String> completenessReasons) {
075        this.identity = identity;
076        this.edges = edges == null
077                ? Collections.<RoutineSummaryEdge>emptySet()
078                : Collections.unmodifiableSet(
079                        new LinkedHashSet<RoutineSummaryEdge>(edges));
080        this.calls = calls == null
081                ? Collections.<RawCallSite>emptyList()
082                : Collections.unmodifiableList(
083                        new ArrayList<RawCallSite>(calls));
084        this.callSites = callSites == null
085                ? Collections.<CallSiteRef>emptyList()
086                : Collections.unmodifiableList(
087                        new ArrayList<CallSiteRef>(callSites));
088        this.resultSetShapes = resultSetShapes == null
089                ? Collections.<String, List<String>>emptyMap()
090                : immutableShapes(resultSetShapes);
091        this.completeness = completeness == null ? Completeness.PARTIAL : completeness;
092        this.completenessReasons = completenessReasons == null
093                ? Collections.<String>emptyList()
094                : Collections.unmodifiableList(
095                        new ArrayList<String>(completenessReasons));
096    }
097
098    private static Map<String, List<String>> immutableShapes(
099            Map<String, List<String>> shapes) {
100        Map<String, List<String>> copy =
101                new LinkedHashMap<String, List<String>>();
102        for (Map.Entry<String, List<String>> entry : shapes.entrySet()) {
103            List<String> columns = entry.getValue() == null
104                    ? Collections.<String>emptyList()
105                    : Collections.unmodifiableList(
106                            new ArrayList<String>(entry.getValue()));
107            copy.put(entry.getKey(), columns);
108        }
109        return Collections.unmodifiableMap(copy);
110    }
111
112    /** B5 evaluator copy: all extraction/binding facts are retained while
113     *  the fixed-point edge set and completeness verdict are replaced. */
114    RoutineSummary evaluated(Set<RoutineSummaryEdge> evaluatedEdges,
115            Completeness evaluatedCompleteness, List<String> reasons) {
116        return new RoutineSummary(identity, evaluatedEdges, calls, callSites,
117                resultSetShapes, evaluatedCompleteness, reasons);
118    }
119
120    /** Null when the definition could not be indexed (OPAQUE summaries). */
121    public RoutineIdentity getIdentity() { return identity; }
122    public Set<RoutineSummaryEdge> getEdges() { return edges; }
123    public List<RawCallSite> getCalls() { return calls; }
124    /** B3: call sites with local actual-binding information (empty pre-B3
125     *  construction paths; same sites as {@link #getCalls()} otherwise). */
126    public List<CallSiteRef> getCallSites() { return callSites; }
127    /** B3: terminal result-set display name → its columns in projection
128     *  order, for positional INSERT…EXEC composition (§2.3 rule 3). */
129    public Map<String, List<String>> getResultSetShapes() { return resultSetShapes; }
130    public Completeness getCompleteness() { return completeness; }
131    public List<String> getCompletenessReasons() { return completenessReasons; }
132}