Skip to main content

keelson_sqlite/
frame.rs

1//! Mods for a window frame — which rows around the current one a window function
2//! sees.
3//!
4//! From <https://www.sqlite.org/syntax/frame-spec.html>:
5//!
6//! ```text
7//! { RANGE | ROWS | GROUPS }
8//!   { UNBOUNDED PRECEDING | expr PRECEDING | CURRENT ROW
9//!   | BETWEEN { UNBOUNDED PRECEDING | expr PRECEDING | CURRENT ROW | expr FOLLOWING }
10//!         AND { expr PRECEDING | CURRENT ROW | expr FOLLOWING | UNBOUNDED FOLLOWING } }
11//!   [ EXCLUDE { NO OTHERS | CURRENT ROW | GROUP | TIES } ]
12//! ```
13//!
14//! Read that inner list carefully: `UNBOUNDED FOLLOWING` appears only as a
15//! frame-*end* and `UNBOUNDED PRECEDING` only as a frame-*start*. So there is no
16//! `from_unbounded_following` and no `to_unbounded_preceding` here — PostgreSQL's
17//! grammar lists both in both positions and leaves the server to refuse them,
18//! SQLite's diagram does not, and a construct a dialect's grammar lacks should not
19//! be representable.
20//!
21//! Two of the grammar's defaults are relied on rather than written: the mode
22//! defaults to `RANGE`, and the start bound to `UNBOUNDED PRECEDING`. So a `to_*`
23//! mod on its own gives a complete `BETWEEN UNBOUNDED PRECEDING AND …`, and
24//! `BETWEEN` appears exactly when there is an end bound.
25//!
26//! ```
27//! use keelson_sqlite::{arg, f, frame};
28//!
29//! // count(*) OVER (ROWS BETWEEN ?1 PRECEDING AND CURRENT ROW EXCLUDE TIES)
30//! let e = f("count", "*").over((
31//!     frame::rows(),
32//!     frame::from_preceding(arg(3i32)),
33//!     frame::to_current_row(),
34//!     frame::exclude_ties(),
35//! ));
36//! ```
37
38use keelson_core::clause::{FrameExclusion, FrameMode, HasFrame};
39use keelson_core::expr::{Expr, IntoExpr};
40use keelson_core::{Mod, mod_fn};
41
42fn mode<Q: HasFrame>(mode: FrameMode) -> impl Mod<Q> {
43    mod_fn(move |q: &mut Q| q.frame_mut().set_mode(mode))
44}
45
46fn exclusion<Q: HasFrame>(exclusion: FrameExclusion) -> impl Mod<Q> {
47    mod_fn(move |q: &mut Q| q.frame_mut().set_exclusion(exclusion))
48}
49
50fn start<Q: HasFrame>(bound: Expr) -> impl Mod<Q> {
51    mod_fn(move |q: &mut Q| q.frame_mut().set_start(bound))
52}
53
54fn end<Q: HasFrame>(bound: Expr) -> impl Mod<Q> {
55    mod_fn(move |q: &mut Q| q.frame_mut().set_end(bound))
56}
57
58/// `offset PRECEDING` / `offset FOLLOWING`.
59///
60/// The offset is an expression, so a bound argument works: `?1 PRECEDING` is legal
61/// and is what a paged window needs.
62fn offset_bound(offset: impl IntoExpr, keyword: &'static str) -> Expr {
63    Expr::join((offset, Expr::raw(keyword)))
64}
65
66/// `RANGE` — the offsets are values compared against the `ORDER BY` key. The
67/// grammar's default, so this only ever documents intent.
68pub fn range<Q: HasFrame>() -> impl Mod<Q> {
69    mode(FrameMode::Range)
70}
71
72/// `ROWS` — the offsets are row counts.
73pub fn rows<Q: HasFrame>() -> impl Mod<Q> {
74    mode(FrameMode::Rows)
75}
76
77/// `GROUPS` — the offsets are counts of peer groups. Requires an `ORDER BY` on the
78/// window.
79pub fn groups<Q: HasFrame>() -> impl Mod<Q> {
80    mode(FrameMode::Groups)
81}
82
83/// `UNBOUNDED PRECEDING` as the start bound — the default, written out.
84pub fn from_unbounded_preceding<Q: HasFrame>() -> impl Mod<Q> {
85    start(Expr::raw("UNBOUNDED PRECEDING"))
86}
87
88/// `offset PRECEDING` as the start bound.
89pub fn from_preceding<Q: HasFrame>(offset: impl IntoExpr) -> impl Mod<Q> {
90    start(offset_bound(offset, "PRECEDING"))
91}
92
93/// `CURRENT ROW` as the start bound.
94pub fn from_current_row<Q: HasFrame>() -> impl Mod<Q> {
95    start(Expr::raw("CURRENT ROW"))
96}
97
98/// `offset FOLLOWING` as the start bound. Only legal inside a `BETWEEN`, so pair it
99/// with a `to_*` mod.
100pub fn from_following<Q: HasFrame>(offset: impl IntoExpr) -> impl Mod<Q> {
101    start(offset_bound(offset, "FOLLOWING"))
102}
103
104/// `offset PRECEDING` as the end bound. Turns the frame into a `BETWEEN`.
105pub fn to_preceding<Q: HasFrame>(offset: impl IntoExpr) -> impl Mod<Q> {
106    end(offset_bound(offset, "PRECEDING"))
107}
108
109/// `CURRENT ROW` as the end bound. Turns the frame into a `BETWEEN`.
110pub fn to_current_row<Q: HasFrame>() -> impl Mod<Q> {
111    end(Expr::raw("CURRENT ROW"))
112}
113
114/// `offset FOLLOWING` as the end bound. Turns the frame into a `BETWEEN`.
115pub fn to_following<Q: HasFrame>(offset: impl IntoExpr) -> impl Mod<Q> {
116    end(offset_bound(offset, "FOLLOWING"))
117}
118
119/// `UNBOUNDED FOLLOWING` as the end bound. Turns the frame into a `BETWEEN`.
120pub fn to_unbounded_following<Q: HasFrame>() -> impl Mod<Q> {
121    end(Expr::raw("UNBOUNDED FOLLOWING"))
122}
123
124/// `EXCLUDE NO OTHERS` — the default, written out.
125pub fn exclude_no_others<Q: HasFrame>() -> impl Mod<Q> {
126    exclusion(FrameExclusion::NoOthers)
127}
128
129/// `EXCLUDE CURRENT ROW`.
130pub fn exclude_current_row<Q: HasFrame>() -> impl Mod<Q> {
131    exclusion(FrameExclusion::CurrentRow)
132}
133
134/// `EXCLUDE GROUP` — the current row and all its peers.
135pub fn exclude_group<Q: HasFrame>() -> impl Mod<Q> {
136    exclusion(FrameExclusion::Group)
137}
138
139/// `EXCLUDE TIES` — the current row's peers, but not the row itself.
140pub fn exclude_ties<Q: HasFrame>() -> impl Mod<Q> {
141    exclusion(FrameExclusion::Ties)
142}