001package gudusoft.gsqlparser.ir.semantic.joinanalysis;
002
003import gudusoft.gsqlparser.ir.semantic.SourceSpan;
004
005import java.util.Objects;
006
007/**
008 * A single conjunct extracted from an ON or WHERE condition (GAP 2). A
009 * predicate is the binary shape {@code left <operator> right}, except
010 * for {@link PredicateKind#NULL_CHECK} (which has only a left operand)
011 * and {@link PredicateKind#COMPLEX} (which preserves an undecomposed
012 * sub-tree, typically with a single operand).
013 *
014 * <ul>
015 *   <li>{@code operator} is the verbatim comparison token
016 *       ({@code =}, {@code <>}, {@code >}, {@code IS NULL},
017 *       {@code IS NOT NULL}, {@code BETWEEN}, ...). May be null for a
018 *       {@link PredicateKind#COMPLEX} predicate.</li>
019 *   <li>{@code leftOperand} is non-null.</li>
020 *   <li>{@code rightOperand} is null for {@link PredicateKind#NULL_CHECK}
021 *       and may be null for {@link PredicateKind#COMPLEX}.</li>
022 *   <li>{@code sourceSpan} covers the whole predicate; null when the
023 *       parser cannot anchor it.</li>
024 * </ul>
025 *
026 * <p>Immutable. Introduced by join-analysis slice 162 (S1); produced by
027 * {@code PredicateTreeBuilder} in slice 166 (S5).
028 */
029public final class Predicate {
030
031    private final PredicateKind kind;
032    private final String operator;
033    private final PredicateOperand leftOperand;
034    private final PredicateOperand rightOperand;
035    private final SourceSpan sourceSpan;
036
037    public Predicate(PredicateKind kind, String operator,
038                     PredicateOperand leftOperand, PredicateOperand rightOperand,
039                     SourceSpan sourceSpan) {
040        if (kind == null) {
041            throw new IllegalArgumentException("kind must be non-null");
042        }
043        if (leftOperand == null) {
044            throw new IllegalArgumentException("leftOperand must be non-null");
045        }
046        if (kind == PredicateKind.NULL_CHECK && rightOperand != null) {
047            throw new IllegalArgumentException("NULL_CHECK predicate must not have a right operand");
048        }
049        this.kind = kind;
050        this.operator = operator;
051        this.leftOperand = leftOperand;
052        this.rightOperand = rightOperand;
053        this.sourceSpan = sourceSpan;
054    }
055
056    public PredicateKind getKind() {
057        return kind;
058    }
059
060    /** Verbatim comparison token; may be null for COMPLEX. */
061    public String getOperator() {
062        return operator;
063    }
064
065    public PredicateOperand getLeftOperand() {
066        return leftOperand;
067    }
068
069    /** Null for NULL_CHECK; may be null for COMPLEX. */
070    public PredicateOperand getRightOperand() {
071        return rightOperand;
072    }
073
074    /** Optional; null when the parser cannot anchor this predicate. */
075    public SourceSpan getSourceSpan() {
076        return sourceSpan;
077    }
078
079    @Override
080    public boolean equals(Object o) {
081        if (this == o) return true;
082        if (!(o instanceof Predicate)) return false;
083        Predicate that = (Predicate) o;
084        return kind == that.kind
085                && Objects.equals(operator, that.operator)
086                && Objects.equals(leftOperand, that.leftOperand)
087                && Objects.equals(rightOperand, that.rightOperand)
088                && Objects.equals(sourceSpan, that.sourceSpan);
089    }
090
091    @Override
092    public int hashCode() {
093        return Objects.hash(kind, operator, leftOperand, rightOperand, sourceSpan);
094    }
095
096    @Override
097    public String toString() {
098        StringBuilder sb = new StringBuilder();
099        sb.append('(').append(leftOperand);
100        if (operator != null) sb.append(' ').append(operator);
101        if (rightOperand != null) sb.append(' ').append(rightOperand);
102        sb.append(") ").append(kind);
103        return sb.toString();
104    }
105}