001package gudusoft.gsqlparser.ir.semantic.joinanalysis;
002
003import java.util.Objects;
004
005/**
006 * Resolution of a {@code ColumnRef} to its base relation / final table
007 * (GAP 5). Pairs the resolved qualified name with an explicit
008 * {@link ColumnResolutionStatus} so an unresolved column is surfaced
009 * honestly rather than as a silent empty.
010 *
011 * <p>Invariants:
012 * <ul>
013 *   <li>{@link ColumnResolutionStatus#RESOLVED} ⇒
014 *       {@code resolvedTableQualifiedName != null}.</li>
015 *   <li>{@link ColumnResolutionStatus#UNRESOLVED} /
016 *       {@link ColumnResolutionStatus#NOT_APPLICABLE} ⇒
017 *       {@code resolvedTableQualifiedName == null}; a final table is
018 *       <em>never</em> fabricated.</li>
019 * </ul>
020 *
021 * <p>Immutable. Introduced by join-analysis slice 162 (S1); populated
022 * from {@code ColumnBinding} in slice 165 (S4).
023 */
024public final class ColumnResolution {
025
026    /** Shared singleton for the unresolved case. */
027    public static final ColumnResolution UNRESOLVED =
028            new ColumnResolution(null, ColumnResolutionStatus.UNRESOLVED);
029
030    /** Shared singleton for the not-applicable case. */
031    public static final ColumnResolution NOT_APPLICABLE =
032            new ColumnResolution(null, ColumnResolutionStatus.NOT_APPLICABLE);
033
034    private final String resolvedTableQualifiedName;
035    private final ColumnResolutionStatus status;
036
037    public ColumnResolution(String resolvedTableQualifiedName, ColumnResolutionStatus status) {
038        if (status == null) {
039            throw new IllegalArgumentException("status must be non-null");
040        }
041        if (status == ColumnResolutionStatus.RESOLVED
042                && (resolvedTableQualifiedName == null || resolvedTableQualifiedName.isEmpty())) {
043            throw new IllegalArgumentException(
044                    "RESOLVED requires a non-empty resolvedTableQualifiedName");
045        }
046        if (status != ColumnResolutionStatus.RESOLVED && resolvedTableQualifiedName != null) {
047            throw new IllegalArgumentException(
048                    "resolvedTableQualifiedName must be null unless RESOLVED");
049        }
050        this.resolvedTableQualifiedName = resolvedTableQualifiedName;
051        this.status = status;
052    }
053
054    /** Convenience: build a RESOLVED resolution. */
055    public static ColumnResolution resolved(String resolvedTableQualifiedName) {
056        return new ColumnResolution(resolvedTableQualifiedName, ColumnResolutionStatus.RESOLVED);
057    }
058
059    /** Null when not {@link ColumnResolutionStatus#RESOLVED}. */
060    public String getResolvedTableQualifiedName() {
061        return resolvedTableQualifiedName;
062    }
063
064    public ColumnResolutionStatus getStatus() {
065        return status;
066    }
067
068    public boolean isResolved() {
069        return status == ColumnResolutionStatus.RESOLVED;
070    }
071
072    @Override
073    public boolean equals(Object o) {
074        if (this == o) return true;
075        if (!(o instanceof ColumnResolution)) return false;
076        ColumnResolution that = (ColumnResolution) o;
077        return status == that.status
078                && Objects.equals(resolvedTableQualifiedName, that.resolvedTableQualifiedName);
079    }
080
081    @Override
082    public int hashCode() {
083        return Objects.hash(resolvedTableQualifiedName, status);
084    }
085
086    @Override
087    public String toString() {
088        return status + (resolvedTableQualifiedName != null
089                ? "(" + resolvedTableQualifiedName + ")" : "");
090    }
091}