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}