001package gudusoft.gsqlparser.ir.semantic.joinanalysis;
002
003import gudusoft.gsqlparser.ir.semantic.SourceSpan;
004
005import java.util.Objects;
006
007/**
008 * Lexical scope metadata for one query block (GAP 4). Records the
009 * block's stable {@code statementIndex} (its position in the flat
010 * {@code SemanticProgram.getStatements()} list), the
011 * {@code parentStatementIndex} of its <em>lexical owner</em> (null for
012 * the main block; CTE <em>reference</em> sites are not parent edges),
013 * the {@link ScopeKind}, an optional name (e.g. a CTE name), and an
014 * optional span.
015 *
016 * <p>Immutable. Introduced by join-analysis slice 162 (S1); populated in
017 * slice 170 (S9).
018 */
019public final class QueryBlockScope {
020
021    private final int statementIndex;
022    private final Integer parentStatementIndex;
023    private final ScopeKind scopeKind;
024    private final String name;
025    private final SourceSpan sourceSpan;
026
027    public QueryBlockScope(int statementIndex, Integer parentStatementIndex,
028                           ScopeKind scopeKind, String name, SourceSpan sourceSpan) {
029        if (statementIndex < 0) {
030            throw new IllegalArgumentException("statementIndex must be >= 0");
031        }
032        if (scopeKind == null) {
033            throw new IllegalArgumentException("scopeKind must be non-null");
034        }
035        if (parentStatementIndex != null && parentStatementIndex < 0) {
036            throw new IllegalArgumentException("parentStatementIndex must be >= 0 when present");
037        }
038        this.statementIndex = statementIndex;
039        this.parentStatementIndex = parentStatementIndex;
040        this.scopeKind = scopeKind;
041        this.name = name;
042        this.sourceSpan = sourceSpan;
043    }
044
045    public int getStatementIndex() {
046        return statementIndex;
047    }
048
049    /** Null for the main (top-level) block. */
050    public Integer getParentStatementIndex() {
051        return parentStatementIndex;
052    }
053
054    public ScopeKind getScopeKind() {
055        return scopeKind;
056    }
057
058    /** Optional; e.g. a CTE name. May be null. */
059    public String getName() {
060        return name;
061    }
062
063    /** Optional; null when the parser cannot anchor the block. */
064    public SourceSpan getSourceSpan() {
065        return sourceSpan;
066    }
067
068    @Override
069    public boolean equals(Object o) {
070        if (this == o) return true;
071        if (!(o instanceof QueryBlockScope)) return false;
072        QueryBlockScope that = (QueryBlockScope) o;
073        return statementIndex == that.statementIndex
074                && Objects.equals(parentStatementIndex, that.parentStatementIndex)
075                && scopeKind == that.scopeKind
076                && Objects.equals(name, that.name)
077                && Objects.equals(sourceSpan, that.sourceSpan);
078    }
079
080    @Override
081    public int hashCode() {
082        return Objects.hash(statementIndex, parentStatementIndex, scopeKind, name, sourceSpan);
083    }
084
085    @Override
086    public String toString() {
087        return "QueryBlockScope{idx=" + statementIndex
088                + ", parent=" + parentStatementIndex
089                + ", " + scopeKind
090                + (name != null ? ", name=" + name : "") + "}";
091    }
092}