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     * Return a new provider that records whether the current SELECT body has a
177     * <em>fully-built join graph</em> — i.e. its FROM clause resolved two or
178     * more join endpoints from explicit {@code ON} / {@code CROSS} / comma
179     * predicates. This is the structural anchor that lets the IR builder
180     * degrade an otherwise-fatal {@link
181     * gudusoft.gsqlparser.ir.semantic.DiagnosticCode#COLUMN_BINDING_NON_EXACT}
182     * for an unqualified, catalog-less column to a non-fatal warning: when the
183     * join structure is fully known, the only thing missing without a catalog
184     * is <em>which side</em> the unqualified column belongs to — the same
185     * non-fatal case the {@code JOIN ... USING} merged-key anchor already
186     * tolerates ({@link #getUsingScope()}).
187     *
188     * <p>REPLACE semantics: each {@code buildSelectStatementImpl} invocation
189     * resets this at entry (passing {@code false}) so a parent SELECT's anchor
190     * cannot leak into a recursive nested build, then installs the value for
191     * its own FROM clause after relations are bound. The default returns the
192     * same instance (no-op).
193     */
194    default NameBindingProvider withJoinStructureAnchor(boolean anchored) {
195        return this;
196    }
197
198    /**
199     * Return whether the current SELECT body carries a fully-built join-graph
200     * structural anchor (see {@link #withJoinStructureAnchor(boolean)}).
201     * Default returns {@code false} so providers without this state keep the
202     * strict (fatal) {@code COLUMN_BINDING_NON_EXACT} behavior.
203     */
204    default boolean hasJoinStructureAnchor() {
205        return false;
206    }
207
208    /**
209     * Slice 93 — return a new provider that trusts Phase 1's
210     * {@code linkColumnToTable}-set {@code TObjectName.getSourceTable()}
211     * as an EXACT_MATCH when Phase 2 (TSQLResolver2) left
212     * {@code TObjectName.getResolution()} null. Used for Hive multi-insert
213     * sub-SELECT bodies whose secondary branches are not traversed by
214     * Resolver2 during {@code TGSqlParser.parse()}; without the fallback,
215     * those branches would universally fail with {@code NOT_FOUND}.
216     *
217     * <p>Safety: the fallback fires only when {@code resolution == null}
218     * (proves Phase 2 did not run, NOT that it explicitly rejected) AND
219     * the column's SQL-written qualifier (if any) is consistent with
220     * Phase 1's chosen source table — the implementation verifies the
221     * qualifier matches the source's name or alias case-insensitively
222     * before promoting.
223     *
224     * <p>Default returns the same instance (no-op).
225     */
226    default NameBindingProvider withSourceTableFallback(boolean enabled) {
227        return this;
228    }
229
230    /**
231     * Slice 117 — return a new provider that admits qualified outer-scope
232     * column references as synthetic EXACT_MATCH bindings instead of
233     * letting them surface as {@code NOT_FOUND}. Used by the UPDATE
234     * SET-RHS scalar-subquery extractor so a correlated outer reference
235     * like {@code t.k} (where {@code t} is the UPDATE target or a
236     * FROM-side outer relation, NOT inside the inner SELECT's FROM list)
237     * survives {@code appendMergedOrBoundColumnRef}'s strict
238     * non-EXACT_MATCH reject. The slice-11
239     * {@code promoteCorrelatedRefsToOuterReference} then sees the ref's
240     * alias and synthesises an {@code OUTER_REFERENCE} relation.
241     *
242     * <p>The {@code innerLocalAliasesLower} argument scopes the fallback:
243     * a qualified ref whose qualifier IS in the inner local aliases is
244     * NOT promoted (typos like {@code t.bad_col} where {@code t} is the
245     * inner FROM alias still reject with {@code COLUMN_BINDING_NON_EXACT}
246     * — they are real errors). Unqualified refs are NOT promoted (their
247     * binding remains ambiguous between inner and outer).
248     *
249     * <p>Passing an empty or null set disables the fallback (no-op).
250     *
251     * <p>Default returns the same instance (no-op).
252     */
253    default NameBindingProvider withTolerantOuterBinding(
254            Set<String> innerLocalAliasesLower) {
255        return this;
256    }
257}