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