001package gudusoft.gsqlparser.ir.semantic; 002 003import java.util.ArrayList; 004import java.util.Collections; 005import java.util.List; 006 007/** 008 * One projected column in the SELECT list. {@link #sources} is the list of 009 * input columns this output depends on; {@link #derived} distinguishes a 010 * direct column reference (e.g. {@code SELECT id}) from a computed 011 * expression (e.g. {@code SELECT salary*2 AS doubled} or 012 * {@code SELECT a.x + a.y}); {@link #aggregate} marks the output as the 013 * result of an aggregate function call (e.g. {@code COUNT(*)}, 014 * {@code SUM(salary)}); {@link #windowSpec} (slice 13) carries 015 * PARTITION BY / OVER ORDER BY refs when the projection is a window 016 * function. 017 * 018 * <p>Slice 4 introduced {@code derived}; slice 6 added {@code aggregate} 019 * and lifted the empty-sources restriction for aggregates only (e.g. 020 * {@code COUNT(*)} has no column refs); slice 13 added {@code windowSpec} 021 * and lifts the empty-sources restriction for window functions 022 * (e.g. {@code ROW_NUMBER() OVER (...)} has no column refs). 023 * 024 * <p><b>Note on flag propagation:</b> {@code derived}, {@code aggregate}, 025 * and {@code windowSpec} are local to a single statement's output. They 026 * are <i>not</i> propagated transitively. If a CTE defines 027 * {@code total = SUM(salary)}, that CTE's output has 028 * {@code aggregate=true}; an outer query that re-projects the CTE's 029 * {@code total} column ({@code SELECT total FROM cte}) is a direct 030 * reference and so the outer's output has {@code aggregate=false} (and 031 * {@code derived=false}). Consumers that need a transitive view should 032 * walk {@link gudusoft.gsqlparser.ir.semantic.SemanticProgram#getLineage()} 033 * and inspect each upstream {@code OutputColumn}. 034 * 035 * <p><b>Window-function invariant (slice 13):</b> when {@code windowSpec 036 * != null}, {@code aggregate} MUST be {@code false}. Window functions 037 * are row-preserving (analytic), not row-collapsing, so the per-output 038 * aggregate flag stays false even when the window function is a name 039 * that overlaps with an aggregate (e.g. {@code AVG(salary) OVER (...)}). 040 * The constructor enforces this invariant. 041 */ 042public final class OutputColumn { 043 044 private final String name; 045 private final boolean derived; 046 private final boolean aggregate; 047 private final List<ColumnRef> sources; 048 private final WindowSpec windowSpec; 049 050 /** 051 * Backwards-compatible constructor delegating to the 5-arg form with 052 * {@code windowSpec=null}. Slice 13 added the 5-arg constructor; this 053 * overload preserves source-compatibility for downstream callers that 054 * were already using {@link OutputColumn} from a published version of 055 * the library. 056 */ 057 public OutputColumn(String name, boolean derived, boolean aggregate, 058 List<ColumnRef> sources) { 059 this(name, derived, aggregate, sources, null); 060 } 061 062 public OutputColumn(String name, boolean derived, boolean aggregate, 063 List<ColumnRef> sources, WindowSpec windowSpec) { 064 if (name == null || name.isEmpty()) { 065 throw new IllegalArgumentException("name must be non-empty"); 066 } 067 if (sources == null) { 068 throw new IllegalArgumentException("sources must not be null"); 069 } 070 if (windowSpec != null && aggregate) { 071 throw new IllegalArgumentException( 072 "windowSpec is non-null but aggregate=true; window functions are " 073 + "row-preserving and must carry aggregate=false"); 074 } 075 this.name = name; 076 this.derived = derived; 077 this.aggregate = aggregate; 078 this.sources = Collections.unmodifiableList(new ArrayList<>(sources)); 079 this.windowSpec = windowSpec; 080 } 081 082 public String getName() { 083 return name; 084 } 085 086 /** {@code true} when the projection is an expression rather than a direct column reference. */ 087 public boolean isDerived() { 088 return derived; 089 } 090 091 /** 092 * {@code true} when the projection is an aggregate function call 093 * (COUNT, SUM, AVG, MIN, MAX, etc.). Always implies {@link #isDerived()}. 094 * Always {@code false} when {@link #getWindowSpec()} is non-null 095 * (window functions are row-preserving). 096 */ 097 public boolean isAggregate() { 098 return aggregate; 099 } 100 101 public List<ColumnRef> getSources() { 102 return sources; 103 } 104 105 /** 106 * Per-output analytic dependencies (slice 13). Non-null iff this 107 * projection is a window function (e.g. {@code FUNC(arg) OVER (...)}); 108 * null otherwise. 109 */ 110 public WindowSpec getWindowSpec() { 111 return windowSpec; 112 } 113}