001package gudusoft.gsqlparser.dlineage.dynamicsql;
002
003import gudusoft.gsqlparser.TCustomSqlStatement;
004import gudusoft.gsqlparser.nodes.TObjectName;
005
006import java.util.Collections;
007import java.util.List;
008import java.util.Map;
009
010/**
011 * B0 CONTRACT (design {@code docs/designs/sp/routine-summary-scc-design.md}
012 * §2.3): the one canonical call-site binding interface. Implementations
013 * arrive in slice B3 by adapting the existing vendor extractors (P5
014 * literal/EXEC/USE/DBMS_SQL machinery becomes the lower layer); B0 ships the
015 * contract only, so that B2 summary extraction and B3 application code are
016 * written against a stable shape.
017 *
018 * <p>Binding rules the contract encodes (normative, from the design):
019 * <ul>
020 * <li>expression actuals bind as a FAN-IN of all leaf source columns plus a
021 *     transform — never a single-column substitution;</li>
022 * <li>OUT/INOUT/RETURN targets must be validated writable lvalues; an invalid
023 *     OUT actual is diagnosed and causes summary application to quarantine
024 *     the whole non-executable call, never fabricate a partial effect;</li>
025 * <li>literal actuals produce no source (matching the inline engine);</li>
026 * <li>INSERT…EXEC result-set consumption maps positionally, not via
027 *     formals;</li>
028 * <li>an unresolved or ambiguous callee binds NOTHING — ambiguity is
029 *     reported, never guessed through.</li>
030 * </ul>
031 */
032public interface RoutineCallBinder {
033
034    /**
035     * Resolve and bind one call site against the candidate definitions
036     * visible to the analysis.
037     *
038     * @param callSite   the call statement (EXEC / CALL / function reference)
039     * @param callerContext effective caller context (USE database, schema)
040     * @return never null; {@link BindingResult#getStatus()} reports failures
041     */
042    BindingResult bind(TCustomSqlStatement callSite, CallerContext callerContext);
043
044    /** Effective ambient context of the caller at the call site. */
045    final class CallerContext {
046        private final String database;
047        private final String schema;
048
049        public CallerContext(String database, String schema) {
050            this.database = database;
051            this.schema = schema;
052        }
053
054        public String getDatabase() { return database; }
055        public String getSchema() { return schema; }
056    }
057
058    /** Outcome status of a bind attempt. */
059    enum BindingStatus {
060        /** Unique callee resolved; maps populated per the contract. */
061        RESOLVED,
062        /** Multiple compatible definitions — nothing bound, candidates reported. */
063        AMBIGUOUS,
064        /** Callee known only by signature (metadata) — binding maps populated, no body. */
065        EXTERNAL,
066        /** No candidate definition found. */
067        UNRESOLVED
068    }
069
070    /** Completeness of the produced maps for a RESOLVED/EXTERNAL bind. */
071    enum BindingCompleteness {
072        COMPLETE,
073        /** Some arguments could not be bound; reasons in diagnostics. */
074        PARTIAL
075    }
076
077    /** Diagnostic codes surfaced by binding (design §2.1b/§2.3). */
078    enum BindingDiagnosticCode {
079        PROC_CALL_AMBIGUOUS_OVERLOAD,
080        PROC_CALL_UNRESOLVED,
081        PROC_CALL_OUT_TARGET_NOT_WRITABLE,
082        /** Call-site OUTPUT was supplied for a formal not declared OUTPUT. */
083        PROC_CALL_OUTPUT_MODE_MISMATCH,
084        PROC_CALL_ARGUMENT_UNBOUND,
085        PROC_CALL_EXTERNAL_SIGNATURE_ONLY
086    }
087
088    /** One structured diagnostic attached to a bind. */
089    final class BindingDiagnostic {
090        private final BindingDiagnosticCode code;
091        private final String detail;
092
093        public BindingDiagnostic(BindingDiagnosticCode code, String detail) {
094            this.code = code;
095            this.detail = detail == null ? "" : detail;
096        }
097
098        public BindingDiagnosticCode getCode() { return code; }
099        public String getDetail() { return detail; }
100    }
101
102    /**
103     * The leaf sources feeding one IN/INOUT formal: zero or more source
104     * column references (zero for a pure literal actual) plus the actual
105     * expression text when the actual is not a bare column/variable
106     * (the transform carrier of the fan-in rule).
107     */
108    final class ActualSources {
109        private final List<TObjectName> leafSources;
110        private final String expressionText;
111
112        public ActualSources(List<TObjectName> leafSources, String expressionText) {
113            this.leafSources = leafSources == null
114                    ? Collections.<TObjectName>emptyList()
115                    : Collections.unmodifiableList(leafSources);
116            this.expressionText = expressionText;
117        }
118
119        public List<TObjectName> getLeafSources() { return leafSources; }
120        /** Null when the actual is a bare column/variable or literal. */
121        public String getExpressionText() { return expressionText; }
122    }
123
124    /** A validated writable lvalue receiving an OUT/INOUT/RETURN/status flow. */
125    final class WritableTarget {
126        private final TObjectName target;
127
128        public WritableTarget(TObjectName target) {
129            this.target = target;
130        }
131
132        public TObjectName getTarget() { return target; }
133    }
134
135    /**
136     * Positional mapping of a consumed result set (INSERT…EXEC and friends):
137     * ordinal position in the routine's result-set shape → target column.
138     */
139    final class ResultSetMapping {
140        private final List<TObjectName> positionalTargets;
141
142        public ResultSetMapping(List<TObjectName> positionalTargets) {
143            this.positionalTargets = positionalTargets == null
144                    ? Collections.<TObjectName>emptyList()
145                    : Collections.unmodifiableList(positionalTargets);
146        }
147
148        public List<TObjectName> getPositionalTargets() { return positionalTargets; }
149    }
150
151    /**
152     * Canonical result of binding one call site (design §2.3). Formals are
153     * addressed by declaration position (index into the resolved identity's
154     * parameter lists) so the result never depends on name spelling.
155     */
156    final class BindingResult {
157        private final BindingStatus status;
158        private final RoutineIdentity resolved;
159        private final List<RoutineIdentity> candidates;
160        private final Map<Integer, ActualSources> inputs;
161        private final Map<Integer, WritableTarget> outputs;
162        private final WritableTarget returnTarget;
163        private final ResultSetMapping resultSets;
164        private final BindingCompleteness completeness;
165        private final List<BindingDiagnostic> diagnostics;
166
167        public BindingResult(BindingStatus status, RoutineIdentity resolved,
168                List<RoutineIdentity> candidates,
169                Map<Integer, ActualSources> inputs,
170                Map<Integer, WritableTarget> outputs,
171                WritableTarget returnTarget, ResultSetMapping resultSets,
172                BindingCompleteness completeness,
173                List<BindingDiagnostic> diagnostics) {
174            this.status = status;
175            this.resolved = resolved;
176            this.candidates = candidates == null
177                    ? Collections.<RoutineIdentity>emptyList()
178                    : Collections.unmodifiableList(candidates);
179            this.inputs = inputs == null
180                    ? Collections.<Integer, ActualSources>emptyMap()
181                    : Collections.unmodifiableMap(inputs);
182            this.outputs = outputs == null
183                    ? Collections.<Integer, WritableTarget>emptyMap()
184                    : Collections.unmodifiableMap(outputs);
185            this.returnTarget = returnTarget;
186            this.resultSets = resultSets;
187            this.completeness = completeness == null
188                    ? BindingCompleteness.PARTIAL : completeness;
189            this.diagnostics = diagnostics == null
190                    ? Collections.<BindingDiagnostic>emptyList()
191                    : Collections.unmodifiableList(diagnostics);
192        }
193
194        public BindingStatus getStatus() { return status; }
195        /** Non-null only when status is RESOLVED or EXTERNAL. */
196        public RoutineIdentity getResolved() { return resolved; }
197        /** Populated when status is AMBIGUOUS. */
198        public List<RoutineIdentity> getCandidates() { return candidates; }
199        /** Formal position → sources feeding it (IN/INOUT reads). */
200        public Map<Integer, ActualSources> getInputs() { return inputs; }
201        /** Formal position → validated caller target (OUT/INOUT writes). */
202        public Map<Integer, WritableTarget> getOutputs() { return outputs; }
203        /** Null unless a function return / status flows to a caller target. */
204        public WritableTarget getReturnTarget() { return returnTarget; }
205        /** Null unless the call site consumes a result set positionally. */
206        public ResultSetMapping getResultSets() { return resultSets; }
207        public BindingCompleteness getCompleteness() { return completeness; }
208        public List<BindingDiagnostic> getDiagnostics() { return diagnostics; }
209    }
210}