Skip to content

Commit 0b91994

Browse files
committed
Support generic and aggregate sql_function!s
This extends the new syntax of `sql_function!` to allow generics. To test this, I wanted to use it internally by replacing some of our "special" function macros with normal `sql_function!`. To do this for our aggregate functions, I also needed to add a way to skip the `NonAggregate` impl. Generic functions were easier than expected, though capturing the type parameters and their bounds was annoying as all hell since `<>` isn't a token tree... Right now we don't support multiple bounds on a type either, which prevents me from moving `max` and `min` over to this. I'm not sure how I'm going to handle that, since you can't capture a sequence separated by `+`... Ideally I'd just be able to do `$($param:ident $(: $bound:ty)*),*` since `Foo + Bar` parses as a valid type. However, Rust doesn't allow a type token in the bounds position, only `path` and `lifetime`. I think we'll probably just have to support where clauses, and say "if you need more than one bound, put it in a where clause". Dealing with `#[aggregate]` was a bit more annoying, but it's something we will need to eventually do in order to support `#[sql_name]` anyway. We just do our typical token munching strategy, going through one meta item at a time and letting `macro_rules!` branching do the work for us as we recursively call ourselves. The main thing that makes this annoying is that we can never capture anything as a `meta` token, since once you do that it no longer can be matched against, ever. Luckily for attributes in particular this is not a huge annoyance, as the attribute itself is surrounded by `[]` making it a single token tree.
1 parent e67985b commit 0b91994

4 files changed

Lines changed: 219 additions & 139 deletions

File tree

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,23 @@ for Rust libraries in [RFC #1105](https://github.com/rust-lang/rfcs/blob/master/
1212

1313
[record-1-3-0]: http://docs.diesel.rs/diesel/pg/types/sql_types/struct.Record.html
1414

15+
* `sql_function!` now supports generic functions. See [the documentation for
16+
`sql_function!`][sql-function-1-3-0] for more details.
17+
18+
* `sql_function!` now supports aggregate functions like `sum` and `max`, by
19+
annotating them with `#[aggregate]`. This skips the implementation of
20+
`NonAggregate` for your function. See [the documentation for
21+
`sql_function!`][sql-function-1-3-0] for more details.
22+
1523
### Changed
1624

1725
* `sql_function!` has been redesigned. The syntax is now `sql_function!(fn
1826
lower(x: Text) -> Text);`. The output of the new syntax is slightly different
1927
than what was generated in the past. See [the documentation for
2028
`sql_function!`][sql-function-1-3-0] for more details.
2129

30+
[sql-function-1-3-0]: http://docs.diesel.rs/diesel/macro.sql_function.html
31+
2232
### Fixed
2333

2434
* `diesel print-schema` and `infer_schema!` now properly handle unsigned types
Lines changed: 65 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -1,118 +1,69 @@
1-
use backend::Backend;
2-
use expression::Expression;
3-
use query_builder::*;
4-
use result::QueryResult;
51
use sql_types::Foldable;
62

7-
macro_rules! fold_function {
8-
($fn_name:ident, $type_name:ident, $operator:expr, $docs:expr) => {
9-
#[doc=$docs]
10-
pub fn $fn_name<ST, T>(t: T) -> $type_name<T> where
11-
ST: Foldable,
12-
T: Expression<SqlType=ST>,
13-
{
14-
$type_name {
15-
target: t,
16-
}
17-
}
18-
19-
#[derive(Debug, Clone, Copy, QueryId)]
20-
#[doc(hidden)]
21-
pub struct $type_name<T> {
22-
target: T,
23-
}
24-
25-
impl<ST, T> Expression for $type_name<T> where
26-
ST: Foldable,
27-
T: Expression<SqlType=ST>
28-
{
29-
type SqlType = <<T as Expression>::SqlType as Foldable>::$type_name;
30-
}
31-
32-
impl<T, DB> QueryFragment<DB> for $type_name<T> where
33-
T: Expression + QueryFragment<DB>,
34-
DB: Backend,
35-
{
36-
fn walk_ast(&self, mut out: AstPass<DB>) -> QueryResult<()> {
37-
out.push_sql(concat!($operator, "("));
38-
self.target.walk_ast(out.reborrow())?;
39-
out.push_sql(")");
40-
Ok(())
41-
}
42-
}
43-
44-
impl_selectable_expression!($type_name<T>);
45-
}
3+
sql_function! {
4+
/// Represents a SQL `SUM` function. This function can only take types which are
5+
/// Foldable.
6+
///
7+
/// # Examples
8+
///
9+
/// ```rust
10+
/// # #[macro_use] extern crate diesel;
11+
/// # include!("../../doctest_setup.rs");
12+
/// # use diesel::dsl::*;
13+
/// #
14+
/// # fn main() {
15+
/// # use schema::animals::dsl::*;
16+
/// # let connection = establish_connection();
17+
/// assert_eq!(Ok(Some(12i64)), animals.select(sum(legs)).first(&connection));
18+
/// # }
19+
/// ```
20+
#[aggregate]
21+
fn sum<ST: Foldable>(expr: ST) -> ST::Sum;
4622
}
4723

48-
fold_function!(
49-
sum,
50-
Sum,
51-
"SUM",
52-
"Represents a SQL `SUM` function. This function can only take types which are
53-
Foldable.
54-
55-
# Examples
56-
57-
```rust
58-
# #[macro_use] extern crate diesel;
59-
# include!(\"../../doctest_setup.rs\");
60-
# use diesel::dsl::*;
61-
#
62-
# fn main() {
63-
# use schema::animals::dsl::*;
64-
# let connection = establish_connection();
65-
assert_eq!(Ok(Some(12i64)), animals.select(sum(legs)).first(&connection));
66-
# }
67-
"
68-
);
69-
70-
fold_function!(
71-
avg,
72-
Avg,
73-
"AVG",
74-
r#"Represents a SQL `AVG` function. This function can only take types which are
75-
Foldable.
76-
77-
# Examples
78-
79-
```rust
80-
# #[macro_use] extern crate diesel;
81-
# include!("../../doctest_setup.rs");
82-
# use diesel::dsl::*;
83-
# #[cfg(feature = "bigdecimal")]
84-
# extern crate bigdecimal;
85-
#
86-
# fn main() {
87-
# run_test().unwrap();
88-
# }
89-
#
90-
# table! {
91-
# numbers (number) {
92-
# number -> Integer,
93-
# }
94-
# }
95-
#
96-
# #[cfg(all(feature = "numeric", any(feature = "postgres", not(feature = "sqlite"))))]
97-
# fn run_test() -> QueryResult<()> {
98-
# use bigdecimal::BigDecimal;
99-
# use numbers::dsl::*;
100-
# let conn = establish_connection();
101-
# conn.execute("DROP TABLE IF EXISTS numbers")?;
102-
# conn.execute("CREATE TABLE numbers (number INTEGER)")?;
103-
diesel::insert_into(numbers)
104-
.values(&vec![number.eq(1), number.eq(2)])
105-
.execute(&conn)?;
106-
let average = numbers.select(avg(number)).get_result(&conn)?;
107-
let expected = "1.5".parse::<BigDecimal>().unwrap();
108-
assert_eq!(Some(expected), average);
109-
# Ok(())
110-
# }
111-
#
112-
# #[cfg(not(all(feature = "numeric", any(feature = "postgres", not(feature = "sqlite")))))]
113-
# fn run_test() -> QueryResult<()> {
114-
# Ok(())
115-
# }
116-
```
117-
"#
118-
);
24+
sql_function! {
25+
/// Represents a SQL `AVG` function. This function can only take types which are
26+
/// Foldable.
27+
///
28+
/// # Examples
29+
///
30+
/// ```rust
31+
/// # #[macro_use] extern crate diesel;
32+
/// # include!("../../doctest_setup.rs");
33+
/// # use diesel::dsl::*;
34+
/// # #[cfg(feature = "bigdecimal")]
35+
/// # extern crate bigdecimal;
36+
/// #
37+
/// # fn main() {
38+
/// # run_test().unwrap();
39+
/// # }
40+
/// #
41+
/// # table! {
42+
/// # numbers (number) {
43+
/// # number -> Integer,
44+
/// # }
45+
/// # }
46+
/// #
47+
/// # #[cfg(all(feature = "numeric", any(feature = "postgres", not(feature = "sqlite"))))]
48+
/// # fn run_test() -> QueryResult<()> {
49+
/// # use bigdecimal::BigDecimal;
50+
/// # use numbers::dsl::*;
51+
/// # let conn = establish_connection();
52+
/// # conn.execute("DROP TABLE IF EXISTS numbers")?;
53+
/// # conn.execute("CREATE TABLE numbers (number INTEGER)")?;
54+
/// diesel::insert_into(numbers)
55+
/// .values(&vec![number.eq(1), number.eq(2)])
56+
/// .execute(&conn)?;
57+
/// let average = numbers.select(avg(number)).get_result(&conn)?;
58+
/// let expected = "1.5".parse::<BigDecimal>().unwrap();
59+
/// assert_eq!(Some(expected), average);
60+
/// # Ok(())
61+
/// # }
62+
/// #
63+
/// # #[cfg(not(all(feature = "numeric", any(feature = "postgres", not(feature = "sqlite")))))]
64+
/// # fn run_test() -> QueryResult<()> {
65+
/// # Ok(())
66+
/// # }
67+
#[aggregate]
68+
fn avg<ST: Foldable>(expr: ST) -> ST::Avg;
69+
}

diesel/src/expression/functions/helper_types.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#![allow(non_camel_case_types)]
22

3-
use dsl::AsExprOf;
3+
use dsl::{AsExprOf, SqlTypeOf};
44
use expression::grouped::Grouped;
55
use expression::operators;
66
use sql_types::Bool;
@@ -20,7 +20,7 @@ pub type max<Expr> = super::aggregate_ordering::Max<Expr>;
2020
pub type min<Expr> = super::aggregate_ordering::Min<Expr>;
2121

2222
/// The return type of [`sum(expr)`](../dsl/fn.sum.html)
23-
pub type sum<Expr> = super::aggregate_folding::Sum<Expr>;
23+
pub type sum<Expr> = super::aggregate_folding::sum::HelperType<SqlTypeOf<Expr>, Expr>;
2424

2525
/// The return type of [`avg(expr)`](../dsl/fn.avg.html)
26-
pub type avg<Expr> = super::aggregate_folding::Avg<Expr>;
26+
pub type avg<Expr> = super::aggregate_folding::avg::HelperType<SqlTypeOf<Expr>, Expr>;

0 commit comments

Comments
 (0)