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}