001package gudusoft.gsqlparser.ir.semantic.binding; 002 003import gudusoft.gsqlparser.ir.semantic.RelationKind; 004 005/** 006 * Binding of a FROM-clause relation to a catalog object. Slice 1 always 007 * produces a {@link RelationKind#TABLE} binding whose 008 * {@code qualifiedName} is the bare table name; later slices fill 009 * server/database/schema and add CTE/SUBQUERY kinds. 010 * 011 * <p>Slice 14 added {@link RelationKind#OUTER_REFERENCE} to model an 012 * inner statement's reference to an enclosing-scope alias. Slice 15 013 * extends OUTER_REFERENCE bindings to carry an {@code outerKind} 014 * specifying what the enclosing-scope relation is actually bound to 015 * (TABLE / CTE / SUBQUERY); the projector dispatches through that 016 * resolved kind to BFS the outer body for CTE / SUBQUERY correlations. 017 * 018 * <p>Constructor invariants: 019 * <ul> 020 * <li>The 2-arg constructor rejects {@code kind == OUTER_REFERENCE} — 021 * OUTER_REFERENCE callers MUST use the 3-arg form so the 022 * resolved {@code outerKind} is always declared explicitly.</li> 023 * <li>The 3-arg constructor rejects {@code kind != OUTER_REFERENCE}. 024 * The {@code outerKind} parameter must be one of the allow-list 025 * {@code {TABLE, CTE, SUBQUERY}}; UNION / UNKNOWN / 026 * OUTER_REFERENCE / null all throw.</li> 027 * </ul> 028 */ 029public final class RelationBinding { 030 031 private final RelationKind kind; 032 private final String qualifiedName; 033 private final RelationKind outerKind; 034 035 public RelationBinding(RelationKind kind, String qualifiedName) { 036 if (kind == null) { 037 throw new IllegalArgumentException("kind must not be null"); 038 } 039 if (kind == RelationKind.OUTER_REFERENCE) { 040 throw new IllegalArgumentException( 041 "OUTER_REFERENCE bindings must use the 3-arg constructor " 042 + "with an explicit outerKind"); 043 } 044 if (qualifiedName == null || qualifiedName.isEmpty()) { 045 throw new IllegalArgumentException("qualifiedName must be non-empty"); 046 } 047 this.kind = kind; 048 this.qualifiedName = qualifiedName; 049 this.outerKind = null; 050 } 051 052 public RelationBinding(RelationKind kind, String qualifiedName, 053 RelationKind outerKind) { 054 if (kind == null) { 055 throw new IllegalArgumentException("kind must not be null"); 056 } 057 if (kind != RelationKind.OUTER_REFERENCE) { 058 throw new IllegalArgumentException( 059 "3-arg RelationBinding constructor is for OUTER_REFERENCE only; got " + kind); 060 } 061 if (qualifiedName == null || qualifiedName.isEmpty()) { 062 throw new IllegalArgumentException("qualifiedName must be non-empty"); 063 } 064 if (outerKind != RelationKind.TABLE 065 && outerKind != RelationKind.CTE 066 && outerKind != RelationKind.SUBQUERY) { 067 throw new IllegalArgumentException( 068 "OUTER_REFERENCE outerKind must be TABLE, CTE, or SUBQUERY; got " + outerKind); 069 } 070 this.kind = kind; 071 this.qualifiedName = qualifiedName; 072 this.outerKind = outerKind; 073 } 074 075 public RelationKind getKind() { 076 return kind; 077 } 078 079 public String getQualifiedName() { 080 return qualifiedName; 081 } 082 083 /** 084 * For OUTER_REFERENCE bindings, the underlying kind that the 085 * enclosing-scope relation resolves to (TABLE / CTE / SUBQUERY). 086 * Returns {@code null} for non-OUTER_REFERENCE bindings. 087 */ 088 public RelationKind getOuterKind() { 089 return outerKind; 090 } 091}