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