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}