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}