001package gudusoft.gsqlparser.ir.semantic;
002
003import java.util.ArrayList;
004import java.util.Collections;
005import java.util.List;
006
007/**
008 * Per-output analytic dependencies for a window-function projection
009 * (slice 13). When an {@link OutputColumn} is built from a SELECT-list
010 * expression of the form {@code FUNC(arg) OVER (PARTITION BY ... ORDER
011 * BY ... ROWS BETWEEN ...)}, the IR records the PARTITION BY / OVER
012 * ORDER BY column refs here; the function args remain in
013 * {@link OutputColumn#getSources()}; the optional frame clause is
014 * captured in {@link #getFrame()} (slice 22).
015 *
016 * <p><b>Refs are in-scope, not necessarily base-table.</b> Each
017 * {@link ColumnRef} is what {@link gudusoft.gsqlparser.ir.semantic.binding.NameBindingProvider}
018 * returned during the build, so a window function in a CTE body may
019 * reference the CTE's local relation alias. Consumers must walk
020 * {@link SemanticProgram#getLineage()} to resolve final base columns.
021 *
022 * <p><b>Empty windows are forbidden by construction.</b> The slice-13
023 * builder rejects {@code OVER ()} (no PARTITION BY AND no OVER ORDER
024 * BY) before reaching this constructor, because the dlineage XML for
025 * an empty-OVER window function is byte-identical to a plain aggregate
026 * (no discriminator → would manufacture an
027 * {@link gudusoft.gsqlparser.ir.semantic.diff.DivergenceClass#AGGREGATION_MISMATCH}
028 * by design). The constructor enforces the same invariant defensively
029 * so a future builder mistake fails loudly here rather than producing
030 * a silently empty {@code WindowSpec}.
031 *
032 * <p><b>Frame substitution is forbidden.</b> A frame clause does NOT
033 * substitute for a non-empty PARTITION BY / OVER ORDER BY. The
034 * empty-OVER guard fires before the frame is built, so a frame-only
035 * {@code OVER (ROWS BETWEEN ...)} is rejected before reaching this
036 * constructor (slice-22 invariant).
037 */
038public final class WindowSpec {
039
040    private final List<ColumnRef> partitionRefs;
041    private final List<ColumnRef> orderRefs;
042    private final WindowFrame frame;
043
044    /**
045     * Slice-13 two-arg constructor. Delegates to the slice-22 three-arg
046     * form with {@code frame = null}; preserved for source compatibility.
047     */
048    public WindowSpec(List<ColumnRef> partitionRefs, List<ColumnRef> orderRefs) {
049        this(partitionRefs, orderRefs, null);
050    }
051
052    /**
053     * Slice-22 three-arg constructor. {@code frame} is nullable; null means
054     * the projection had no {@code ROWS}/{@code RANGE}/{@code GROUPS}
055     * clause inside its {@code OVER (...)}.
056     */
057    public WindowSpec(List<ColumnRef> partitionRefs, List<ColumnRef> orderRefs, WindowFrame frame) {
058        if (partitionRefs == null) {
059            throw new IllegalArgumentException("partitionRefs must not be null");
060        }
061        if (orderRefs == null) {
062            throw new IllegalArgumentException("orderRefs must not be null");
063        }
064        if (partitionRefs.isEmpty() && orderRefs.isEmpty()) {
065            throw new IllegalArgumentException(
066                    "WindowSpec requires at least one PARTITION BY or OVER ORDER BY ref; "
067                            + "empty OVER () is rejected by the builder");
068        }
069        this.partitionRefs = Collections.unmodifiableList(new ArrayList<>(partitionRefs));
070        this.orderRefs = Collections.unmodifiableList(new ArrayList<>(orderRefs));
071        this.frame = frame;
072    }
073
074    public List<ColumnRef> getPartitionRefs() {
075        return partitionRefs;
076    }
077
078    public List<ColumnRef> getOrderRefs() {
079        return orderRefs;
080    }
081
082    public WindowFrame getFrame() {
083        return frame;
084    }
085}