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    private final JoinEndpointKind kind;
033    // RELATION fields
034    private final String alias;
035    private final String qualifiedName;
036    private final int instanceId;
037    // JOIN_RESULT fields
038    private final int producingJoinOrder;
039    private final List<String> contributingAliases;
040
041    private JoinEndpoint(JoinEndpointKind kind, String alias, String qualifiedName,
042                         int instanceId, int producingJoinOrder,
043                         List<String> contributingAliases) {
044        this.kind = kind;
045        this.alias = alias;
046        this.qualifiedName = qualifiedName;
047        this.instanceId = instanceId;
048        this.producingJoinOrder = producingJoinOrder;
049        this.contributingAliases = contributingAliases == null
050                ? Collections.<String>emptyList()
051                : Collections.unmodifiableList(new ArrayList<String>(contributingAliases));
052    }
053
054    /**
055     * A single FROM-clause relation with no stable instance id. {@code alias}
056     * must be non-empty; {@code qualifiedName} may be {@code null} when the
057     * relation has no resolvable name (e.g. an unaliased derived table).
058     */
059    public static JoinEndpoint relation(String alias, String qualifiedName) {
060        return relation(alias, qualifiedName, NO_INSTANCE_ID);
061    }
062
063    /**
064     * A single FROM-clause relation carrying its stable
065     * {@code instanceId} (slice 179, R4) — the same per-block FROM-order
066     * ordinal as the matching {@code RelationSource.getInstanceId()}, so an
067     * endpoint links to its exact relation instance without alias-keying.
068     */
069    public static JoinEndpoint relation(String alias, String qualifiedName, int instanceId) {
070        if (alias == null || alias.isEmpty()) {
071            throw new IllegalArgumentException("alias must be non-empty");
072        }
073        return new JoinEndpoint(JoinEndpointKind.RELATION, alias, qualifiedName,
074                instanceId, -1, null);
075    }
076
077    /**
078     * The accumulated result of prior joins in a left-deep chain.
079     *
080     * @param producingJoinOrder the {@link JoinEntity#getOrder()} of the
081     *                           join producing this result
082     * @param contributingAliases aliases flowing through this result (may
083     *                            be empty, never null)
084     */
085    public static JoinEndpoint joinResult(int producingJoinOrder, List<String> contributingAliases) {
086        if (producingJoinOrder < 0) {
087            throw new IllegalArgumentException("producingJoinOrder must be >= 0");
088        }
089        return new JoinEndpoint(JoinEndpointKind.JOIN_RESULT, null, null,
090                NO_INSTANCE_ID, producingJoinOrder, contributingAliases);
091    }
092
093    public JoinEndpointKind getKind() {
094        return kind;
095    }
096
097    /** Non-null only for {@link JoinEndpointKind#RELATION}. */
098    public String getAlias() {
099        return alias;
100    }
101
102    /** May be null even for {@link JoinEndpointKind#RELATION}. */
103    public String getQualifiedName() {
104        return qualifiedName;
105    }
106
107    /**
108     * Stable relation instance id (slice 179, R4) for a RELATION endpoint —
109     * matches {@code RelationSource.getInstanceId()}. {@link #NO_INSTANCE_ID}
110     * for JOIN_RESULT endpoints or when not assigned.
111     */
112    public int getInstanceId() {
113        return instanceId;
114    }
115
116    /** Meaningful only for {@link JoinEndpointKind#JOIN_RESULT}; else -1. */
117    public int getProducingJoinOrder() {
118        return producingJoinOrder;
119    }
120
121    /** Never null; empty for {@link JoinEndpointKind#RELATION}. */
122    public List<String> getContributingAliases() {
123        return contributingAliases;
124    }
125
126    @Override
127    public boolean equals(Object o) {
128        if (this == o) return true;
129        if (!(o instanceof JoinEndpoint)) return false;
130        JoinEndpoint that = (JoinEndpoint) o;
131        return kind == that.kind
132                && instanceId == that.instanceId
133                && producingJoinOrder == that.producingJoinOrder
134                && Objects.equals(alias, that.alias)
135                && Objects.equals(qualifiedName, that.qualifiedName)
136                && contributingAliases.equals(that.contributingAliases);
137    }
138
139    @Override
140    public int hashCode() {
141        return Objects.hash(kind, alias, qualifiedName, instanceId,
142                producingJoinOrder, contributingAliases);
143    }
144
145    @Override
146    public String toString() {
147        if (kind == JoinEndpointKind.RELATION) {
148            return "RELATION{" + alias + (qualifiedName != null ? "/" + qualifiedName : "")
149                    + (instanceId != NO_INSTANCE_ID ? "#" + instanceId : "") + "}";
150        }
151        return "JOIN_RESULT{order=" + producingJoinOrder + ", aliases=" + contributingAliases + "}";
152    }
153}