001package gudusoft.gsqlparser.ir.semantic.joinanalysis;
002
003import gudusoft.gsqlparser.ir.semantic.ColumnRef;
004import gudusoft.gsqlparser.ir.semantic.SourceSpan;
005
006import java.util.Objects;
007
008/**
009 * One operand of a {@link Predicate}. Discriminated by
010 * {@link PredicateOperandKind}:
011 *
012 * <ul>
013 *   <li>{@link PredicateOperandKind#COLUMN} — carries a resolved
014 *       {@link ColumnRef} (whose own resolution/span is populated by
015 *       slices 164/165).</li>
016 *   <li>{@link PredicateOperandKind#LITERAL} /
017 *       {@link PredicateOperandKind#CALL} /
018 *       {@link PredicateOperandKind#COMPLEX} — carry no column; the
019 *       optional verbatim {@code text} (a SQL substring derived from
020 *       {@code sourceSpan}, never reformatted) preserves the shape.</li>
021 * </ul>
022 *
023 * <p>The {@code sourceSpan} is the source of truth for operand text; it
024 * is excluded from neither equality here (these objects live in ordered
025 * lists, not dedupe sets). {@code text}, when present, is the verbatim
026 * substring only.
027 *
028 * <p>Immutable. Introduced by join-analysis slice 162 (S1).
029 */
030public final class PredicateOperand {
031
032    private final PredicateOperandKind kind;
033    private final ColumnRef column;
034    private final String text;
035    private final SourceSpan sourceSpan;
036
037    private PredicateOperand(PredicateOperandKind kind, ColumnRef column,
038                             String text, SourceSpan sourceSpan) {
039        if (kind == null) {
040            throw new IllegalArgumentException("kind must be non-null");
041        }
042        if (kind == PredicateOperandKind.COLUMN && column == null) {
043            throw new IllegalArgumentException("COLUMN operand requires a non-null ColumnRef");
044        }
045        if (kind != PredicateOperandKind.COLUMN && column != null) {
046            throw new IllegalArgumentException("non-COLUMN operand must not carry a ColumnRef");
047        }
048        this.kind = kind;
049        this.column = column;
050        this.text = text;
051        this.sourceSpan = sourceSpan;
052    }
053
054    public static PredicateOperand column(ColumnRef column, SourceSpan span) {
055        return new PredicateOperand(PredicateOperandKind.COLUMN, column, null, span);
056    }
057
058    public static PredicateOperand literal(String text, SourceSpan span) {
059        return new PredicateOperand(PredicateOperandKind.LITERAL, null, text, span);
060    }
061
062    public static PredicateOperand call(String text, SourceSpan span) {
063        return new PredicateOperand(PredicateOperandKind.CALL, null, text, span);
064    }
065
066    public static PredicateOperand complex(String text, SourceSpan span) {
067        return new PredicateOperand(PredicateOperandKind.COMPLEX, null, text, span);
068    }
069
070    public PredicateOperandKind getKind() {
071        return kind;
072    }
073
074    /** Non-null only for {@link PredicateOperandKind#COLUMN}. */
075    public ColumnRef getColumn() {
076        return column;
077    }
078
079    /** Optional verbatim SQL substring; may be null. */
080    public String getText() {
081        return text;
082    }
083
084    /** Optional; null when the parser cannot anchor this operand. */
085    public SourceSpan getSourceSpan() {
086        return sourceSpan;
087    }
088
089    @Override
090    public boolean equals(Object o) {
091        if (this == o) return true;
092        if (!(o instanceof PredicateOperand)) return false;
093        PredicateOperand that = (PredicateOperand) o;
094        return kind == that.kind
095                && Objects.equals(column, that.column)
096                && Objects.equals(text, that.text)
097                && Objects.equals(sourceSpan, that.sourceSpan);
098    }
099
100    @Override
101    public int hashCode() {
102        return Objects.hash(kind, column, text, sourceSpan);
103    }
104
105    @Override
106    public String toString() {
107        switch (kind) {
108            case COLUMN:
109                return column.toString();
110            default:
111                return kind + (text != null ? "(" + text + ")" : "");
112        }
113    }
114}