001package gudusoft.gsqlparser.ir.semantic.joinanalysis;
002
003import java.util.ArrayList;
004import java.util.Collections;
005import java.util.List;
006import java.util.Objects;
007
008/**
009 * One input side of a {@link JoinEntity}. A join never references a bare
010 * alias directly; it references a {@code JoinEndpoint} so that the left
011 * side of a left-deep chain ({@code t1 JOIN t2 JOIN t3}) can be modelled
012 * as the accumulated {@link JoinEndpointKind#JOIN_RESULT} of the prior
013 * joins rather than a single relation.
014 *
015 * <ul>
016 *   <li>A {@link JoinEndpointKind#RELATION} endpoint carries the
017 *       FROM-clause {@code alias} and the relation's
018 *       {@code qualifiedName} (e.g. {@code t1}).</li>
019 *   <li>A {@link JoinEndpointKind#JOIN_RESULT} endpoint carries the
020 *       {@code producingJoinOrder} (the {@link JoinEntity#getOrder()} of
021 *       the join that produced this result) and the immutable list of
022 *       {@code contributingAliases} flowing through it.</li>
023 * </ul>
024 *
025 * <p>Immutable. Introduced by join-analysis slice 162 (S1).
026 */
027public final class JoinEndpoint {
028
029    /** Sentinel for "no stable relation instance id" (slice 179). */
030    public static final int NO_INSTANCE_ID = -1;
031
032    /** Sentinel for "no subquery statement index" (R8). */
033    public static final int NO_STATEMENT_INDEX = -1;
034
035    private final JoinEndpointKind kind;
036    // RELATION fields
037    private final String alias;
038    private final String qualifiedName;
039    private final int instanceId;
040    // JOIN_RESULT fields
041    private final int producingJoinOrder;
042    private final List<String> contributingAliases;
043    // SUBQUERY field (R8): index of the lifted subquery's StatementGraph.
044    private final int statementIndex;
045
046    private JoinEndpoint(JoinEndpointKind kind, String alias, String qualifiedName,
047                         int instanceId, int producingJoinOrder,
048                         List<String> contributingAliases, int statementIndex) {
049        this.kind = kind;
050        this.alias = alias;
051        this.qualifiedName = qualifiedName;
052        this.instanceId = instanceId;
053        this.producingJoinOrder = producingJoinOrder;
054        this.contributingAliases = contributingAliases == null
055                ? Collections.<String>emptyList()
056                : Collections.unmodifiableList(new ArrayList<String>(contributingAliases));
057        this.statementIndex = statementIndex;
058    }
059
060    /**
061     * A single FROM-clause relation with no stable instance id. {@code alias}
062     * must be non-empty; {@code qualifiedName} may be {@code null} when the
063     * relation has no resolvable name (e.g. an unaliased derived table).
064     */
065    public static JoinEndpoint relation(String alias, String qualifiedName) {
066        return relation(alias, qualifiedName, NO_INSTANCE_ID);
067    }
068
069    /**
070     * A single FROM-clause relation carrying its stable
071     * {@code instanceId} (slice 179, R4) — the same per-block FROM-order
072     * ordinal as the matching {@code RelationSource.getInstanceId()}, so an
073     * endpoint links to its exact relation instance without alias-keying.
074     */
075    public static JoinEndpoint relation(String alias, String qualifiedName, int instanceId) {
076        if (alias == null || alias.isEmpty()) {
077            throw new IllegalArgumentException("alias must be non-empty");
078        }
079        return new JoinEndpoint(JoinEndpointKind.RELATION, alias, qualifiedName,
080                instanceId, -1, null, NO_STATEMENT_INDEX);
081    }
082
083    /**
084     * The right side of a predicate-derived semi-join (R8): a lifted
085     * {@code EXISTS} / {@code IN} subquery. {@code statementIndex} is the
086     * index of the subquery's own {@link gudusoft.gsqlparser.ir.semantic.StatementGraph}
087     * block in the program; {@code label} is an optional human-facing name
088     * (the synthetic {@code <predicate_subquery_N>} name or the subquery's
089     * alias), may be {@code null}.
090     */
091    public static JoinEndpoint subquery(int statementIndex, String label) {
092        if (statementIndex < 0) {
093            throw new IllegalArgumentException("statementIndex must be >= 0");
094        }
095        return new JoinEndpoint(JoinEndpointKind.SUBQUERY, label, null,
096                NO_INSTANCE_ID, -1, null, statementIndex);
097    }
098
099    /**
100     * The accumulated result of prior joins in a left-deep chain.
101     *
102     * @param producingJoinOrder the {@link JoinEntity#getOrder()} of the
103     *                           join producing this result
104     * @param contributingAliases aliases flowing through this result (may
105     *                            be empty, never null)
106     */
107    public static JoinEndpoint joinResult(int producingJoinOrder, List<String> contributingAliases) {
108        if (producingJoinOrder < 0) {
109            throw new IllegalArgumentException("producingJoinOrder must be >= 0");
110        }
111        return new JoinEndpoint(JoinEndpointKind.JOIN_RESULT, null, null,
112                NO_INSTANCE_ID, producingJoinOrder, contributingAliases, NO_STATEMENT_INDEX);
113    }
114
115    public JoinEndpointKind getKind() {
116        return kind;
117    }
118
119    /** Non-null only for {@link JoinEndpointKind#RELATION}. */
120    public String getAlias() {
121        return alias;
122    }
123
124    /** May be null even for {@link JoinEndpointKind#RELATION}. */
125    public String getQualifiedName() {
126        return qualifiedName;
127    }
128
129    /**
130     * Stable relation instance id (slice 179, R4) for a RELATION endpoint —
131     * matches {@code RelationSource.getInstanceId()}. {@link #NO_INSTANCE_ID}
132     * for JOIN_RESULT endpoints or when not assigned.
133     */
134    public int getInstanceId() {
135        return instanceId;
136    }
137
138    /** Meaningful only for {@link JoinEndpointKind#JOIN_RESULT}; else -1. */
139    public int getProducingJoinOrder() {
140        return producingJoinOrder;
141    }
142
143    /** Never null; empty for {@link JoinEndpointKind#RELATION}. */
144    public List<String> getContributingAliases() {
145        return contributingAliases;
146    }
147
148    /**
149     * Index of the lifted subquery's {@code StatementGraph} for a
150     * {@link JoinEndpointKind#SUBQUERY} endpoint (R8); {@link #NO_STATEMENT_INDEX}
151     * for other endpoint kinds. For a SUBQUERY endpoint {@link #getAlias()}
152     * returns the optional label (synthetic name or alias, may be null).
153     */
154    public int getStatementIndex() {
155        return statementIndex;
156    }
157
158    @Override
159    public boolean equals(Object o) {
160        if (this == o) return true;
161        if (!(o instanceof JoinEndpoint)) return false;
162        JoinEndpoint that = (JoinEndpoint) o;
163        return kind == that.kind
164                && instanceId == that.instanceId
165                && producingJoinOrder == that.producingJoinOrder
166                && statementIndex == that.statementIndex
167                && Objects.equals(alias, that.alias)
168                && Objects.equals(qualifiedName, that.qualifiedName)
169                && contributingAliases.equals(that.contributingAliases);
170    }
171
172    @Override
173    public int hashCode() {
174        return Objects.hash(kind, alias, qualifiedName, instanceId,
175                producingJoinOrder, contributingAliases, statementIndex);
176    }
177
178    @Override
179    public String toString() {
180        if (kind == JoinEndpointKind.RELATION) {
181            return "RELATION{" + alias + (qualifiedName != null ? "/" + qualifiedName : "")
182                    + (instanceId != NO_INSTANCE_ID ? "#" + instanceId : "") + "}";
183        }
184        if (kind == JoinEndpointKind.SUBQUERY) {
185            return "SUBQUERY{stmt=" + statementIndex
186                    + (alias != null ? ", label=" + alias : "") + "}";
187        }
188        return "JOIN_RESULT{order=" + producingJoinOrder + ", aliases=" + contributingAliases + "}";
189    }
190}