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}