1use core::marker::PhantomData;
27
28use crate::sql::{SQL, Token};
29use crate::traits::{SQLParam, ToSQL};
30use crate::types::{BooleanLike, Compatible, DataType};
31
32use super::agg::{CountPolicy, FloatPolicy};
33use super::null::NullOr;
34use super::{Agg, Expr, NonNull, Null, Nullability, SQLExpr, Scalar};
35use crate::dialect::DialectTypes;
36
37pub trait AggregateFilterSupport {}
39
40impl AggregateFilterSupport for crate::SQLiteDialect {}
41impl AggregateFilterSupport for crate::PostgresDialect {}
42
43#[derive(Debug, Clone, Copy)]
49pub enum FrameBound {
50 UnboundedPreceding,
52 Preceding(u64),
54 CurrentRow,
56 Following(u64),
58 UnboundedFollowing,
60}
61
62impl FrameBound {
63 fn write_sql<'a, V: SQLParam>(&self) -> SQL<'a, V> {
64 match self {
65 Self::UnboundedPreceding => SQL::from(Token::UNBOUNDED).push(Token::PRECEDING),
66 Self::Preceding(n) => {
67 SQL::number(usize::try_from(*n).unwrap_or(usize::MAX)).push(Token::PRECEDING)
68 }
69 Self::CurrentRow => SQL::from(Token::CURRENT).push(Token::ROW),
70 Self::Following(n) => {
71 SQL::number(usize::try_from(*n).unwrap_or(usize::MAX)).push(Token::FOLLOWING)
72 }
73 Self::UnboundedFollowing => SQL::from(Token::UNBOUNDED).push(Token::FOLLOWING),
74 }
75 }
76}
77
78#[derive(Debug, Clone)]
95pub struct WindowSpec<'a, V: SQLParam> {
96 partition: Option<SQL<'a, V>>,
97 order: Option<SQL<'a, V>>,
98 frame: Option<SQL<'a, V>>,
99}
100
101#[must_use]
103pub const fn window<'a, V: SQLParam>() -> WindowSpec<'a, V> {
104 WindowSpec {
105 partition: None,
106 order: None,
107 frame: None,
108 }
109}
110
111impl<'a, V: SQLParam + 'a> WindowSpec<'a, V> {
112 #[must_use]
114 pub fn partition_by<I>(mut self, exprs: I) -> Self
115 where
116 I: IntoIterator,
117 I::Item: ToSQL<'a, V>,
118 {
119 self.partition = Some(
120 SQL::from(Token::PARTITION)
121 .push(Token::BY)
122 .append(SQL::join(exprs, Token::COMMA)),
123 );
124 self
125 }
126
127 #[must_use]
129 pub fn order_by<T: ToSQL<'a, V>>(mut self, exprs: T) -> Self {
130 self.order = Some(
131 SQL::from(Token::ORDER)
132 .push(Token::BY)
133 .append(exprs.into_sql()),
134 );
135 self
136 }
137
138 #[must_use]
140 pub fn rows_between(mut self, start: FrameBound, end: FrameBound) -> Self {
141 self.frame = Some(
142 SQL::from(Token::ROWS)
143 .push(Token::BETWEEN)
144 .append(start.write_sql())
145 .push(Token::AND)
146 .append(end.write_sql()),
147 );
148 self
149 }
150
151 #[must_use]
153 pub fn range_between(mut self, start: FrameBound, end: FrameBound) -> Self {
154 self.frame = Some(
155 SQL::from(Token::RANGE)
156 .push(Token::BETWEEN)
157 .append(start.write_sql())
158 .push(Token::AND)
159 .append(end.write_sql()),
160 );
161 self
162 }
163
164 fn into_sql(self) -> SQL<'a, V> {
166 let mut sql = SQL::empty();
167 if let Some(p) = self.partition {
168 sql.append_mut(p);
169 }
170 if let Some(o) = self.order {
171 sql.append_mut(o);
172 }
173 if let Some(f) = self.frame {
174 sql.append_mut(f);
175 }
176 sql
177 }
178}
179
180impl<'a, V, T, N> SQLExpr<'a, V, T, N, Agg>
185where
186 V: SQLParam + 'a,
187 T: DataType,
188 N: Nullability,
189{
190 pub fn over(self, spec: WindowSpec<'a, V>) -> SQLExpr<'a, V, T, N, Scalar> {
207 let sql = self
208 .into_sql()
209 .push(Token::OVER)
210 .push(Token::LPAREN)
211 .append(spec.into_sql())
212 .push(Token::RPAREN);
213 SQLExpr::new(sql)
214 }
215
216 #[must_use]
220 pub fn filter<C>(self, condition: C) -> Self
221 where
222 C: Expr<'a, V>,
223 C::SQLType: BooleanLike,
224 V::DialectMarker: AggregateFilterSupport,
225 {
226 let sql = self
227 .into_sql()
228 .push(Token::FILTER)
229 .push(Token::LPAREN)
230 .push(Token::WHERE)
231 .append(condition.into_expr_sql())
232 .push(Token::RPAREN);
233 SQLExpr::new(sql)
234 }
235}
236
237#[derive(Debug, Clone)]
247pub struct WindowFnExpr<'a, V: SQLParam, T: DataType, N: Nullability> {
248 sql: SQL<'a, V>,
249 _marker: PhantomData<(T, N)>,
250}
251
252impl<'a, V, T, N> WindowFnExpr<'a, V, T, N>
253where
254 V: SQLParam + 'a,
255 T: DataType,
256 N: Nullability,
257{
258 const fn new(sql: SQL<'a, V>) -> Self {
259 Self {
260 sql,
261 _marker: PhantomData,
262 }
263 }
264
265 pub fn over(self, spec: WindowSpec<'a, V>) -> SQLExpr<'a, V, T, N, Scalar> {
269 let sql = self
270 .sql
271 .push(Token::OVER)
272 .push(Token::LPAREN)
273 .append(spec.into_sql())
274 .push(Token::RPAREN);
275 SQLExpr::new(sql)
276 }
277}
278
279#[must_use]
287pub fn row_number<'a, V>() -> WindowFnExpr<'a, V, <V::DialectMarker as CountPolicy>::Count, NonNull>
288where
289 V: SQLParam + 'a,
290 V::DialectMarker: CountPolicy,
291{
292 WindowFnExpr::new(SQL::raw("ROW_NUMBER()"))
293}
294
295#[must_use]
299pub fn rank<'a, V>() -> WindowFnExpr<'a, V, <V::DialectMarker as CountPolicy>::Count, NonNull>
300where
301 V: SQLParam + 'a,
302 V::DialectMarker: CountPolicy,
303{
304 WindowFnExpr::new(SQL::raw("RANK()"))
305}
306
307#[must_use]
311pub fn dense_rank<'a, V>() -> WindowFnExpr<'a, V, <V::DialectMarker as CountPolicy>::Count, NonNull>
312where
313 V: SQLParam + 'a,
314 V::DialectMarker: CountPolicy,
315{
316 WindowFnExpr::new(SQL::raw("DENSE_RANK()"))
317}
318
319#[must_use]
323pub fn ntile<'a, V>(
324 n: usize,
325) -> WindowFnExpr<'a, V, <V::DialectMarker as DialectTypes>::Int, NonNull>
326where
327 V: SQLParam + 'a,
328{
329 WindowFnExpr::new(SQL::func("NTILE", SQL::number(n)))
330}
331
332#[must_use]
336pub fn percent_rank<'a, V>()
337-> WindowFnExpr<'a, V, <V::DialectMarker as FloatPolicy>::Float, NonNull>
338where
339 V: SQLParam + 'a,
340 V::DialectMarker: FloatPolicy,
341{
342 WindowFnExpr::new(SQL::raw("PERCENT_RANK()"))
343}
344
345#[must_use]
349pub fn cume_dist<'a, V>() -> WindowFnExpr<'a, V, <V::DialectMarker as FloatPolicy>::Float, NonNull>
350where
351 V: SQLParam + 'a,
352 V::DialectMarker: FloatPolicy,
353{
354 WindowFnExpr::new(SQL::raw("CUME_DIST()"))
355}
356
357pub fn lag<'a, V, E>(expr: E) -> WindowFnExpr<'a, V, E::SQLType, Null>
361where
362 V: SQLParam + 'a,
363 E: Expr<'a, V>,
364{
365 WindowFnExpr::new(SQL::func("LAG", expr.into_sql()))
366}
367
368pub fn lag_with_default<'a, V, E, D>(
372 expr: E,
373 offset: usize,
374 default: D,
375) -> WindowFnExpr<'a, V, E::SQLType, <E::Nullable as NullOr<D::Nullable>>::Output>
376where
377 V: SQLParam + 'a,
378 E: Expr<'a, V>,
379 D: Expr<'a, V>,
380 E::SQLType: Compatible<D::SQLType>,
381 E::Nullable: NullOr<D::Nullable>,
382 D::Nullable: Nullability,
383{
384 let args = expr
385 .into_sql()
386 .push(Token::COMMA)
387 .append(SQL::number(offset))
388 .push(Token::COMMA)
389 .append(default.into_sql());
390 WindowFnExpr::new(SQL::func("LAG", args))
391}
392
393pub fn lead<'a, V, E>(expr: E) -> WindowFnExpr<'a, V, E::SQLType, Null>
397where
398 V: SQLParam + 'a,
399 E: Expr<'a, V>,
400{
401 WindowFnExpr::new(SQL::func("LEAD", expr.into_sql()))
402}
403
404pub fn lead_with_default<'a, V, E, D>(
408 expr: E,
409 offset: usize,
410 default: D,
411) -> WindowFnExpr<'a, V, E::SQLType, <E::Nullable as NullOr<D::Nullable>>::Output>
412where
413 V: SQLParam + 'a,
414 E: Expr<'a, V>,
415 D: Expr<'a, V>,
416 E::SQLType: Compatible<D::SQLType>,
417 E::Nullable: NullOr<D::Nullable>,
418 D::Nullable: Nullability,
419{
420 let args = expr
421 .into_sql()
422 .push(Token::COMMA)
423 .append(SQL::number(offset))
424 .push(Token::COMMA)
425 .append(default.into_sql());
426 WindowFnExpr::new(SQL::func("LEAD", args))
427}
428
429pub fn first_value<'a, V, E>(expr: E) -> WindowFnExpr<'a, V, E::SQLType, Null>
433where
434 V: SQLParam + 'a,
435 E: Expr<'a, V>,
436{
437 WindowFnExpr::new(SQL::func("FIRST_VALUE", expr.into_sql()))
438}
439
440pub fn last_value<'a, V, E>(expr: E) -> WindowFnExpr<'a, V, E::SQLType, Null>
444where
445 V: SQLParam + 'a,
446 E: Expr<'a, V>,
447{
448 WindowFnExpr::new(SQL::func("LAST_VALUE", expr.into_sql()))
449}
450
451pub fn nth_value<'a, V, E>(expr: E, n: usize) -> WindowFnExpr<'a, V, E::SQLType, Null>
455where
456 V: SQLParam + 'a,
457 E: Expr<'a, V>,
458{
459 let args = expr.into_sql().push(Token::COMMA).append(SQL::number(n));
460 WindowFnExpr::new(SQL::func("NTH_VALUE", args))
461}