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 * "catalog known but no columns"; 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}