001package gudusoft.gsqlparser.ir.semantic.binding; 002 003import gudusoft.gsqlparser.ETableSource; 004import gudusoft.gsqlparser.TSourceToken; 005import gudusoft.gsqlparser.nodes.TTable; 006 007/** 008 * Slice 74 — synthetic naming for anonymous FROM-clause subqueries. 009 * 010 * <p>Lives in the binding package so both {@link Resolver2NameBindingProvider} 011 * and {@code SemanticIRBuilder} (in the {@code builder} package) can reach it 012 * without a circular dependency. 013 * 014 * <p>The slice-74 decision is to <em>admit</em> FROM subqueries that lack a 015 * written alias, mirroring the parity baseline established by {@code dlineage} 016 * (which already accepts these unconditionally). The synthetic alias is keyed 017 * on the parser's start-token position so it is: 018 * <ul> 019 * <li>deterministic — same SQL produces the same synthetic name across 020 * builds;</li> 021 * <li>unique per source location — two distinct anonymous subqueries 022 * cannot share their opening-paren position, so the names cannot 023 * collide;</li> 024 * <li>not user-writable in normal SQL — the leading and trailing double 025 * underscores combined with the {@code l<n>_c<n>} suffix make 026 * accidental collisions with hand-written identifiers effectively 027 * impossible.</li> 028 * </ul> 029 * 030 * <p>If two anonymous subqueries somehow shared a start token (parser bug), 031 * the existing {@link gudusoft.gsqlparser.ir.semantic.DiagnosticCode#DUPLICATE_FROM_SUBQUERY_ALIAS} 032 * rejecter catches the collision the same way it catches a literal 033 * user-written duplicate alias. 034 */ 035public final class FromSubqueryNaming { 036 037 /** Prefix marker for synthetic alias strings; useful for diagnostics. */ 038 public static final String SYNTH_PREFIX = "__subquery_"; 039 040 private FromSubqueryNaming() { 041 // utility — no instances 042 } 043 044 /** 045 * Compute the synthetic alias for an unaliased FROM-clause subquery 046 * {@code TTable}. The result is keyed on the parser's start-token 047 * line/column. Callers should only invoke this for {@link TTable} 048 * nodes whose {@link TTable#getTableType()} is 049 * {@link ETableSource#subquery} and whose {@link TTable#getAliasName()} 050 * is {@code null} or empty — but the method is defensive about both. 051 * 052 * <p>Returns {@code "__subquery_anonymous__"} as a defensive fallback 053 * when the start token is missing (should not happen for a parsed AST). 054 */ 055 public static String synthAliasFor(TTable t) { 056 if (t == null) return null; 057 TSourceToken st = t.getStartToken(); 058 if (st != null && st.lineNo > 0) { 059 return SYNTH_PREFIX + "l" + st.lineNo + "_c" + st.columnNo + "__"; 060 } 061 return SYNTH_PREFIX + "anonymous__"; 062 } 063 064 /** 065 * Effective alias for any FROM-clause {@link TTable}: the SQL-written 066 * alias if non-empty, else the synthetic alias for unaliased 067 * FROM-subquery TTables, else {@code null}. Callers handling base 068 * tables fall back to {@link TTable#getName()} when this returns 069 * {@code null}. 070 */ 071 public static String effectiveAliasOrNull(TTable t) { 072 if (t == null) return null; 073 String written = t.getAliasName(); 074 if (written != null && !written.isEmpty()) { 075 return written; 076 } 077 if (t.getTableType() == ETableSource.subquery) { 078 return synthAliasFor(t); 079 } 080 return null; 081 } 082}