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}