Skip to content

Commit 891b76b

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 b170af7 commit 891b76b

File tree

15 files changed

+1190
-3
lines changed

15 files changed

+1190
-3
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
use crate::backend::Backend;
2+
use crate::expression::{is_aggregate, AsExpression, ValidGrouping};
3+
use crate::query_builder::{AstPass, 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 crate::query_builder::QueryFragment<diesel::pg::Pg> for $name {
13+
fn walk_ast<'b>(
14+
&'b self,
15+
_pass: crate::query_builder::AstPass<'_, 'b, diesel::pg::Pg>,
16+
) -> crate::QueryResult<()> {
17+
Ok(())
18+
}
19+
}
20+
};
21+
}
22+
23+
mod aggregate_filter;
24+
mod aggregate_order;
25+
pub(crate) mod frame_clause;
26+
mod over_clause;
27+
mod partition_by;
28+
mod prefix;
29+
mod within_group;
30+
31+
use self::aggregate_filter::{Filter, FilterDsl, NoFilter};
32+
use self::aggregate_order::{NoOrder, Order, OrderAggregateDsl, OrderWindowDsl};
33+
use self::frame_clause::{FrameDsl, NoFrame};
34+
use self::over_clause::{NoWindow, OverClause, OverDsl};
35+
use self::partition_by::{PartitionBy, PartitionByDsl};
36+
use self::prefix::{All, AllDsl, Distinct, DistinctDsl, NoPrefix};
37+
use self::within_group::{NoWithin, WithinGroup, WithinGroupDsl};
38+
39+
#[derive(QueryId)]
40+
pub struct AggregateExpression<
41+
Fn,
42+
Prefix = NoPrefix,
43+
Order = NoOrder,
44+
Filter = NoFilter,
45+
Within = NoWithin,
46+
Window = NoWindow,
47+
> {
48+
prefix: Prefix,
49+
function: Fn,
50+
order: Order,
51+
filter: Filter,
52+
within_group: Within,
53+
window: Window,
54+
}
55+
56+
impl<Fn, Prefix, Order, Filter, Within, Window> QueryFragment<diesel::pg::Pg>
57+
for AggregateExpression<Fn, Prefix, Order, Filter, Within, Window>
58+
where
59+
Fn: FunctionFragment<diesel::pg::Pg>,
60+
Prefix: QueryFragment<diesel::pg::Pg>,
61+
Order: QueryFragment<diesel::pg::Pg>,
62+
Filter: QueryFragment<diesel::pg::Pg>,
63+
Within: QueryFragment<diesel::pg::Pg>,
64+
Window: QueryFragment<diesel::pg::Pg>,
65+
{
66+
fn walk_ast<'b>(&'b self, mut pass: AstPass<'_, 'b, diesel::pg::Pg>) -> QueryResult<()> {
67+
self.function.walk_name(pass.reborrow())?;
68+
pass.push_sql("(");
69+
self.prefix.walk_ast(pass.reborrow())?;
70+
self.function.walk_arguments(pass.reborrow())?;
71+
self.order.walk_ast(pass.reborrow())?;
72+
pass.push_sql(")");
73+
self.within_group.walk_ast(pass.reborrow())?;
74+
self.filter.walk_ast(pass.reborrow())?;
75+
self.window.walk_ast(pass.reborrow())?;
76+
Ok(())
77+
}
78+
}
79+
80+
impl<Fn, Prefix, Order, Filter, Within, GB> ValidGrouping<GB>
81+
for AggregateExpression<Fn, Prefix, Order, Filter, Within>
82+
where
83+
Fn: ValidGrouping<GB>,
84+
{
85+
type IsAggregate = <Fn as ValidGrouping<GB>>::IsAggregate;
86+
}
87+
88+
impl<Fn, Prefix, Order, Filter, Within, GB, Partition, WindowOrder, Frame> ValidGrouping<GB>
89+
for AggregateExpression<
90+
Fn,
91+
Prefix,
92+
Order,
93+
Filter,
94+
Within,
95+
OverClause<Partition, WindowOrder, Frame>,
96+
>
97+
where
98+
Fn: ValidGrouping<GB>,
99+
{
100+
// not sure about that, check this
101+
type IsAggregate = is_aggregate::No;
102+
}
103+
104+
impl<Fn, Prefix, Order, Filter, Within, Window> Expression
105+
for AggregateExpression<Fn, Prefix, Order, Filter, Within, Window>
106+
where
107+
Fn: Expression,
108+
{
109+
type SqlType = <Fn as Expression>::SqlType;
110+
}
111+
112+
impl<Fn, Prefix, Order, Filter, Within, Window, QS> AppearsOnTable<QS>
113+
for AggregateExpression<Fn, Prefix, Order, Filter, Within, Window>
114+
where
115+
Self: Expression,
116+
Fn: AppearsOnTable<QS>,
117+
{
118+
}
119+
120+
impl<Fn, Prefix, Order, Filter, Within, Window, QS> SelectableExpression<QS>
121+
for AggregateExpression<Fn, Prefix, Order, Filter, Within, Window>
122+
where
123+
Self: Expression,
124+
Fn: SelectableExpression<QS>,
125+
{
126+
}
127+
128+
pub trait WindowFunction {}
129+
pub trait AggregateFunction {}
130+
pub trait FunctionFragment<DB: Backend> {
131+
fn walk_name<'b>(&'b self, pass: AstPass<'_, 'b, DB>) -> QueryResult<()>;
132+
133+
fn walk_arguments<'b>(&'b self, pass: AstPass<'_, 'b, DB>) -> QueryResult<()>;
134+
}
135+
136+
// TODO: write helper types for all functions
137+
pub trait AggregateExpressionMethods: Sized {
138+
fn distinct(self) -> Self::Output
139+
where
140+
Self: DistinctDsl,
141+
{
142+
<Self as DistinctDsl>::distinct(self)
143+
}
144+
145+
fn all(self) -> Self::Output
146+
where
147+
Self: AllDsl,
148+
{
149+
<Self as AllDsl>::all(self)
150+
}
151+
152+
// todo: do we want `or_filter` as well?
153+
fn filter_aggregate<P>(self, f: P) -> Self::Output
154+
where
155+
P: AsExpression<Bool>,
156+
Self: FilterDsl<P::Expression>,
157+
{
158+
<Self as FilterDsl<P::Expression>>::filter(self, f.as_expression())
159+
}
160+
161+
fn order_aggregate<O>(self, o: O) -> Self::Output
162+
where
163+
Self: OrderAggregateDsl<O>,
164+
{
165+
<Self as OrderAggregateDsl<O>>::order(self, o)
166+
}
167+
168+
// todo: restrict this to order set aggregates
169+
// (we don't have any in diesel yet)
170+
fn within_group<O>(self, o: O) -> Self::Output
171+
where
172+
Self: WithinGroupDsl<O>,
173+
{
174+
<Self as WithinGroupDsl<O>>::within_group(self, o)
175+
}
176+
}
177+
178+
impl<T> AggregateExpressionMethods for T {}
179+
180+
pub trait WindowExpressionMethods: Sized {
181+
fn over(self) -> Self::Output
182+
where
183+
Self: OverDsl,
184+
{
185+
<Self as OverDsl>::over(self)
186+
}
187+
188+
// todo: do we want `or_filter` as well?
189+
fn filter_window<P>(self, f: P) -> Self::Output
190+
where
191+
P: AsExpression<Bool>,
192+
Self: FilterDsl<P::Expression>,
193+
{
194+
<Self as FilterDsl<P::Expression>>::filter(self, f.as_expression())
195+
}
196+
197+
fn partition_by<E>(self, expr: E) -> Self::Output
198+
where
199+
Self: PartitionByDsl<E>,
200+
{
201+
<Self as PartitionByDsl<E>>::partition_by(self, expr)
202+
}
203+
204+
fn window_order<E>(self, expr: E) -> Self::Output
205+
where
206+
Self: OrderWindowDsl<E>,
207+
{
208+
<Self as OrderWindowDsl<E>>::order(self, expr)
209+
}
210+
211+
fn frame_by<E>(self, expr: E) -> Self::Output
212+
where
213+
Self: FrameDsl<E>,
214+
{
215+
<Self as FrameDsl<E>>::frame(self, expr)
216+
}
217+
}
218+
219+
impl<T> WindowExpressionMethods for T {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
use super::aggregate_order::NoOrder;
2+
use super::prefix::NoPrefix;
3+
use super::AggregateExpression;
4+
use super::AggregateFunction;
5+
use super::NoWindow;
6+
use super::NoWithin;
7+
use crate::query_builder::where_clause::NoWhereClause;
8+
use crate::query_builder::where_clause::WhereAnd;
9+
use crate::query_builder::QueryFragment;
10+
use crate::query_builder::{AstPass, QueryId};
11+
use crate::sql_types::Bool;
12+
use crate::Expression;
13+
use crate::QueryResult;
14+
15+
empty_clause!(NoFilter);
16+
17+
#[derive(QueryId, Copy, Clone)]
18+
pub struct Filter<P>(P);
19+
20+
impl<P> QueryFragment<diesel::pg::Pg> for Filter<P>
21+
where
22+
P: QueryFragment<diesel::pg::Pg>,
23+
{
24+
fn walk_ast<'b>(&'b self, mut pass: AstPass<'_, 'b, diesel::pg::Pg>) -> QueryResult<()> {
25+
pass.push_sql(" FILTER (");
26+
self.0.walk_ast(pass.reborrow())?;
27+
pass.push_sql(")");
28+
Ok(())
29+
}
30+
}
31+
32+
pub trait FilterDsl<P> {
33+
type Output;
34+
35+
fn filter(self, f: P) -> Self::Output;
36+
}
37+
38+
impl<P, T> FilterDsl<P> for T
39+
where
40+
T: AggregateFunction,
41+
// todo: allow nullable bools here
42+
P: Expression<SqlType = Bool>,
43+
{
44+
type Output =
45+
AggregateExpression<T, NoPrefix, NoOrder, Filter<<NoWhereClause as WhereAnd<P>>::Output>>;
46+
47+
fn filter(self, f: P) -> Self::Output {
48+
AggregateExpression {
49+
prefix: NoPrefix,
50+
function: self,
51+
order: NoOrder,
52+
filter: Filter(NoWhereClause.and(f)),
53+
within_group: NoWithin,
54+
window: NoWindow,
55+
}
56+
}
57+
}
58+
59+
impl<Fn, P, Prefix, Order, F, Within, Window> FilterDsl<P>
60+
for AggregateExpression<Fn, Prefix, Order, Filter<F>, Within, Window>
61+
where
62+
// todo: allow nullable bools here
63+
F: WhereAnd<P>,
64+
{
65+
type Output =
66+
AggregateExpression<Fn, Prefix, Order, Filter<<F as WhereAnd<P>>::Output>, Within, Window>;
67+
68+
fn filter(self, f: P) -> Self::Output {
69+
AggregateExpression {
70+
prefix: self.prefix,
71+
function: self.function,
72+
order: self.order,
73+
filter: Filter(WhereAnd::<P>::and(self.filter.0, f)),
74+
within_group: self.within_group,
75+
window: self.window,
76+
}
77+
}
78+
}
79+
80+
impl<Fn, P, Prefix, Order, Within, Window> FilterDsl<P>
81+
for AggregateExpression<Fn, Prefix, Order, NoFilter, Within, Window>
82+
where
83+
// todo: allow nullable bools here
84+
NoWhereClause: WhereAnd<P>,
85+
{
86+
type Output = AggregateExpression<
87+
Fn,
88+
Prefix,
89+
Order,
90+
Filter<<NoWhereClause as WhereAnd<P>>::Output>,
91+
Within,
92+
Window,
93+
>;
94+
95+
fn filter(self, f: P) -> Self::Output {
96+
AggregateExpression {
97+
prefix: self.prefix,
98+
function: self.function,
99+
order: self.order,
100+
filter: Filter(WhereAnd::<P>::and(NoWhereClause, f)),
101+
within_group: self.within_group,
102+
window: self.window,
103+
}
104+
}
105+
}

0 commit comments

Comments
 (0)