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 * 024 * <p><b>API status: supported read-only result.</b> Public getters and 025 * {@link #isEmpty()} are part of Join Analysis Consumption Profile v1. 026 * Constructors and {@code with*} helpers are producer-oriented and are outside 027 * that profile. 028 */ 029public final class JoinAnalysisFacts { 030 031 /** Shared empty carrier; the default for legacy constructors. */ 032 public static final JoinAnalysisFacts EMPTY = 033 new JoinAnalysisFacts(JoinGraph.EMPTY, Collections.<Predicate>emptyList(), null); 034 035 private final JoinGraph joinGraph; 036 private final List<Predicate> filterPredicates; 037 private final QueryBlockScope queryBlockScope; 038 039 public JoinAnalysisFacts(JoinGraph joinGraph, 040 List<Predicate> filterPredicates, 041 QueryBlockScope queryBlockScope) { 042 this.joinGraph = joinGraph == null ? JoinGraph.EMPTY : joinGraph; 043 this.filterPredicates = filterPredicates == null 044 ? Collections.<Predicate>emptyList() 045 : Collections.unmodifiableList(new ArrayList<Predicate>(filterPredicates)); 046 this.queryBlockScope = queryBlockScope; 047 } 048 049 /** Never null; {@link JoinGraph#EMPTY} when absent. */ 050 public JoinGraph getJoinGraph() { 051 return joinGraph; 052 } 053 054 /** Never null; empty when the WHERE clause is absent / not modelled. */ 055 public List<Predicate> getFilterPredicates() { 056 return filterPredicates; 057 } 058 059 /** Optional; null until scope is populated (slice 170). */ 060 public QueryBlockScope getQueryBlockScope() { 061 return queryBlockScope; 062 } 063 064 public boolean isEmpty() { 065 return joinGraph.isEmpty() && filterPredicates.isEmpty() && queryBlockScope == null; 066 } 067 068 /** Copy with a replaced join graph. */ 069 public JoinAnalysisFacts withJoinGraph(JoinGraph newGraph) { 070 return new JoinAnalysisFacts(newGraph, filterPredicates, queryBlockScope); 071 } 072 073 /** Copy with a replaced filter-predicate list. */ 074 public JoinAnalysisFacts withFilterPredicates(List<Predicate> newFilters) { 075 return new JoinAnalysisFacts(joinGraph, newFilters, queryBlockScope); 076 } 077 078 /** Copy with a replaced query-block scope. */ 079 public JoinAnalysisFacts withQueryBlockScope(QueryBlockScope newScope) { 080 return new JoinAnalysisFacts(joinGraph, filterPredicates, newScope); 081 } 082 083 @Override 084 public boolean equals(Object o) { 085 if (this == o) return true; 086 if (!(o instanceof JoinAnalysisFacts)) return false; 087 JoinAnalysisFacts that = (JoinAnalysisFacts) o; 088 return joinGraph.equals(that.joinGraph) 089 && filterPredicates.equals(that.filterPredicates) 090 && Objects.equals(queryBlockScope, that.queryBlockScope); 091 } 092 093 @Override 094 public int hashCode() { 095 return Objects.hash(joinGraph, filterPredicates, queryBlockScope); 096 } 097 098 @Override 099 public String toString() { 100 return "JoinAnalysisFacts{joins=" + joinGraph.getJoins().size() 101 + ", filters=" + filterPredicates.size() 102 + ", scope=" + queryBlockScope + "}"; 103 } 104}