squonk_ast/ast/window.rs
1// SPDX-License-Identifier: MIT
2// Copyright (c) 2026 Moderately AI Inc.
3
4//! Window-function clauses: the `OVER` specification, frame, and the SELECT-level
5//! `WINDOW` clause.
6//!
7//! These nodes hang off [`FunctionCall::over`](super::FunctionCall) (an inline or
8//! named `OVER` clause) and [`Select::windows`](super::Select) (the named-window
9//! definitions an `OVER name` reference resolves against). Frame *units* and
10//! *exclusions* are closed tag enums (one canonical shape per construct);
11//! the frame *bounds* carry the optional offset expression they bind.
12
13use super::{Expr, Extension, Ident, NoExt, OrderByExpr};
14use crate::vocab::Meta;
15use thin_vec::ThinVec;
16
17/// The `OVER` clause attached to a window function call.
18///
19/// `OVER name` references a definition from the query's `WINDOW` clause;
20/// `OVER ( … )` carries the definition inline. Boxed in the inline case so the
21/// reference case (just an [`Ident`]) stays small.
22#[derive(Clone, Debug, PartialEq, Eq, Hash)]
23#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
24#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
25pub enum WindowSpec<X: Extension = NoExt> {
26 /// `OVER window_name` — a reference to a named [`WINDOW`-clause](NamedWindow)
27 /// definition.
28 Named {
29 /// Name referenced by this syntax.
30 name: Ident,
31 /// Source location and node identity.
32 meta: Meta,
33 },
34 /// An inline `OVER (…)` window specification.
35 Inline {
36 /// The inline `OVER (…)` window definition; see [`WindowDefinition`].
37 definition: Box<WindowDefinition<X>>,
38 /// Source location and node identity.
39 meta: Meta,
40 },
41}
42
43/// A window definition: the body of `OVER ( … )` or of a `WINDOW name AS ( … )`
44/// entry.
45///
46/// Every part is optional. `existing` is the leading base-window name PostgreSQL
47/// allows inside the parens (`OVER (w PARTITION BY …)`), which the definition then
48/// extends. Empty `partition_by`/`order_by` and a `None` frame mean the clause was
49/// not written.
50#[derive(Clone, Debug, PartialEq, Eq, Hash)]
51#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
52#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
53pub struct WindowDefinition<X: Extension = NoExt> {
54 /// A base window name this definition extends, if written first inside the parens.
55 pub existing: Option<Ident>,
56 /// Expressions that partition the input rows.
57 pub partition_by: ThinVec<Expr<X>>,
58 /// Ordering terms in source order.
59 pub order_by: ThinVec<OrderByExpr<X>>,
60 /// The frame clause (`ROWS`/`RANGE`/`GROUPS` …), if written.
61 pub frame: Option<WindowFrame<X>>,
62 /// Source location and node identity.
63 pub meta: Meta,
64}
65
66/// A window frame: `{ ROWS | RANGE | GROUPS } <extent> [ <exclusion> ]`.
67///
68/// `end` is `Some` only for the `BETWEEN <start> AND <end>` spelling; a bare
69/// `<start>` bound leaves it `None`.
70#[derive(Clone, Debug, PartialEq, Eq, Hash)]
71#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
72#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
73pub struct WindowFrame<X: Extension = NoExt> {
74 /// Whether the frame is measured in `ROWS`/`RANGE`/`GROUPS`; see [`WindowFrameUnits`].
75 pub units: WindowFrameUnits,
76 /// The starting frame bound; see [`WindowFrameBound`].
77 pub start: WindowFrameBound<X>,
78 /// Optional end for this syntax.
79 pub end: Option<WindowFrameBound<X>>,
80 /// Optional exclusion for this syntax.
81 pub exclusion: Option<WindowFrameExclusion>,
82 /// Source location and node identity.
83 pub meta: Meta,
84}
85
86#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
87#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
88#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
89/// The SQL window frame units forms represented by the AST.
90pub enum WindowFrameUnits {
91 /// `ROWS` — the frame is counted in physical rows.
92 Rows,
93 /// `RANGE` — the frame spans rows with peer ordering-key values.
94 Range,
95 /// `GROUPS` — the frame is counted in peer groups.
96 Groups,
97}
98
99/// One bound of a window frame.
100///
101/// The offset forms carry their `<expr> PRECEDING`/`FOLLOWING` distance; the
102/// `UNBOUNDED`/`CURRENT ROW` forms carry their keyword span in `Meta`.
103#[derive(Clone, Debug, PartialEq, Eq, Hash)]
104#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
105#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
106pub enum WindowFrameBound<X: Extension = NoExt> {
107 /// `CURRENT ROW` — the bound at the current row.
108 CurrentRow {
109 /// Source location and node identity.
110 meta: Meta,
111 },
112 /// `UNBOUNDED PRECEDING` — the bound at the partition's first row.
113 UnboundedPreceding {
114 /// Source location and node identity.
115 meta: Meta,
116 },
117 /// `UNBOUNDED FOLLOWING` — the bound at the partition's last row.
118 UnboundedFollowing {
119 /// Source location and node identity.
120 meta: Meta,
121 },
122 /// `<offset> PRECEDING` — a bound the given distance before the current row.
123 Preceding {
124 /// Row or frame offset selected by this syntax.
125 offset: Box<Expr<X>>,
126 /// Source location and node identity.
127 meta: Meta,
128 },
129 /// `<offset> FOLLOWING` — a bound the given distance after the current row.
130 Following {
131 /// Row or frame offset selected by this syntax.
132 offset: Box<Expr<X>>,
133 /// Source location and node identity.
134 meta: Meta,
135 },
136}
137
138#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
139#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
140#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
141/// The SQL window frame exclusion forms represented by the AST.
142pub enum WindowFrameExclusion {
143 /// `EXCLUDE CURRENT ROW` — omit the current row from the frame.
144 CurrentRow,
145 /// `EXCLUDE GROUP` — omit the current row and its peers.
146 Group,
147 /// `EXCLUDE TIES` — omit the current row's peers but keep the current row.
148 Ties,
149 /// `EXCLUDE NO OTHERS` — the default; exclude nothing.
150 NoOthers,
151}
152
153#[derive(Clone, Debug, PartialEq, Eq, Hash)]
154#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
155#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
156/// An SQL named window.
157pub struct NamedWindow<X: Extension = NoExt> {
158 /// Name referenced by this syntax.
159 pub name: Ident,
160 /// The window definition bound to the name; see [`WindowDefinition`].
161 pub definition: WindowDefinition<X>,
162 /// Source location and node identity.
163 pub meta: Meta,
164}