diesel_derive_newtype/
lib.rs

1#![recursion_limit = "1024"] // the new default in rust 1.19, quote! takes a lot
2
3//! # `#[derive(DieselNewType)]`
4//!
5//! This crate exposes a single custom-derive macro `DieselNewType` which
6//! implements `ToSql`, `FromSql`, `FromSqlRow`, `Queryable`, `AsExpression`
7//! and `QueryId` for the single-field tuple struct ([NewType][]) it is applied
8//! to.
9//!
10//! The goal of this project is that:
11//!
12//! * `derive(DieselNewType)` should be enough for you to use newtypes anywhere you
13//!   would use their underlying types within Diesel. (plausibly successful)
14//! * Should get the same compile-time guarantees when using your newtypes as
15//!   expression elements in Diesel as you do in other rust code (depends on
16//!   your desires, see [Limitations][], below.)
17//!
18//! [NewType]: https://aturon.github.io/features/types/newtype.html
19//!
20//! # Example
21//!
22//! This implementation:
23//!
24//! ```
25//! #[macro_use]
26//! extern crate diesel_derive_newtype;
27//!
28//! #[derive(DieselNewType)] // Doesn't need to be on its own line
29//! #[derive(Debug, Hash, PartialEq, Eq)] // required by diesel
30//! struct MyId(i64);
31//! # fn main() {}
32//! ```
33//!
34//! Allows you to use the `MyId` struct inside your entities as though they were
35//! the underlying type:
36//!
37//! ```
38//! # #[macro_use] extern crate diesel;
39//! # #[macro_use] extern crate diesel_derive_newtype;
40//! # use diesel::prelude::*;
41//! table! {
42//!     my_items {
43//!         id -> Integer,
44//!         val -> Integer,
45//!     }
46//! }
47//!
48//! # #[derive(DieselNewType)] // Doesn't need to be on its own line
49//! # #[derive(Debug, Hash, PartialEq, Eq)] // required by diesel
50//! # struct MyId(i64);
51//! #[derive(Debug, PartialEq, Identifiable, Queryable)]
52//! struct MyItem {
53//!     id: MyId,
54//!     val: u8,
55//! }
56//! # fn main() {}
57//! ```
58//!
59//! Oooohhh. Ahhhh.
60//!
61//! See [the tests][] for a more complete example.
62//!
63//! [the tests]: https://github.com/quodlibetor/diesel-derive-newtype/blob/master/tests/db-roundtrips.rs
64//!
65//! # Limitations
66//! [limitations]: #limitations
67//!
68//! The `DieselNewtype` derive does not create new _database_ types, or Diesel
69//! serialization types. That is, if you have a `MyId(i64)`, this will use
70//! Diesel's underlying `BigInt` type, which means that even though your
71//! newtypes can be used anywhere the underlying type can be used, *the
72//! underlying types, or any other newtypes of the same underlying type, can be
73//! used as well*.
74//!
75//! At a certain point everything does become bits on the wire, so if we didn't
76//! do it this way then Diesel would have to do it somewhere else, and this is
77//! reasonable default behavior (it's pretty debuggable), but I'm investigating
78//! auto-generating new proxy types as well to make it impossible to construct
79//! an insert statement using a tuple or a mis-typed struct.
80//!
81//! Here's an example of that this type-hole looks like:
82//!
83//! ```rust,ignore
84//! #[derive(Debug, Hash, PartialEq, Eq, DieselNewType)]
85//! struct OneId(i64);
86//!
87//! #[derive(Debug, Hash, PartialEq, Eq, DieselNewType)]
88//! struct OtherId(i64);
89//!
90//! #[derive(Debug, Clone, PartialEq, Identifiable, Insertable, Queryable)]
91//! #[diesel(table_name = my_entities)]
92//! pub struct MyEntity {
93//!     id: OneId,
94//!     val: i32,
95//! }
96//!
97//! fn darn(conn: &Connection) {
98//!     // shouldn't allow constructing the wrong type, but does
99//!     let OtherId: Vec<OtherId> = my_entities
100//!         .select(id)
101//!         .filter(id.eq(OtherId(1)))  // shouldn't allow filtering by wrong type
102//!         .execute(conn).unwrap();
103//! }
104//! ```
105//!
106//! See [`tests/should-not-compile.rs`](tests/should-not-compile.rs) for the
107//! things I think should fail to compile.
108//!
109//! I believe that the root cause of this is that Diesel implements the various
110//! expression methods for types that implement `AsExpression`, based on the
111//! _SQL_ type, not caring about `self` and `other`'s Rust type matching. That
112//! seems like a pretty good decision in general, but it is a bit unfortunate
113//! here.
114//!
115//! I hope to find a solution that doesn't involve implementing every
116//! `*Expression` trait manually with an extra bound, but for now you have to
117//! keep in mind that the Diesel methods basically auto-transmute your data into
118//! the underlying SQL type.
119
120extern crate syn;
121#[macro_use]
122extern crate quote;
123extern crate proc_macro;
124extern crate proc_macro2;
125
126use proc_macro2::{Span, TokenStream};
127
128#[proc_macro_derive(DieselNewType)]
129#[doc(hidden)]
130pub fn diesel_new_type(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
131    let ast = syn::parse(input).unwrap();
132
133    expand_sql_types(&ast).into()
134}
135
136fn expand_sql_types(ast: &syn::DeriveInput) -> TokenStream {
137    let name = &ast.ident;
138    let wrapped_ty = match ast.data {
139        syn::Data::Struct(ref data) => {
140            let mut iter = data.fields.iter();
141            match (iter.next(), iter.next()) {
142                (Some(field), None) => &field.ty,
143                (_, _) => panic!(
144                    "#[derive(DieselNewType)] can only be used with structs with exactly one field"
145                ),
146            }
147        }
148        _ => panic!("#[derive(DieselNewType)] can only be used with structs with a single field"),
149    };
150
151    // Required to be able to insert/read from the db, don't allow searching
152    let to_sql_impl = gen_tosql(name, wrapped_ty);
153    let as_expr_impl = gen_asexpressions(name, wrapped_ty);
154
155    // raw deserialization
156    let from_sql_impl = gen_from_sql(name, wrapped_ty);
157
158    // querying
159    let queryable_impl = gen_queryable(name, wrapped_ty);
160
161    // since our query doesn't take varargs it's fine for the DB to cache it
162    let query_id_impl = gen_query_id(name);
163
164    wrap_impls_in_const(&quote! {
165        #to_sql_impl
166        #as_expr_impl
167
168        #from_sql_impl
169
170        #queryable_impl
171
172        #query_id_impl
173    })
174}
175
176fn gen_tosql(name: &syn::Ident, wrapped_ty: &syn::Type) -> TokenStream {
177    quote! {
178        impl<ST, DB> diesel::serialize::ToSql<ST, DB> for #name
179        where
180            #wrapped_ty: diesel::serialize::ToSql<ST, DB>,
181            DB: diesel::backend::Backend,
182            DB: diesel::sql_types::HasSqlType<ST>,
183        {
184            fn to_sql<'b>(&'b self, out: &mut diesel::serialize::Output<'b, '_, DB>) -> diesel::serialize::Result
185            {
186                self.0.to_sql(out)
187            }
188        }
189    }
190}
191
192fn gen_asexpressions(name: &syn::Ident, wrapped_ty: &syn::Type) -> TokenStream {
193    quote! {
194
195        impl<ST> diesel::expression::AsExpression<ST> for #name
196        where
197            diesel::internal::derives::as_expression::Bound<ST, #wrapped_ty>:
198                diesel::expression::Expression<SqlType=ST>,
199            ST: diesel::sql_types::SingleValue,
200        {
201            type Expression = diesel::internal::derives::as_expression::Bound<ST, Self>;
202
203            fn as_expression(self) -> Self::Expression {
204                diesel::internal::derives::as_expression::Bound::new(self)
205            }
206        }
207
208        impl<'expr, ST> diesel::expression::AsExpression<ST> for &'expr #name
209        where
210            diesel::internal::derives::as_expression::Bound<ST, #wrapped_ty>:
211                diesel::expression::Expression<SqlType=ST>,
212            ST: diesel::sql_types::SingleValue,
213        {
214            type Expression = diesel::internal::derives::as_expression::Bound<ST, Self>;
215
216            fn as_expression(self) -> Self::Expression {
217                diesel::internal::derives::as_expression::Bound::new(self)
218            }
219        }
220
221        impl<'expr2, 'expr, ST> diesel::expression::AsExpression<ST> for &'expr2 &'expr #name
222        where
223            diesel::internal::derives::as_expression::Bound<ST, #wrapped_ty>:
224                diesel::expression::Expression<SqlType=ST>,
225            ST: diesel::sql_types::SingleValue,
226        {
227            type Expression = diesel::internal::derives::as_expression::Bound<ST, Self>;
228
229            fn as_expression(self) -> Self::Expression {
230                diesel::internal::derives::as_expression::Bound::new(self)
231            }
232        }
233    }
234}
235
236fn gen_from_sql(name: &syn::Ident, wrapped_ty: &syn::Type) -> TokenStream {
237    quote! {
238        impl<ST, DB> diesel::deserialize::FromSql<ST, DB> for #name
239        where
240            #wrapped_ty: diesel::deserialize::FromSql<ST, DB>,
241            DB: diesel::backend::Backend,
242            DB: diesel::sql_types::HasSqlType<ST>,
243        {
244            fn from_sql(raw: DB::RawValue<'_>)
245            -> ::std::result::Result<Self, Box<::std::error::Error + Send + Sync>>
246            {
247                diesel::deserialize::FromSql::<ST, DB>::from_sql(raw)
248                    .map(#name)
249            }
250        }
251    }
252}
253
254fn gen_queryable(name: &syn::Ident, wrapped_ty: &syn::Type) -> TokenStream {
255    quote! {
256        impl<ST, DB> diesel::deserialize::Queryable<ST, DB> for #name
257        where
258            #wrapped_ty: diesel::deserialize::FromStaticSqlRow<ST, DB>,
259            DB: diesel::backend::Backend,
260            DB: diesel::sql_types::HasSqlType<ST>,
261        {
262            type Row = #wrapped_ty;
263
264            fn build(row: Self::Row) -> diesel::deserialize::Result<Self> {
265                Ok(#name(row))
266            }
267        }
268    }
269}
270
271fn gen_query_id(name: &syn::Ident) -> TokenStream {
272    quote! {
273        impl diesel::query_builder::QueryId for #name {
274            type QueryId = Self;
275        }
276    }
277}
278
279/// This guarantees that items we generate don't pollute the module scope
280fn wrap_impls_in_const(item: &TokenStream) -> TokenStream {
281    let dummy_const = syn::Ident::new("_", Span::call_site());
282    quote! {
283        const #dummy_const: () = {
284            #item
285        };
286    }
287}