Skip to content

Commit 5d6eba1

Browse files
committed
First draft for supporting window functions and other aggregate function expressions
See [here](https://www.postgresql.org/docs/current/sql-expressions.html#SYNTAX-WINDOW-FUNCTIONS) and [here](https://www.postgresql.org/docs/current/sql-expressions.html#SYNTAX-AGGREGATES) for the relevant postgresql documentation This PR implements mainly the relevant DSL structure for this types. As of today this should support most of the relevant API surface. This PR is not finished yet, I mainly push this now to get some early feedback on the exposed user API. See the two dummy tests for how to use the new functionality. What's still missing: - [ ] Support for other backends - [ ] Tests, a lot of tests - [ ] Compile tests to verify that we do not generate invalid SQL - [ ] Documentation
1 parent e832adb commit 5d6eba1

26 files changed

+2193
-250
lines changed

1

Whitespace-only changes.

diesel/src/backend.rs

+42
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,20 @@ pub trait SqlDialect: self::private::TrustedBackend {
317317
doc = "See [`sql_dialect::alias_syntax`] for provided default implementations"
318318
)]
319319
type AliasSyntax;
320+
321+
/// Configures how this backend support the `GROUP` frame unit for window functions
322+
#[cfg_attr(
323+
feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes",
324+
doc = "See [`sql_dialect::window_frame_clause_group_support`] for provided default implementations"
325+
)]
326+
type WindowFrameClauseGroupSupport;
327+
328+
/// Configures how this backend supports aggregate function expressions
329+
#[cfg_attr(
330+
feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes",
331+
doc = "See [`sql_dialect::window_frame_clause_group_support`] for provided default implementations"
332+
)]
333+
type AggregateFunctionExpressions;
320334
}
321335

322336
/// This module contains all options provided by diesel to configure the [`SqlDialect`] trait.
@@ -539,6 +553,34 @@ pub(crate) mod sql_dialect {
539553
#[derive(Debug, Copy, Clone)]
540554
pub struct AsAliasSyntax;
541555
}
556+
557+
/// This module contains all reusable options to configure [`SqlDialect::WindowFrameClauseGroupSupport`]
558+
#[diesel_derives::__diesel_public_if(
559+
feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"
560+
)]
561+
pub mod window_frame_clause_group_support {
562+
/// Indicates that this backend does not support the `GROUPS` frame unit
563+
#[derive(Debug, Copy, Clone)]
564+
pub struct NoGroupWindowFrameUnit;
565+
566+
/// Indicates that this backend does support the `GROUPS` frame unit as specified by the standard
567+
#[derive(Debug, Copy, Clone)]
568+
pub struct IsoGroupWindowFrameUnit;
569+
}
570+
571+
/// This module contains all reusable options to configure [`SqlDialect::AggregateFunctionExpressions`]
572+
#[diesel_derives::__diesel_public_if(
573+
feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"
574+
)]
575+
pub mod aggregate_function_expressions {
576+
/// Indicates that this backend does not support aggregate function expressions
577+
#[derive(Debug, Copy, Clone)]
578+
pub struct NoAggregateFunctionExpressions;
579+
580+
/// Indicates that this backend supports aggregate function expressions similar to PostgreSQL
581+
#[derive(Debug, Copy, Clone)]
582+
pub struct PostgresLikeAggregateFunctionExpressions;
583+
}
542584
}
543585

544586
// These traits are not part of the public API

diesel/src/connection/instrumentation.rs

+18
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,12 @@ impl DynInstrumentation {
356356
#[diesel_derives::__diesel_public_if(
357357
feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"
358358
)]
359+
#[cfg(any(
360+
feature = "postgres",
361+
feature = "sqlite",
362+
feature = "mysql",
363+
feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"
364+
))]
359365
pub(crate) fn default_instrumentation() -> Self {
360366
Self {
361367
inner: get_default_instrumentation(),
@@ -367,6 +373,12 @@ impl DynInstrumentation {
367373
#[diesel_derives::__diesel_public_if(
368374
feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"
369375
)]
376+
#[cfg(any(
377+
feature = "postgres",
378+
feature = "sqlite",
379+
feature = "mysql",
380+
feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"
381+
))]
370382
pub(crate) fn none() -> Self {
371383
Self {
372384
inner: None,
@@ -378,6 +390,12 @@ impl DynInstrumentation {
378390
#[diesel_derives::__diesel_public_if(
379391
feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"
380392
)]
393+
#[cfg(any(
394+
feature = "postgres",
395+
feature = "sqlite",
396+
feature = "mysql",
397+
feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"
398+
))]
381399
pub(crate) fn on_connection_event(&mut self, event: InstrumentationEvent<'_>) {
382400
// This implementation is not necessary to be able to call this method on this object
383401
// because of the already existing Deref impl.

diesel/src/expression/count.rs

+1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ define_sql_function! {
2929
/// # }
3030
/// ```
3131
#[aggregate]
32+
#[window]
3233
fn count<T: SqlType + SingleValue>(expr: T) -> BigInt;
3334
}
3435

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
use crate::backend::Backend;
2+
use crate::expression::{AsExpression, ValidGrouping};
3+
use crate::query_builder::{AstPass, NotSpecialized, QueryFragment, QueryId};
4+
use crate::sql_types::Bool;
5+
use crate::{AppearsOnTable, Expression, QueryResult, SelectableExpression};
6+
7+
macro_rules! empty_clause {
8+
($name: ident) => {
9+
#[derive(Debug, Clone, Copy, QueryId)]
10+
pub struct $name;
11+
12+
impl<DB> crate::query_builder::QueryFragment<DB> for $name
13+
where
14+
DB: crate::backend::Backend + crate::backend::DieselReserveSpecialization,
15+
{
16+
fn walk_ast<'b>(
17+
&'b self,
18+
_pass: crate::query_builder::AstPass<'_, 'b, DB>,
19+
) -> crate::QueryResult<()> {
20+
Ok(())
21+
}
22+
}
23+
};
24+
}
25+
26+
mod aggregate_filter;
27+
mod aggregate_order;
28+
pub(crate) mod frame_clause;
29+
mod over_clause;
30+
mod partition_by;
31+
mod prefix;
32+
mod within_group;
33+
34+
use self::aggregate_filter::{FilterDsl, NoFilter};
35+
use self::aggregate_order::{NoOrder, OrderAggregateDsl, OrderWindowDsl};
36+
use self::frame_clause::{FrameDsl, NoFrame};
37+
pub use self::over_clause::OverClause;
38+
use self::over_clause::{NoWindow, OverDsl};
39+
use self::partition_by::PartitionByDsl;
40+
use self::prefix::{All, AllDsl, DistinctDsl, NoPrefix};
41+
use self::within_group::{NoWithin, WithinGroupDsl};
42+
43+
#[derive(QueryId, Debug)]
44+
pub struct AggregateExpression<
45+
Fn,
46+
Prefix = NoPrefix,
47+
Order = NoOrder,
48+
Filter = NoFilter,
49+
Within = NoWithin,
50+
Window = NoWindow,
51+
> {
52+
prefix: Prefix,
53+
function: Fn,
54+
order: Order,
55+
filter: Filter,
56+
within_group: Within,
57+
window: Window,
58+
}
59+
60+
impl<Fn, Prefix, Order, Filter, Within, Window, DB> QueryFragment<DB>
61+
for AggregateExpression<Fn, Prefix, Order, Filter, Within, Window>
62+
where
63+
DB: crate::backend::Backend + crate::backend::DieselReserveSpecialization,
64+
Fn: FunctionFragment<DB>,
65+
Prefix: QueryFragment<DB>,
66+
Order: QueryFragment<DB>,
67+
Filter: QueryFragment<DB>,
68+
Within: QueryFragment<DB>,
69+
Window: QueryFragment<DB> + WindowFunctionFragment<Fn, DB>,
70+
{
71+
fn walk_ast<'b>(&'b self, mut pass: AstPass<'_, 'b, DB>) -> QueryResult<()> {
72+
pass.push_sql(Fn::FUNCTION_NAME);
73+
pass.push_sql("(");
74+
self.prefix.walk_ast(pass.reborrow())?;
75+
self.function.walk_arguments(pass.reborrow())?;
76+
self.order.walk_ast(pass.reborrow())?;
77+
pass.push_sql(")");
78+
self.within_group.walk_ast(pass.reborrow())?;
79+
self.filter.walk_ast(pass.reborrow())?;
80+
self.window.walk_ast(pass.reborrow())?;
81+
Ok(())
82+
}
83+
}
84+
85+
impl<Fn, Prefix, Order, Filter, Within, GB> ValidGrouping<GB>
86+
for AggregateExpression<Fn, Prefix, Order, Filter, Within>
87+
where
88+
Fn: ValidGrouping<GB>,
89+
{
90+
type IsAggregate = <Fn as ValidGrouping<GB>>::IsAggregate;
91+
}
92+
93+
impl<Fn, Prefix, Order, Filter, Within, GB, Partition, WindowOrder, Frame> ValidGrouping<GB>
94+
for AggregateExpression<
95+
Fn,
96+
Prefix,
97+
Order,
98+
Filter,
99+
Within,
100+
OverClause<Partition, WindowOrder, Frame>,
101+
>
102+
where
103+
Fn: IsWindowFunction,
104+
Fn::ArgTypes: ValidGrouping<GB>,
105+
{
106+
// not sure about that, check this
107+
type IsAggregate = <Fn::ArgTypes as ValidGrouping<GB>>::IsAggregate;
108+
}
109+
110+
impl<Fn, Prefix, Order, Filter, Within, Window> Expression
111+
for AggregateExpression<Fn, Prefix, Order, Filter, Within, Window>
112+
where
113+
Fn: Expression,
114+
{
115+
type SqlType = <Fn as Expression>::SqlType;
116+
}
117+
118+
impl<Fn, Prefix, Order, Filter, Within, Window, QS> AppearsOnTable<QS>
119+
for AggregateExpression<Fn, Prefix, Order, Filter, Within, Window>
120+
where
121+
Self: Expression,
122+
Fn: AppearsOnTable<QS>,
123+
{
124+
}
125+
126+
impl<Fn, Prefix, Order, Filter, Within, Window, QS> SelectableExpression<QS>
127+
for AggregateExpression<Fn, Prefix, Order, Filter, Within, Window>
128+
where
129+
Self: Expression,
130+
Fn: SelectableExpression<QS>,
131+
{
132+
}
133+
134+
/// A helper marker trait that this function is a window function
135+
/// This is only used to provide the gate the `WindowExpressionMethods`
136+
/// trait onto, not to check if the construct is valid for a given backend
137+
/// This check is postponed to building the query via `QueryFragment`
138+
/// (We have access to the DB type there)
139+
pub trait IsWindowFunction {
140+
/// A tuple of all arg types
141+
type ArgTypes;
142+
}
143+
144+
/// A helper marker trait that this function is a valid window function
145+
/// for the given backend
146+
/// this trait is used to transport information that
147+
/// a certain function can be used as window function for a specific
148+
/// backend
149+
/// We allow to specialize this function for different SQL dialects
150+
pub trait WindowFunctionFragment<Fn, DB: Backend, SP = NotSpecialized> {}
151+
152+
/// A helper marker trait that this function as a aggregate function
153+
/// This is only used to provide the gate the `AggregateExpressionMethods`
154+
/// trait onto, not to check if the construct is valid for a given backend
155+
/// This check is postponed to building the query via `QueryFragment`
156+
/// (We have access to the DB type there)
157+
pub trait IsAggregateFunction {}
158+
159+
/// A specialized QueryFragment helper trait that allows us to walk the function name
160+
/// and the function arguments in seperate steps
161+
pub trait FunctionFragment<DB: Backend> {
162+
/// The name of the sql function
163+
const FUNCTION_NAME: &'static str;
164+
165+
/// Walk the function argument part (everything between ())
166+
fn walk_arguments<'b>(&'b self, pass: AstPass<'_, 'b, DB>) -> QueryResult<()>;
167+
}
168+
169+
// TODO: write helper types for all functions
170+
// TODO: write doc tests for all functions
171+
/// Expression methods to build aggregate function expressions
172+
pub trait AggregateExpressionMethods: Sized {
173+
/// `DISTINCT` modifier
174+
fn distinct(self) -> Self::Output
175+
where
176+
Self: DistinctDsl,
177+
{
178+
<Self as DistinctDsl>::distinct(self)
179+
}
180+
181+
/// `ALL` modifier
182+
fn all(self) -> Self::Output
183+
where
184+
Self: AllDsl,
185+
{
186+
<Self as AllDsl>::all(self)
187+
}
188+
189+
/// Add an aggregate filter
190+
fn filter_aggregate<P>(self, f: P) -> Self::Output
191+
where
192+
P: AsExpression<Bool>,
193+
Self: FilterDsl<P::Expression>,
194+
{
195+
<Self as FilterDsl<P::Expression>>::filter(self, f.as_expression())
196+
}
197+
198+
/// Add an aggregate order
199+
fn order_aggregate<O>(self, o: O) -> Self::Output
200+
where
201+
Self: OrderAggregateDsl<O>,
202+
{
203+
<Self as OrderAggregateDsl<O>>::order(self, o)
204+
}
205+
206+
// todo: restrict this to order set aggregates
207+
// (we don't have any in diesel yet)
208+
#[doc(hidden)] // for now
209+
fn within_group<O>(self, o: O) -> Self::Output
210+
where
211+
Self: WithinGroupDsl<O>,
212+
{
213+
<Self as WithinGroupDsl<O>>::within_group(self, o)
214+
}
215+
}
216+
217+
impl<T> AggregateExpressionMethods for T {}
218+
219+
/// Methods to construct a window function call
220+
pub trait WindowExpressionMethods: Sized {
221+
/// Turn a function call into a window function call
222+
fn over(self) -> Self::Output
223+
where
224+
Self: OverDsl,
225+
{
226+
<Self as OverDsl>::over(self)
227+
}
228+
229+
/// Add a filter to the current window function
230+
// todo: do we want `or_filter` as well?
231+
fn filter_window<P>(self, f: P) -> Self::Output
232+
where
233+
P: AsExpression<Bool>,
234+
Self: FilterDsl<P::Expression>,
235+
{
236+
<Self as FilterDsl<P::Expression>>::filter(self, f.as_expression())
237+
}
238+
239+
/// Add a partition clause to the current window function
240+
fn partition_by<E>(self, expr: E) -> Self::Output
241+
where
242+
Self: PartitionByDsl<E>,
243+
{
244+
<Self as PartitionByDsl<E>>::partition_by(self, expr)
245+
}
246+
247+
/// Add a order clause to the current window function
248+
fn window_order<E>(self, expr: E) -> Self::Output
249+
where
250+
Self: OrderWindowDsl<E>,
251+
{
252+
<Self as OrderWindowDsl<E>>::order(self, expr)
253+
}
254+
255+
/// Add a frame clause to the current window function
256+
fn frame_by<E>(self, expr: E) -> Self::Output
257+
where
258+
Self: FrameDsl<E>,
259+
{
260+
<Self as FrameDsl<E>>::frame(self, expr)
261+
}
262+
}
263+
264+
impl<T> WindowExpressionMethods for T {}

0 commit comments

Comments
 (0)