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 * 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}