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}