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("e! {
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}