001package gudusoft.gsqlparser.ir.semantic.binding;
002
003import gudusoft.gsqlparser.nodes.TObjectName;
004import gudusoft.gsqlparser.nodes.TTable;
005import gudusoft.gsqlparser.stmt.TSelectSqlStatement;
006
007import java.util.Set;
008
009/**
010 * Boundary between the SQL parser/resolver world and the Semantic IR world.
011 *
012 * <p>The Semantic IR builder never reads {@code TObjectName.getResolution()}
013 * directly; it always asks a {@code NameBindingProvider}. This makes it
014 * possible to swap implementations (current: TSQLResolver2; future:
015 * Bound IR; tests: stub).
016 *
017 * <p><b>API status: advanced/preview SPI.</b> Implementing or calling this
018 * interface is outside Join Analysis Consumption Profile v1. Prefer
019 * {@link gudusoft.gsqlparser.ir.semantic.SqlSemanticAnalyzer} unless the
020 * integration deliberately owns the low-level builder pipeline.
021 */
022public interface NameBindingProvider {
023
024    /**
025     * Resolve a FROM-clause relation reference to a {@link RelationBinding}.
026     * Returns {@code null} when the binding is not available (e.g. unresolved).
027     */
028    RelationBinding bindRelation(TTable table);
029
030    /**
031     * Resolve a column-typed {@link TObjectName} to a {@link ColumnBinding}.
032     * Returns {@code null} when the binding is not available; callers must
033     * then decide whether to treat that as a hard error.
034     */
035    ColumnBinding bindColumn(TObjectName columnRef);
036
037    /**
038     * Return a new provider configured for a CTE scope. The default returns
039     * the same instance, which means callers without CTE context behave
040     * unchanged. Implementations that distinguish CTE references from base
041     * tables should override this and return a per-scope copy.
042     *
043     * <p>The set is treated as case-insensitive by convention; callers
044     * should pass already-lowercased CTE names.
045     */
046    default NameBindingProvider withCteContext(Set<String> cteNamesInScope) {
047        return this;
048    }
049
050    /**
051     * Slice 19 (alias-bound PARTITION BY discriminator). True when
052     * {@code columnRef} is an unqualified column reference whose binding
053     * lacks definite FROM-scope evidence AND whose name (case-insensitive)
054     * matches a calculated-expression alias in the directly-enclosing
055     * SELECT's result-column list.
056     *
057     * <p>Used by the IR builder to reject alias-bound PARTITION BY refs
058     * that today bind heuristically to a base table (e.g.
059     * {@code salary*2 AS doubled, PARTITION BY doubled} → resolver
060     * synthesises {@code employees.doubled} via {@code inferred_from_usage}).
061     * Without schema metadata the resolver cannot tell whether the name is
062     * an alias or a real shadowing column; slice 19 chooses conservative
063     * rejection over silent guess.
064     *
065     * <p>The default returns {@code false} so providers without resolver
066     * state fall through transparently. {@code Resolver2NameBindingProvider}
067     * overrides this with the real check (unqualified-only +
068     * {@code !hasDefiniteEvidence()} + AST walk over
069     * {@code enclosingSelect.getResultColumnList()} for a calculated alias
070     * of the same name).
071     *
072     * @param columnRef        the column-typed AST node being inspected
073     * @param enclosingSelect  the SELECT statement whose result columns
074     *                         define the alias scope; the builder already
075     *                         holds this and passes it in (avoids
076     *                         context-dependent walks inside the resolver
077     *                         layer)
078     * @return true to reject this reference as alias-bound; false otherwise
079     */
080    default boolean isCalculatedProjectionAliasFallback(TObjectName columnRef,
081                                                        TSelectSqlStatement enclosingSelect) {
082        return false;
083    }
084
085    /**
086     * Slice 60 — return a new provider scoped with a map of "in-scope
087     * relation alias → published column names" for the current
088     * consuming SELECT. Used by {@code SemanticIRBuilder.tryExpandStar}
089     * to expand {@code SELECT *} / {@code SELECT alias.*} when the
090     * FROM-clause relation binds to a CTE or a FROM-subquery body
091     * already built earlier in the same {@code build()} invocation.
092     *
093     * <p>Semantics are REPLACE, not merge: callers always pass the
094     * complete visible map for the scope. Implementations must
095     * defensively copy and lower-case keys; values should be made
096     * unmodifiable. The default returns the same instance (no-op).
097     *
098     * <p>{@link #withCteContext(Set)} and
099     * {@code withInScopeRelationColumns} are independent facets of the
100     * same per-scope provider context. Implementations must preserve
101     * the other facet's state across each narrower call.
102     */
103    default NameBindingProvider withInScopeRelationColumns(
104            java.util.Map<String, java.util.List<String>> nameToColumns) {
105        return this;
106    }
107
108    /**
109     * Slice 60 — return the in-scope relation column map last set via
110     * {@link #withInScopeRelationColumns}. Default returns an empty
111     * map.
112     */
113    default java.util.Map<String, java.util.List<String>> getInScopeRelationColumns() {
114        return java.util.Collections.emptyMap();
115    }
116
117    /**
118     * Slice 58 — catalog-known column names for {@code table} in catalog
119     * declaration order, or {@code null} when no catalog information is
120     * available for this relation.
121     *
122     * <p>Used by {@code SemanticIRBuilder.tryExpandStar} to expand
123     * {@code SELECT *} and {@code SELECT alias.*} projections into per-
124     * column {@link gudusoft.gsqlparser.ir.semantic.OutputColumn}s, each
125     * carrying a {@link gudusoft.gsqlparser.ir.semantic.ColumnRef} to the
126     * underlying base column.
127     *
128     * <p>Default returns {@code null} so providers without catalog access
129     * fall through transparently and the builder emits a structured
130     * unsupported diagnostic. {@code Resolver2NameBindingProvider}
131     * overrides this when constructed with a non-null {@code TSQLEnv}.
132     *
133     * <p>Implementations must not return an empty list to mean
134     * &quot;catalog known but no columns&quot;; an empty list is treated
135     * identically to {@code null} (no usable catalog) by the builder.
136     *
137     * @param table the FROM-clause relation node being expanded; never
138     *              null in practice (the builder filters out null tables
139     *              before calling)
140     * @return column-name list in declaration order, or null when no
141     *         catalog metadata is available
142     */
143    default java.util.List<String> getRelationColumnNames(TTable table) {
144        return null;
145    }
146
147    /**
148     * Slice 65 — return a new provider scoped with the {@link UsingScope}
149     * for the current SELECT body. Used by collectors and
150     * {@code SemanticIRBuilder.expandBareStarOverUsing} to resolve
151     * unqualified merged-key references to the merged source list.
152     *
153     * <p>Semantics are REPLACE, not merge: each
154     * {@code buildSelectStatementImpl} invocation MUST call this with
155     * {@link UsingScope#EMPTY} at entry so an enclosing SELECT's USING
156     * cannot leak into recursive nested builds (predicate-subquery
157     * bodies, scalar-subquery bodies, set-op branch bodies, CTE bodies,
158     * FROM-subquery bodies all see only their own scope).
159     *
160     * <p>{@link #withCteContext(java.util.Set)},
161     * {@link #withInScopeRelationColumns(java.util.Map)}, and
162     * {@code withUsingScope} are independent facets of the same
163     * per-scope provider context. Implementations must preserve the
164     * other facets' state across each narrower call.
165     *
166     * <p>Default returns the same instance (no-op).
167     */
168    default NameBindingProvider withUsingScope(UsingScope scope) {
169        return this;
170    }
171
172    /**
173     * Slice 65 — return the using scope last set via
174     * {@link #withUsingScope}. Default returns {@link UsingScope#EMPTY}.
175     */
176    default UsingScope getUsingScope() {
177        return UsingScope.EMPTY;
178    }
179
180    /**
181     * Return a new provider that records whether the current SELECT body has a
182     * <em>fully-built join graph</em> — i.e. its FROM clause resolved two or
183     * more join endpoints from explicit {@code ON} / {@code CROSS} / comma
184     * predicates. This is the structural anchor that lets the IR builder
185     * degrade an otherwise-fatal {@link
186     * gudusoft.gsqlparser.ir.semantic.DiagnosticCode#COLUMN_BINDING_NON_EXACT}
187     * for an unqualified, catalog-less column to a non-fatal warning: when the
188     * join structure is fully known, the only thing missing without a catalog
189     * is <em>which side</em> the unqualified column belongs to — the same
190     * non-fatal case the {@code JOIN ... USING} merged-key anchor already
191     * tolerates ({@link #getUsingScope()}).
192     *
193     * <p>REPLACE semantics: each {@code buildSelectStatementImpl} invocation
194     * resets this at entry (passing {@code false}) so a parent SELECT's anchor
195     * cannot leak into a recursive nested build, then installs the value for
196     * its own FROM clause after relations are bound. The default returns the
197     * same instance (no-op).
198     */
199    default NameBindingProvider withJoinStructureAnchor(boolean anchored) {
200        return this;
201    }
202
203    /**
204     * Return whether the current SELECT body carries a fully-built join-graph
205     * structural anchor (see {@link #withJoinStructureAnchor(boolean)}).
206     * Default returns {@code false} so providers without this state keep the
207     * strict (fatal) {@code COLUMN_BINDING_NON_EXACT} behavior.
208     */
209    default boolean hasJoinStructureAnchor() {
210        return false;
211    }
212
213    /**
214     * Slice 93 — return a new provider that trusts Phase 1's
215     * {@code linkColumnToTable}-set {@code TObjectName.getSourceTable()}
216     * as an EXACT_MATCH when Phase 2 (TSQLResolver2) left
217     * {@code TObjectName.getResolution()} null. Used for Hive multi-insert
218     * sub-SELECT bodies whose secondary branches are not traversed by
219     * Resolver2 during {@code TGSqlParser.parse()}; without the fallback,
220     * those branches would universally fail with {@code NOT_FOUND}.
221     *
222     * <p>Safety: the fallback fires only when {@code resolution == null}
223     * (proves Phase 2 did not run, NOT that it explicitly rejected) AND
224     * the column's SQL-written qualifier (if any) is consistent with
225     * Phase 1's chosen source table — the implementation verifies the
226     * qualifier matches the source's name or alias case-insensitively
227     * before promoting.
228     *
229     * <p>Default returns the same instance (no-op).
230     */
231    default NameBindingProvider withSourceTableFallback(boolean enabled) {
232        return this;
233    }
234
235    /**
236     * Slice 117 — return a new provider that admits qualified outer-scope
237     * column references as synthetic EXACT_MATCH bindings instead of
238     * letting them surface as {@code NOT_FOUND}. Used by the UPDATE
239     * SET-RHS scalar-subquery extractor so a correlated outer reference
240     * like {@code t.k} (where {@code t} is the UPDATE target or a
241     * FROM-side outer relation, NOT inside the inner SELECT's FROM list)
242     * survives {@code appendMergedOrBoundColumnRef}'s strict
243     * non-EXACT_MATCH reject. The slice-11
244     * {@code promoteCorrelatedRefsToOuterReference} then sees the ref's
245     * alias and synthesises an {@code OUTER_REFERENCE} relation.
246     *
247     * <p>The {@code innerLocalAliasesLower} argument scopes the fallback:
248     * a qualified ref whose qualifier IS in the inner local aliases is
249     * NOT promoted (typos like {@code t.bad_col} where {@code t} is the
250     * inner FROM alias still reject with {@code COLUMN_BINDING_NON_EXACT}
251     * — they are real errors). Unqualified refs are NOT promoted (their
252     * binding remains ambiguous between inner and outer).
253     *
254     * <p>Passing an empty or null set disables the fallback (no-op).
255     *
256     * <p>Default returns the same instance (no-op).
257     */
258    default NameBindingProvider withTolerantOuterBinding(
259            Set<String> innerLocalAliasesLower) {
260        return this;
261    }
262}