Skip to main content

diesel/expression/functions/aggregate_expressions/
frame_clause.rs

1use crate::backend::sql_dialect;
2use crate::query_builder::{QueryFragment, QueryId};
3
4use super::aggregate_filter::NoFilter;
5use super::aggregate_order::{NoOrder, Order};
6use super::over_clause::{NoWindow, OverClause, ValidAggregateFilterForWindow};
7use super::partition_by::NoPartition;
8use super::prefix::NoPrefix;
9use super::{AggregateExpression, IsWindowFunction};
10
11empty_clause!(NoFrame);
12
13#[derive(QueryId, Copy, Clone, Debug)]
14pub struct FrameClause<F>(F);
15
16impl<F, DB> QueryFragment<DB> for FrameClause<F>
17where
18    F: QueryFragment<DB>,
19    DB: crate::backend::Backend,
20{
21    fn walk_ast<'b>(
22        &'b self,
23        pass: crate::query_builder::AstPass<'_, 'b, DB>,
24    ) -> crate::QueryResult<()> {
25        self.0.walk_ast(pass)?;
26        Ok(())
27    }
28}
29
30macro_rules! simple_frame_expr {
31    ($(#[doc = $doc: literal])* $name: ident, $kind: expr) => {
32        #[derive(QueryId, Clone, Copy, Debug)]
33        $(#[doc = $doc])*
34        pub struct $name;
35
36        impl<DB> QueryFragment<DB> for $name
37        where
38            DB: crate::backend::Backend,
39        {
40            fn walk_ast<'b>(
41                &'b self,
42                mut pass: crate::query_builder::AstPass<'_, 'b, DB>,
43            ) -> crate::QueryResult<()> {
44                pass.push_sql($kind);
45                Ok(())
46            }
47        }
48    };
49}
50
51macro_rules! simple_frame_expr_with_bound {
52    ($(#[doc = $doc:literal])* $name: ident, $option: ident, $bound: ty, $kind: expr) => {
53        #[derive(QueryId, Clone, Copy, Debug)]
54        $(#[doc = $doc])*
55        pub struct $name;
56
57        impl<DB> QueryFragment<DB> for $name
58        where
59            DB: crate::backend::Backend,
60            Self: QueryFragment<DB, DB::$option>,
61        {
62            fn walk_ast<'b>(
63                &'b self,
64                pass: crate::query_builder::AstPass<'_, 'b, DB>,
65            ) -> crate::QueryResult<()> {
66                <Self as QueryFragment<DB, DB::$option>>::walk_ast(self, pass)
67            }
68        }
69        impl<DB> QueryFragment<DB, $bound> for $name
70        where
71            DB: crate::backend::Backend<$option = $bound>,
72        {
73            fn walk_ast<'b>(
74                &'b self,
75                mut pass: crate::query_builder::AstPass<'_, 'b, DB>,
76            ) -> crate::QueryResult<()> {
77                pass.push_sql($kind);
78                Ok(())
79            }
80        }
81    };
82}
83
84// kinds
85simple_frame_expr!(
86    /// Range frame mode
87    ///
88    /// Requires to set a `ORDER BY` clause set via `window_order`
89    /// for the current window function call
90    Range, " RANGE ");
91simple_frame_expr!(
92    /// Rows frame mode
93    ///
94    /// This options specifies that the offset starts/ends
95    /// a certain number of rows before/after the current row
96    Rows, " ROWS ");
97simple_frame_expr_with_bound!(
98    /// Groups frame mode
99    ///
100    /// This options specifies that the offset starts/ends
101    /// a certain number of peer groups before or after
102    /// the current row. A peer group is a set of rows that are
103    /// equivalent in the `ORDER BY` ordering
104    ///
105    /// There must be a `ORDER BY` clause set via `window_order`
106    /// for the current window function call
107    Groups,
108    WindowFrameClauseGroupSupport,
109    sql_dialect::window_frame_clause_group_support::IsoGroupWindowFrameUnit,
110    " GROUPS "
111);
112
113// start & end
114simple_frame_expr!(
115    /// A `UNBOUNDED PRECEDING` frame bound
116    ///
117    /// This bound specifies that the frame starts with
118    /// the first row of the partition
119    ///
120    /// This option can be used as frame start
121    UnboundedPreceding, "UNBOUNDED PRECEDING ");
122simple_frame_expr!(
123    /// A `CURRENT ROW` frame bound
124    ///
125    /// For the `RANGE` and `GROUP` frame mode this bound
126    /// specifies that the current frame starts with the current
127    /// rows first peer groups row. A peer group is defined as a
128    /// set of rows with an equivalent `ORDER BY` ordering.
129    ///
130    /// For the `ROWS` mode `CURRENT ROW` simply means the current
131    /// row
132    ///
133    /// This option can be used as frame start and end
134    CurrentRow, "CURRENT ROW ");
135simple_frame_expr!(
136    /// A `UNBOUNDED FOLLOWING` frame bound
137    ///
138    /// This bound specifies that the frame ends with
139    /// the last row of the partition
140    ///
141    /// This option can be used as frame end
142    UnboundedFollowing, "UNBOUNDED FOLLOWING ");
143
144// exclusion
145simple_frame_expr_with_bound!(
146    /// Exclude the current row from the window frame
147    ExcludeCurrentRow,
148    WindowFrameExclusionSupport,
149    sql_dialect::window_frame_exclusion_support::FrameExclusionSupport,
150    "EXCLUDE CURRENT ROW "
151);
152simple_frame_expr_with_bound!(
153    /// Exclude the current peer group from the window frame
154    ///
155    /// A peer group is a set of rows with an equivalent `ORDER BY`
156    /// ordering
157    ExcludeGroup,
158    WindowFrameExclusionSupport,
159    sql_dialect::window_frame_exclusion_support::FrameExclusionSupport,
160    "EXCLUDE GROUP "
161);
162simple_frame_expr_with_bound!(
163    /// Exclude any peers, but not the current row from the window frame
164    ///
165    /// This excludes any row with an equivalent `ORDER BY` ordering
166    /// to the current row from the frame window
167    ExcludeTies,
168    WindowFrameExclusionSupport,
169    sql_dialect::window_frame_exclusion_support::FrameExclusionSupport,
170    "EXCLUDE TIES "
171);
172simple_frame_expr_with_bound!(
173    /// Exclude no rows from the frame windows
174    ///
175    /// This is the default behaviour if not specified
176    ExcludeNoOthers,
177    WindowFrameExclusionSupport,
178    sql_dialect::window_frame_exclusion_support::FrameExclusionSupport,
179    "EXCLUDE NO OTHERS "
180);
181
182/// A preceding frame clause expression with a fixed offset
183///
184/// Can be constructed via [`FrameBoundDsl::preceding`]
185#[derive(Clone, Copy, Debug)]
186pub struct OffsetPreceding<T = u64>(T);
187
188// manual impl as the derive makes this dependent on a `T: QueryId` impl
189// which is wrong
190impl QueryId for OffsetPreceding {
191    type QueryId = ();
192
193    // it's not safe to cache by type id
194    // as we inline a dynamic value (the offset) directly
195    // into the SQL
196    const HAS_STATIC_QUERY_ID: bool = false;
197}
198
199impl<DB> QueryFragment<DB> for OffsetPreceding
200where
201    DB: crate::backend::Backend,
202{
203    fn walk_ast<'b>(
204        &'b self,
205        mut pass: crate::query_builder::AstPass<'_, 'b, DB>,
206    ) -> crate::QueryResult<()> {
207        // we cannot use binds here as mysql doesn't support it :(
208        // at least it's safe to just insert a number here
209        pass.push_sql(&self.0.to_string());
210        pass.push_sql(" PRECEDING ");
211        Ok(())
212    }
213}
214
215/// A following frame clause expression with a fixed offset
216///
217/// Can be constructed via [`FrameBoundDsl::following`]
218#[derive(Clone, Copy, Debug)]
219pub struct OffsetFollowing<I = u64>(I);
220
221// manual impl as the derive makes this dependent on a `T: QueryId` impl
222// which is wrong
223impl QueryId for OffsetFollowing {
224    type QueryId = ();
225
226    // it's not safe to cache by type id
227    // as we inline a dynamic value (the offset) directly
228    // into the SQL
229    const HAS_STATIC_QUERY_ID: bool = false;
230}
231
232impl<DB> QueryFragment<DB> for OffsetFollowing
233where
234    DB: crate::backend::Backend,
235{
236    fn walk_ast<'b>(
237        &'b self,
238        mut pass: crate::query_builder::AstPass<'_, 'b, DB>,
239    ) -> crate::QueryResult<()> {
240        // we cannot use binds here as mysql doesn't support it :(
241        // at least it's safe to just insert a number here
242        pass.push_sql(&self.0.to_string());
243        pass.push_sql(" FOLLOWING ");
244        Ok(())
245    }
246}
247
248pub trait FrameDsl<F> {
249    type Output;
250
251    fn frame(self, expr: F) -> Self::Output;
252}
253
254#[diagnostic::on_unimplemented(
255    message = "`Groups` frame clauses require a ordered window function",
256    note = "call `.window_order(some_column)` first"
257)]
258pub trait ValidFrameClause<O> {}
259
260impl<O, Kind, Start, End, Exclusion> ValidFrameClause<O>
261    for BetweenFrame<Kind, Start, End, Exclusion>
262where
263    Kind: ValidFrameClause<O>,
264{
265}
266impl<O, Kind, Start, Exclusion> ValidFrameClause<O> for StartFrame<Kind, Start, Exclusion> where
267    Kind: ValidFrameClause<O>
268{
269}
270impl<O> ValidFrameClause<O> for Rows {}
271impl<O> ValidFrameClause<O> for Range {}
272impl<O> ValidFrameClause<Order<O, true>> for Groups {}
273
274impl<E, Fn, Filter, Frame, Partition, Order> FrameDsl<E>
275    for AggregateExpression<Fn, NoPrefix, NoOrder, Filter, OverClause<Partition, Order, Frame>>
276where
277    E: FrameClauseExpression,
278    E: ValidFrameClause<Order>,
279    Filter: ValidAggregateFilterForWindow<Fn, OverClause<Partition, Order, FrameClause<E>>>,
280{
281    type Output = AggregateExpression<
282        Fn,
283        NoPrefix,
284        NoOrder,
285        Filter,
286        OverClause<Partition, Order, FrameClause<E>>,
287    >;
288
289    fn frame(self, expr: E) -> Self::Output {
290        AggregateExpression {
291            prefix: self.prefix,
292            function: self.function,
293            order: self.order,
294            filter: self.filter,
295            window: OverClause {
296                partition_by: self.window.partition_by,
297                order: self.window.order,
298                frame_clause: FrameClause(expr),
299            },
300        }
301    }
302}
303
304impl<E, Fn, Filter> FrameDsl<E> for AggregateExpression<Fn, NoPrefix, NoOrder, Filter, NoWindow>
305where
306    E: FrameClauseExpression,
307    E: ValidFrameClause<NoOrder>,
308    Filter: ValidAggregateFilterForWindow<Fn, OverClause<NoPartition, NoOrder, FrameClause<E>>>,
309{
310    type Output = AggregateExpression<
311        Fn,
312        NoPrefix,
313        NoOrder,
314        Filter,
315        OverClause<NoPartition, NoOrder, FrameClause<E>>,
316    >;
317
318    fn frame(self, expr: E) -> Self::Output {
319        AggregateExpression {
320            prefix: self.prefix,
321            function: self.function,
322            order: self.order,
323            filter: self.filter,
324            window: OverClause {
325                partition_by: NoPartition,
326                order: NoOrder,
327                frame_clause: FrameClause(expr),
328            },
329        }
330    }
331}
332
333impl<E, Fn> FrameDsl<E> for Fn
334where
335    Fn: IsWindowFunction,
336    E: FrameClauseExpression,
337    E: ValidFrameClause<NoOrder>,
338{
339    type Output = AggregateExpression<
340        Fn,
341        NoPrefix,
342        NoOrder,
343        NoFilter,
344        OverClause<NoPartition, NoOrder, FrameClause<E>>,
345    >;
346
347    fn frame(self, expr: E) -> Self::Output {
348        AggregateExpression {
349            prefix: NoPrefix,
350            function: self,
351            order: NoOrder,
352            filter: NoFilter,
353            window: OverClause {
354                partition_by: NoPartition,
355                order: NoOrder,
356                frame_clause: FrameClause(expr),
357            },
358        }
359    }
360}
361
362pub trait FrameClauseExpression {}
363
364/// A marker trait for possible start frame expressions
365///
366/// See the list of types implementing this trait to understand
367/// what can be used in this position
368pub trait FrameClauseStartBound: Sealed {}
369
370/// A marker trait for possible end frame expressions
371///
372/// See the list of types implementing this trait to understand
373/// what can be used in this position
374pub trait FrameClauseEndBound: Sealed {}
375
376impl FrameClauseEndBound for UnboundedFollowing {}
377impl Sealed for UnboundedFollowing {}
378impl FrameClauseStartBound for UnboundedPreceding {}
379impl Sealed for UnboundedPreceding {}
380impl FrameClauseEndBound for CurrentRow {}
381impl FrameClauseStartBound for CurrentRow {}
382impl Sealed for CurrentRow {}
383impl FrameClauseEndBound for OffsetFollowing {}
384impl Sealed for OffsetFollowing {}
385impl FrameClauseStartBound for OffsetPreceding {}
386impl Sealed for OffsetPreceding {}
387
388/// A marker trait for possible frame exclusion expressions
389///
390/// See the list of types implementing this trait to understand
391/// what can be used in this position
392pub trait FrameClauseExclusion: Sealed {}
393
394impl FrameClauseExclusion for ExcludeGroup {}
395impl Sealed for ExcludeGroup {}
396impl FrameClauseExclusion for ExcludeNoOthers {}
397impl Sealed for ExcludeNoOthers {}
398impl FrameClauseExclusion for ExcludeTies {}
399impl Sealed for ExcludeTies {}
400impl FrameClauseExclusion for ExcludeCurrentRow {}
401impl Sealed for ExcludeCurrentRow {}
402
403/// Construct a frame clause for window functions from an integer
404pub trait FrameBoundDsl {
405    /// Use the preceding frame clause specification
406    fn preceding(self) -> OffsetPreceding;
407
408    /// Use the following frame clause specification
409    fn following(self) -> OffsetFollowing;
410}
411
412impl FrameBoundDsl for u64 {
413    fn preceding(self) -> OffsetPreceding {
414        OffsetPreceding(self)
415    }
416
417    fn following(self) -> OffsetFollowing {
418        OffsetFollowing(self)
419    }
420}
421// TODO: We might want to implement
422// it for datetime and date intervals?
423// The postgres documentation indicates that
424// something like `RANGE BETWEEN '1 day' PRECEDING AND '10 days' FOLLOWING`
425// is valid
426
427empty_clause!(NoExclusion);
428
429#[derive(QueryId, Copy, Clone, Debug)]
430pub struct StartFrame<Kind, Start, Exclusion = NoExclusion> {
431    kind: Kind,
432    start: Start,
433    exclusion: Exclusion,
434}
435
436impl<Kind, Start, Exclusion, DB> QueryFragment<DB> for StartFrame<Kind, Start, Exclusion>
437where
438    Kind: QueryFragment<DB>,
439    Start: QueryFragment<DB>,
440    Exclusion: QueryFragment<DB>,
441    DB: crate::backend::Backend,
442{
443    fn walk_ast<'b>(
444        &'b self,
445        mut pass: crate::query_builder::AstPass<'_, 'b, DB>,
446    ) -> crate::QueryResult<()> {
447        self.kind.walk_ast(pass.reborrow())?;
448        self.start.walk_ast(pass.reborrow())?;
449        self.exclusion.walk_ast(pass.reborrow())?;
450        Ok(())
451    }
452}
453
454impl<Kind, Start, Exclusion> FrameClauseExpression for StartFrame<Kind, Start, Exclusion> {}
455
456#[derive(QueryId, Copy, Clone, Debug)]
457pub struct BetweenFrame<Kind, Start, End, Exclusion = NoExclusion> {
458    kind: Kind,
459    start: Start,
460    end: End,
461    exclusion: Exclusion,
462}
463
464impl<Kind, Start, End, Exclusion, DB> QueryFragment<DB>
465    for BetweenFrame<Kind, Start, End, Exclusion>
466where
467    Kind: QueryFragment<DB>,
468    Start: QueryFragment<DB>,
469    End: QueryFragment<DB>,
470    Exclusion: QueryFragment<DB>,
471    DB: crate::backend::Backend,
472{
473    fn walk_ast<'b>(
474        &'b self,
475        mut pass: crate::query_builder::AstPass<'_, 'b, DB>,
476    ) -> crate::QueryResult<()> {
477        self.kind.walk_ast(pass.reborrow())?;
478        pass.push_sql(" BETWEEN ");
479        self.start.walk_ast(pass.reborrow())?;
480        pass.push_sql(" AND ");
481        self.end.walk_ast(pass.reborrow())?;
482        self.exclusion.walk_ast(pass.reborrow())?;
483        Ok(())
484    }
485}
486
487impl<Kind, Start, End, Exclusion> FrameClauseExpression
488    for BetweenFrame<Kind, Start, End, Exclusion>
489{
490}
491
492pub trait FrameClauseDslHelper: Sized {}
493
494/// Construct a frame clause for window functions
495pub trait FrameClauseDsl: FrameClauseDslHelper {
496    /// Construct a frame clause with a starting bound
497    fn frame_start_with<E>(self, start: E) -> super::dsl::FrameStartWith<Self, E>
498    where
499        E: FrameClauseStartBound,
500    {
501        StartFrame {
502            kind: self,
503            start,
504            exclusion: NoExclusion,
505        }
506    }
507
508    /// Construct a frame clause with a starting bound and an exclusion condition
509    fn frame_start_with_exclusion<E1, E2>(
510        self,
511        start: E1,
512        exclusion: E2,
513    ) -> super::dsl::FrameStartWithExclusion<Self, E1, E2>
514    where
515        E1: FrameClauseStartBound,
516        E2: FrameClauseExclusion,
517    {
518        StartFrame {
519            kind: self,
520            start,
521            exclusion,
522        }
523    }
524
525    /// Construct a between frame clause with a starting and end bound
526    fn frame_between<E1, E2>(self, start: E1, end: E2) -> super::dsl::FrameBetween<Self, E1, E2>
527    where
528        E1: FrameClauseStartBound,
529        E2: FrameClauseEndBound,
530    {
531        BetweenFrame {
532            kind: self,
533            start,
534            end,
535            exclusion: NoExclusion,
536        }
537    }
538
539    /// Construct a between frame clause with a starting and end bound  with an exclusion condition
540    fn frame_between_with_exclusion<E1, E2, E3>(
541        self,
542        start: E1,
543        end: E2,
544        exclusion: E3,
545    ) -> super::dsl::FrameBetweenWithExclusion<Self, E1, E2, E3>
546    where
547        E1: FrameClauseStartBound,
548        E2: FrameClauseEndBound,
549        E3: FrameClauseExclusion,
550    {
551        BetweenFrame {
552            kind: self,
553            start,
554            end,
555            exclusion,
556        }
557    }
558}
559
560impl<T> FrameClauseDsl for T where T: FrameClauseDslHelper {}
561
562impl FrameClauseDslHelper for Range {}
563impl FrameClauseDslHelper for Rows {}
564impl FrameClauseDslHelper for Groups {}
565
566pub trait Sealed {}