001package gudusoft.gsqlparser.dlineage.dynamicsql; 002 003import gudusoft.gsqlparser.TCustomSqlStatement; 004import gudusoft.gsqlparser.nodes.TObjectName; 005import gudusoft.gsqlparser.dlineage.dynamicsql.RoutineSummaryEdge.Endpoint; 006 007import java.util.Collections; 008import java.util.List; 009import java.util.Map; 010import java.util.Set; 011 012/** 013 * B3 (design {@code routine-summary-scc-design.md} §2.2/§2.3): one call site 014 * observed inside a summarized routine body, carrying the LOCAL actual-binding 015 * information the {@link RoutineCallBinder} and the SHADOW composition step 016 * need — while staying strictly binding-independent (nothing here references 017 * the callee; resolution happens later against the analysis-wide catalog). 018 * 019 * <p>Two coordinate systems meet here and are deliberately kept apart: 020 * <ul> 021 * <li><b>AST-side</b>: the call statement and its actual argument expressions 022 * from the extractor's parse of the definition slice ({@link Actual}).</li> 023 * <li><b>Model-side</b>: the caller's OWN boundary reach at each actual, 024 * computed from the same closure that produced the summary edges — for an 025 * IN actual, which caller boundary endpoints feed it 026 * ({@link #getInSourcesFdd}/{@link #getInSourcesFdr}); for an OUT actual, 027 * which caller boundary endpoints its value reaches downstream 028 * ({@link #getOutTargetsFdd}/{@link #getOutTargetsFdr}). Locals are 029 * quotiented out of the summary itself, so this per-call-site reach is 030 * exactly the information composition would otherwise lose.</li> 031 * </ul> 032 */ 033public final class CallSiteRef { 034 035 /** One actual argument at the call site (AST-side view). */ 036 public static final class Actual { 037 private final int position; 038 private final String namedFormal; 039 private final boolean outFlagged; 040 private final TObjectName bareReference; 041 private final List<TObjectName> leafReferences; 042 private final String expressionText; 043 private final boolean literal; 044 private final boolean defaultToken; 045 046 public Actual(int position, String namedFormal, boolean outFlagged, 047 TObjectName bareReference, List<TObjectName> leafReferences, 048 String expressionText, boolean literal) { 049 this(position, namedFormal, outFlagged, bareReference, 050 leafReferences, expressionText, literal, false); 051 } 052 053 public Actual(int position, String namedFormal, boolean outFlagged, 054 TObjectName bareReference, List<TObjectName> leafReferences, 055 String expressionText, boolean literal, boolean defaultToken) { 056 this.position = position; 057 this.namedFormal = namedFormal; 058 this.outFlagged = outFlagged; 059 this.bareReference = bareReference; 060 this.leafReferences = leafReferences == null 061 ? Collections.<TObjectName>emptyList() 062 : Collections.unmodifiableList(leafReferences); 063 this.expressionText = expressionText; 064 this.literal = literal; 065 this.defaultToken = defaultToken; 066 } 067 068 /** True for an explicit {@code DEFAULT} actual: the declared default 069 * applies — the formal must HAVE one, and no source binds. */ 070 public boolean isDefaultToken() { return defaultToken; } 071 072 /** 0-based position among the call's actuals (call-site order). */ 073 public int getPosition() { return position; } 074 /** Formal name of a named-notation actual (mssql {@code @p = v}); null when positional. */ 075 public String getNamedFormal() { return namedFormal; } 076 /** True when the call site flags this actual OUT/OUTPUT. */ 077 public boolean isOutFlagged() { return outFlagged; } 078 /** Non-null when the actual is a bare variable/column reference. */ 079 public TObjectName getBareReference() { return bareReference; } 080 /** Leaf object references inside an expression actual (fan-in rule). */ 081 public List<TObjectName> getLeafReferences() { return leafReferences; } 082 /** Expression text when the actual is neither bare nor literal; else null. */ 083 public String getExpressionText() { return expressionText; } 084 /** True when the actual is a compile-time literal (binds no source). */ 085 public boolean isLiteral() { return literal; } 086 } 087 088 private final RoutineSummary.RawCallSite raw; 089 private final TCustomSqlStatement callStatement; 090 private final List<Actual> actuals; 091 /** INSERT…EXEC consumption: positional target table columns; null otherwise. */ 092 private final List<Endpoint> insertTargets; 093 private final Map<Integer, Set<Endpoint>> inSourcesFdd; 094 private final Map<Integer, Set<Endpoint>> inSourcesFdr; 095 private final Map<Integer, Set<Endpoint>> outTargetsFdd; 096 private final Map<Integer, Set<Endpoint>> outTargetsFdr; 097 // --- codex-B3-r1 additions. Package-private mutators, set once by the 098 // extractor right after construction (kept out of the constructor to 099 // avoid a 13-parameter signature); immutable to consumers. --- 100 /** INSERT…EXEC column-list object names (positional); empty otherwise. */ 101 private List<TObjectName> insertColumnNames = Collections.emptyList(); 102 /** True when the consuming INSERT has NO explicit column list — the 103 * binder must report PARTIAL, never guess ordinals (finding 4). */ 104 private boolean insertWithoutColumnList; 105 /** {@code @ret = EXEC p} status capture target; null otherwise. */ 106 private TObjectName returnStatusTarget; 107 /** Actual position → the actual's OWN model node keys (bare actuals): 108 * the intra-caller bridge anchor for call-to-call composition (finding 5). */ 109 private Map<Integer, Set<String>> inNodes = Collections.emptyMap(); 110 /** Actual position → every model node its value reaches, with flavor 111 * bits (1=fdd-only path exists, 2=fdr-bearing path exists). Interior 112 * nodes INCLUDED — this is what bridges A(@v OUTPUT); B(@v). */ 113 private Map<Integer, Map<String, Integer>> outReachNodes = Collections.emptyMap(); 114 115 public CallSiteRef(RoutineSummary.RawCallSite raw, 116 TCustomSqlStatement callStatement, List<Actual> actuals, 117 List<Endpoint> insertTargets, 118 Map<Integer, Set<Endpoint>> inSourcesFdd, 119 Map<Integer, Set<Endpoint>> inSourcesFdr, 120 Map<Integer, Set<Endpoint>> outTargetsFdd, 121 Map<Integer, Set<Endpoint>> outTargetsFdr) { 122 this.raw = raw; 123 this.callStatement = callStatement; 124 this.actuals = actuals == null 125 ? Collections.<Actual>emptyList() 126 : Collections.unmodifiableList(actuals); 127 this.insertTargets = insertTargets == null 128 ? null : Collections.unmodifiableList(insertTargets); 129 this.inSourcesFdd = unmodifiable(inSourcesFdd); 130 this.inSourcesFdr = unmodifiable(inSourcesFdr); 131 this.outTargetsFdd = unmodifiable(outTargetsFdd); 132 this.outTargetsFdr = unmodifiable(outTargetsFdr); 133 } 134 135 private static Map<Integer, Set<Endpoint>> unmodifiable( 136 Map<Integer, Set<Endpoint>> map) { 137 return map == null 138 ? Collections.<Integer, Set<Endpoint>>emptyMap() 139 : Collections.unmodifiableMap(map); 140 } 141 142 public RoutineSummary.RawCallSite getRaw() { return raw; } 143 /** The call statement from the extractor's slice parse (run-scoped AST). */ 144 public TCustomSqlStatement getCallStatement() { return callStatement; } 145 public List<Actual> getActuals() { return actuals; } 146 /** Positional INSERT…EXEC target columns; null when the call site is not 147 * consumed by an INSERT (or the INSERT has no explicit column list — 148 * the binder reports that case as PARTIAL, never guesses ordinals). */ 149 public List<Endpoint> getInsertTargets() { return insertTargets; } 150 /** Actual position → caller boundary endpoints feeding it via fdd-only paths. */ 151 public Map<Integer, Set<Endpoint>> getInSourcesFdd() { return inSourcesFdd; } 152 /** Actual position → caller boundary endpoints feeding it via fdr-bearing paths. */ 153 public Map<Integer, Set<Endpoint>> getInSourcesFdr() { return inSourcesFdr; } 154 /** Actual position → caller boundary endpoints its value reaches via fdd-only paths. */ 155 public Map<Integer, Set<Endpoint>> getOutTargetsFdd() { return outTargetsFdd; } 156 /** Actual position → caller boundary endpoints its value reaches via fdr-bearing paths. */ 157 public Map<Integer, Set<Endpoint>> getOutTargetsFdr() { return outTargetsFdr; } 158 159 public List<TObjectName> getInsertColumnNames() { return insertColumnNames; } 160 public boolean isInsertWithoutColumnList() { return insertWithoutColumnList; } 161 public TObjectName getReturnStatusTarget() { return returnStatusTarget; } 162 public Map<Integer, Set<String>> getInNodes() { return inNodes; } 163 public Map<Integer, Map<String, Integer>> getOutReachNodes() { return outReachNodes; } 164 165 void setInsertColumnNames(List<TObjectName> names) { 166 this.insertColumnNames = names == null 167 ? Collections.<TObjectName>emptyList() 168 : Collections.unmodifiableList(names); 169 } 170 171 void setInsertWithoutColumnList(boolean flag) { 172 this.insertWithoutColumnList = flag; 173 } 174 175 void setReturnStatusTarget(TObjectName target) { 176 this.returnStatusTarget = target; 177 } 178 179 void setInNodes(Map<Integer, Set<String>> nodes) { 180 this.inNodes = nodes == null 181 ? Collections.<Integer, Set<String>>emptyMap() 182 : Collections.unmodifiableMap(nodes); 183 } 184 185 void setOutReachNodes(Map<Integer, Map<String, Integer>> nodes) { 186 this.outReachNodes = nodes == null 187 ? Collections.<Integer, Map<String, Integer>>emptyMap() 188 : Collections.unmodifiableMap(nodes); 189 } 190 191 @Override 192 public String toString() { 193 return raw + "[" + actuals.size() + " actuals]"; 194 } 195}