Skip to content

Commit b950723

Browse files
committed
Add first class support for newtypes in Queryable and QueryableByName
A consistent problem that's existed for us has been "what do we do about library X when Diesel doesn't want to care about X, and X doesn't want to care about Diesel?" Another permutation of this problem has been folks wanting type conversions that we're never going to support (such as `impl FromSql<Binary, Sqlite> for Uuid`). Ultimately the answer for this has pretty much always come down to newtypes. Whether you're doing it in your app, or having them provided by a library, that's the only real solution to the orphan rule here. This commit introduces a new API to all our relevant deserialization derives: `#[deserialize_as]`. The API is pretty straightforward. You give us the name of a type. That type has to satisfy whatever constraints would exist if you used that type directly on your struct. It also has to implement `Into` for whatever type is on your struct. This is similar to, but slightly different than `#[serde(deserialize_with)]`, in that it takes a type name rather than a function. We cannot mirror serde's API here, as we use this in a situation where we cannot infer the type you need based on the argument to the function. I intend to follow this PR with an equivalent `#[serialize_as]` attribute for `Insertable` and `AsChangeset`. Unresolved Questions -------------------- I'm really not liking using `#[diesel(...)]` to avoid namespace issues. The main problem with doing that is that we cannot communicate to the compiler what is allowed inside of it. We also cannot use our normal "warn if there's unrecognized options" strategy in this case, since there can be multiple derives that use `#[diesel(...)]` on the same attribute. The result of this is that we silently ignore any abnormal options in a `#[diesel]` attribute. While writing this, I wrote `deserialize_with` instead of `deserialize_as` twice and couldn't figure out what went wront. There's a few options here, but I'm not sure which is best. One would be for us to maintain a global list of all options to `#[diesel]` that all derives recognize, and warn if something doesn't match that list. The main downsides to that implemenation is that it's very brittle, and we won't warn if you use something only recognized by `#[derive(QueryableByName)]` when that derive isn't present. The other option is to deprecated `#[diesel(...)]`, and replace all uses with `#[diesel_...]`. This is the option I'm currently leaning towards (though I do not think it needs to be done in this PR), since the only downside is that if you are passing more than one thing to `#[diesel]`, it will need to be on multiple lines. I do not think we currently have any cases where that is even reasonable.
1 parent 1f23c50 commit b950723

6 files changed

Lines changed: 189 additions & 16 deletions

File tree

diesel/src/deserialize.rs

Lines changed: 155 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,26 @@ pub type Result<T> = result::Result<T, Box<Error + Send + Sync>>;
1515
/// Types which implement `Queryable` represent the result of a SQL query. This
1616
/// does not necessarily mean they represent a single database table.
1717
///
18-
/// This trait can be derived automatically using `#[derive(Queryable)]`. This
19-
/// trait can only be derived for structs, not enums.
20-
///
2118
/// Diesel represents the return type of a query as a tuple. The purpose of this
2219
/// trait is to convert from a tuple of Rust values that have been deserialized
2320
/// into your struct.
2421
///
22+
/// # Deriving
23+
///
24+
/// This trait can be derived automatically using `#[derive(Queryable)]`. This
25+
/// trait can only be derived for structs, not enums.
26+
///
2527
/// When this trait is derived, it will assume that the order of fields on your
2628
/// struct match the order of the fields in the query. This means that field
2729
/// order is significant if you are using `#[derive(Queryable)]`. Field name has
2830
/// no effect.
2931
///
32+
/// To provide custom deserialization behavior for a field, you can use
33+
/// `#[diesel(deserialize_as = "Type")]`. If this attribute is present, Diesel
34+
/// will deserialize into that type, rather than the type on your struct and
35+
/// call `.into` to convert it. This can be used to add custom behavior for a
36+
/// single field, or use types that are otherwise unsupported by Diesel.
37+
///
3038
/// # Examples
3139
///
3240
/// If we just want to map a query to our struct, we can use `derive`.
@@ -55,8 +63,59 @@ pub type Result<T> = result::Result<T, Box<Error + Send + Sync>>;
5563
/// # }
5664
/// ```
5765
///
58-
/// If we want to do additional work during deserialization, we can implement
59-
/// the trait ourselves.
66+
/// If we want to do additional work during deserialization, we can use
67+
/// `deserialize_as` to use a different implementation.
68+
///
69+
/// ```rust
70+
/// # #[macro_use] extern crate diesel;
71+
/// # include!("doctest_setup.rs");
72+
/// #
73+
/// # use schema::users;
74+
/// # use diesel::backend::Backend;
75+
/// # use diesel::deserialize::Queryable;
76+
/// #
77+
/// struct LowercaseString(String);
78+
///
79+
/// impl Into<String> for LowercaseString {
80+
/// fn into(self) -> String {
81+
/// self.0
82+
/// }
83+
/// }
84+
///
85+
/// impl<DB, ST> Queryable<ST, DB> for LowercaseString
86+
/// where
87+
/// DB: Backend,
88+
/// String: Queryable<ST, DB>,
89+
/// {
90+
/// type Row = <String as Queryable<ST, DB>>::Row;
91+
///
92+
/// fn build(row: Self::Row) -> Self {
93+
/// LowercaseString(String::build(row).to_lowercase())
94+
/// }
95+
/// }
96+
///
97+
/// #[derive(Queryable, PartialEq, Debug)]
98+
/// struct User {
99+
/// id: i32,
100+
/// #[diesel(deserialize_as = "LowercaseString")]
101+
/// name: String,
102+
/// }
103+
///
104+
/// # fn main() {
105+
/// # run_test();
106+
/// # }
107+
/// #
108+
/// # fn run_test() -> QueryResult<()> {
109+
/// # use schema::users::dsl::*;
110+
/// # let connection = establish_connection();
111+
/// let first_user = users.first(&connection)?;
112+
/// let expected = User { id: 1, name: "sean".into() };
113+
/// assert_eq!(expected, first_user);
114+
/// # Ok(())
115+
/// # }
116+
/// ```
117+
///
118+
/// Alternatively, we can implement the trait for our struct manually.
60119
///
61120
/// ```rust
62121
/// # #[macro_use] extern crate diesel;
@@ -98,6 +157,7 @@ pub type Result<T> = result::Result<T, Box<Error + Send + Sync>>;
98157
/// assert_eq!(expected, first_user);
99158
/// # Ok(())
100159
/// # }
160+
/// ```
101161
pub trait Queryable<ST, DB>
102162
where
103163
DB: Backend,
@@ -133,7 +193,97 @@ where
133193
/// If a field is another struct which implements `QueryableByName`, instead of
134194
/// a column, you can annotate that struct with `#[diesel(embed)]`
135195
///
196+
/// To provide custom deserialization behavior for a field, you can use
197+
/// `#[diesel(deserialize_as = "Type")]`. If this attribute is present, Diesel
198+
/// will deserialize into that type, rather than the type on your struct and
199+
/// call `.into` to convert it. This can be used to add custom behavior for a
200+
/// single field, or use types that are otherwise unsupported by Diesel.
201+
///
136202
/// [`sql_query`]: ../fn.sql_query.html
203+
///
204+
/// # Examples
205+
///
206+
///
207+
/// If we just want to map a query to our struct, we can use `derive`.
208+
///
209+
/// ```rust
210+
/// # #[macro_use] extern crate diesel;
211+
/// # include!("doctest_setup.rs");
212+
/// # use schema::users;
213+
/// # use diesel::sql_query;
214+
/// #
215+
/// #[derive(QueryableByName, PartialEq, Debug)]
216+
/// #[table_name = "users"]
217+
/// struct User {
218+
/// id: i32,
219+
/// name: String,
220+
/// }
221+
///
222+
/// # fn main() {
223+
/// # run_test();
224+
/// # }
225+
/// #
226+
/// # fn run_test() -> QueryResult<()> {
227+
/// # let connection = establish_connection();
228+
/// let first_user = sql_query("SELECT * FROM users ORDER BY id LIMIT 1")
229+
/// .get_result(&connection)?;
230+
/// let expected = User { id: 1, name: "Sean".into() };
231+
/// assert_eq!(expected, first_user);
232+
/// # Ok(())
233+
/// # }
234+
/// ```
235+
///
236+
/// If we want to do additional work during deserialization, we can use
237+
/// `deserialize_as` to use a different implementation.
238+
///
239+
/// ```rust
240+
/// # #[macro_use] extern crate diesel;
241+
/// # include!("doctest_setup.rs");
242+
/// # use diesel::sql_query;
243+
/// # use schema::users;
244+
/// # use diesel::backend::Backend;
245+
/// # use diesel::deserialize::{self, FromSql};
246+
/// #
247+
/// struct LowercaseString(String);
248+
///
249+
/// impl Into<String> for LowercaseString {
250+
/// fn into(self) -> String {
251+
/// self.0
252+
/// }
253+
/// }
254+
///
255+
/// impl<DB, ST> FromSql<ST, DB> for LowercaseString
256+
/// where
257+
/// DB: Backend,
258+
/// String: FromSql<ST, DB>,
259+
/// {
260+
/// fn from_sql(bytes: Option<&DB::RawValue>) -> deserialize::Result<Self> {
261+
/// String::from_sql(bytes)
262+
/// .map(|s| LowercaseString(s.to_lowercase()))
263+
/// }
264+
/// }
265+
///
266+
/// #[derive(QueryableByName, PartialEq, Debug)]
267+
/// #[table_name = "users"]
268+
/// struct User {
269+
/// id: i32,
270+
/// #[diesel(deserialize_as = "LowercaseString")]
271+
/// name: String,
272+
/// }
273+
///
274+
/// # fn main() {
275+
/// # run_test();
276+
/// # }
277+
/// #
278+
/// # fn run_test() -> QueryResult<()> {
279+
/// # let connection = establish_connection();
280+
/// let first_user = sql_query("SELECT * FROM users ORDER BY id LIMIT 1")
281+
/// .get_result(&connection)?;
282+
/// let expected = User { id: 1, name: "sean".into() };
283+
/// assert_eq!(expected, first_user);
284+
/// # Ok(())
285+
/// # }
286+
/// ```
137287
pub trait QueryableByName<DB>
138288
where
139289
Self: Sized,

diesel_derives/src/field.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use proc_macro2::{self, Ident, Span};
22
use quote::ToTokens;
3+
use std::borrow::Cow;
34
use syn;
45
use syn::spanned::Spanned;
56

@@ -67,6 +68,14 @@ impl Field {
6768
pub fn has_flag(&self, flag: &str) -> bool {
6869
self.flags.has_flag(flag)
6970
}
71+
72+
pub fn ty_for_deserialize(&self) -> Result<Cow<syn::Type>, Diagnostic> {
73+
if let Some(meta) = self.flags.nested_item("deserialize_as")? {
74+
meta.ty_value().map(Cow::Owned)
75+
} else {
76+
Ok(Cow::Borrowed(&self.ty))
77+
}
78+
}
7079
}
7180

7281
pub enum FieldName {

diesel_derives/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ pub fn derive_query_id(input: TokenStream) -> TokenStream {
8585
expand_derive(input, query_id::derive)
8686
}
8787

88-
#[proc_macro_derive(Queryable, attributes(column_name))]
88+
#[proc_macro_derive(Queryable, attributes(column_name, diesel))]
8989
pub fn derive_queryable(input: TokenStream) -> TokenStream {
9090
expand_derive(input, queryable::derive)
9191
}

diesel_derives/src/meta.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,12 @@ impl MetaItem {
130130

131131
pub fn has_flag(&self, flag: &str) -> bool {
132132
self.nested()
133-
.map(|mut n| n.any(|m| m.expect_word() == flag))
133+
.map(|mut n| {
134+
n.any(|m| match m.word() {
135+
Ok(word) => word == flag,
136+
Err(_) => false,
137+
})
138+
})
134139
.unwrap_or_else(|e| {
135140
e.emit();
136141
false

diesel_derives/src/queryable.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,15 @@ pub fn derive(item: syn::DeriveInput) -> Result<proc_macro2::TokenStream, Diagno
88
let model = Model::from_item(&item)?;
99

1010
let struct_name = &item.ident;
11-
let field_ty = model.fields().iter().map(|f| &f.ty).collect::<Vec<_>>();
11+
let field_ty = model
12+
.fields()
13+
.iter()
14+
.map(|f| f.ty_for_deserialize())
15+
.collect::<Result<Vec<_>, _>>()?;
1216
let field_ty = &field_ty;
1317
let build_expr = model.fields().iter().enumerate().map(|(i, f)| {
1418
let i = syn::Index::from(i);
15-
f.name.assign(parse_quote!(row.#i))
19+
f.name.assign(parse_quote!(row.#i.into()))
1620
});
1721

1822
let (_, ty_generics, _) = item.generics.split_for_impl();

diesel_derives/src/queryable_by_name.rs

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@ pub fn derive(item: syn::DeriveInput) -> Result<proc_macro2::TokenStream, Diagno
99
let model = Model::from_item(&item)?;
1010

1111
let struct_name = &item.ident;
12-
let field_expr = model.fields().iter().map(|f| field_expr(f, &model));
12+
let field_expr = model
13+
.fields()
14+
.iter()
15+
.map(|f| field_expr(f, &model))
16+
.collect::<Result<Vec<_>, _>>()?;
1317

1418
let (_, ty_generics, ..) = item.generics.split_for_impl();
1519
let mut generics = item.generics.clone();
@@ -19,7 +23,7 @@ pub fn derive(item: syn::DeriveInput) -> Result<proc_macro2::TokenStream, Diagno
1923

2024
for field in model.fields() {
2125
let where_clause = generics.where_clause.get_or_insert(parse_quote!(where));
22-
let field_ty = &field.ty;
26+
let field_ty = field.ty_for_deserialize()?;
2327
if field.has_flag("embed") {
2428
where_clause
2529
.predicates
@@ -54,17 +58,18 @@ pub fn derive(item: syn::DeriveInput) -> Result<proc_macro2::TokenStream, Diagno
5458
))
5559
}
5660

57-
fn field_expr(field: &Field, model: &Model) -> syn::FieldValue {
61+
fn field_expr(field: &Field, model: &Model) -> Result<syn::FieldValue, Diagnostic> {
5862
if field.has_flag("embed") {
59-
field
63+
Ok(field
6064
.name
61-
.assign(parse_quote!(QueryableByName::build(row)?))
65+
.assign(parse_quote!(QueryableByName::build(row)?)))
6266
} else {
6367
let column_name = field.column_name();
68+
let ty = field.ty_for_deserialize()?;
6469
let st = sql_type(field, model);
65-
field
70+
Ok(field
6671
.name
67-
.assign(parse_quote!(row.get::<#st, _>(stringify!(#column_name))?))
72+
.assign(parse_quote!(row.get::<#st, #ty>(stringify!(#column_name))?.into())))
6873
}
6974
}
7075

0 commit comments

Comments
 (0)