001package gudusoft.gsqlparser.ir.semantic.joinanalysis; 002 003import java.util.ArrayList; 004import java.util.Collections; 005import java.util.List; 006import java.util.Objects; 007 008/** 009 * Single optional carrier added to {@code StatementGraph} for the 010 * join-analysis facts (GAPs 1/2/4): the structured {@link JoinGraph}, 011 * the WHERE/filter {@link Predicate} list, and the 012 * {@link QueryBlockScope}. Using one carrier slot (rather than one slot 013 * per GAP) keeps {@code StatementGraph}'s constructor count from growing 014 * further. 015 * 016 * <p>{@link #EMPTY} is the default for every existing 017 * {@code StatementGraph} constructor — an empty join graph, no filter 018 * predicates, and a null scope — so adding this carrier is fully 019 * additive and the legacy flat accessors are unaffected. 020 * 021 * <p>Immutable. Introduced by join-analysis slice 162 (S1); wired into 022 * {@code StatementGraph} in slice 167 (S6). 023 */ 024public final class JoinAnalysisFacts { 025 026 /** Shared empty carrier; the default for legacy constructors. */ 027 public static final JoinAnalysisFacts EMPTY = 028 new JoinAnalysisFacts(JoinGraph.EMPTY, Collections.<Predicate>emptyList(), null); 029 030 private final JoinGraph joinGraph; 031 private final List<Predicate> filterPredicates; 032 private final QueryBlockScope queryBlockScope; 033 034 public JoinAnalysisFacts(JoinGraph joinGraph, 035 List<Predicate> filterPredicates, 036 QueryBlockScope queryBlockScope) { 037 this.joinGraph = joinGraph == null ? JoinGraph.EMPTY : joinGraph; 038 this.filterPredicates = filterPredicates == null 039 ? Collections.<Predicate>emptyList() 040 : Collections.unmodifiableList(new ArrayList<Predicate>(filterPredicates)); 041 this.queryBlockScope = queryBlockScope; 042 } 043 044 /** Never null; {@link JoinGraph#EMPTY} when absent. */ 045 public JoinGraph getJoinGraph() { 046 return joinGraph; 047 } 048 049 /** Never null; empty when the WHERE clause is absent / not modelled. */ 050 public List<Predicate> getFilterPredicates() { 051 return filterPredicates; 052 } 053 054 /** Optional; null until scope is populated (slice 170). */ 055 public QueryBlockScope getQueryBlockScope() { 056 return queryBlockScope; 057 } 058 059 public boolean isEmpty() { 060 return joinGraph.isEmpty() && filterPredicates.isEmpty() && queryBlockScope == null; 061 } 062 063 /** Copy with a replaced join graph. */ 064 public JoinAnalysisFacts withJoinGraph(JoinGraph newGraph) { 065 return new JoinAnalysisFacts(newGraph, filterPredicates, queryBlockScope); 066 } 067 068 /** Copy with a replaced filter-predicate list. */ 069 public JoinAnalysisFacts withFilterPredicates(List<Predicate> newFilters) { 070 return new JoinAnalysisFacts(joinGraph, newFilters, queryBlockScope); 071 } 072 073 /** Copy with a replaced query-block scope. */ 074 public JoinAnalysisFacts withQueryBlockScope(QueryBlockScope newScope) { 075 return new JoinAnalysisFacts(joinGraph, filterPredicates, newScope); 076 } 077 078 @Override 079 public boolean equals(Object o) { 080 if (this == o) return true; 081 if (!(o instanceof JoinAnalysisFacts)) return false; 082 JoinAnalysisFacts that = (JoinAnalysisFacts) o; 083 return joinGraph.equals(that.joinGraph) 084 && filterPredicates.equals(that.filterPredicates) 085 && Objects.equals(queryBlockScope, that.queryBlockScope); 086 } 087 088 @Override 089 public int hashCode() { 090 return Objects.hash(joinGraph, filterPredicates, queryBlockScope); 091 } 092 093 @Override 094 public String toString() { 095 return "JoinAnalysisFacts{joins=" + joinGraph.getJoins().size() 096 + ", filters=" + filterPredicates.size() 097 + ", scope=" + queryBlockScope + "}"; 098 } 099}