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}