Skip to main content

diesel_derive_pg/
lib.rs

1//! PostgreSQL custom type ToSql and FromSql derive macro
2use proc_macro::TokenStream;
3use quote::quote;
4use syn::{self, parse_macro_input, DeriveInput};
5
6#[derive(deluxe::ExtractAttributes)]
7#[deluxe(attributes(diesel_derive_pg))]
8struct Opts {
9    sql_type: syn::Type,
10}
11
12#[derive(deluxe::ExtractAttributes)]
13#[deluxe(attributes(diesel_derive_pg))]
14struct FieldAttrs {
15    sql_type: syn::Type,
16}
17
18/// Derive ToSql and FromSql trait implementations for
19/// PostgreSQL custom types (composite and domain types)
20///
21/// Example:
22/// ```
23/// use diesel_derive_pg::PgCustomType;
24///
25/// struct SqlType;
26/// struct SqlInnerType;
27///
28/// // Implementation for the following domain type:
29/// // CREATE DOMAIN NEWTYPE AS TEXT;
30/// #[derive(Debug, PgCustomType)]
31/// #[diesel_derive_pg(sql_type = SqlType)]
32/// pub struct Newtype(#[diesel_derive_pg(sql_type = diesel::sql_types::Text)] pub String);
33///
34/// // Implementation for the following composite type:
35/// // CREATE TYPE STRUCT AS (field_one TEXT, field_two INT);
36/// #[derive(Debug, PgCustomType)]
37/// #[diesel_derive_pg(sql_type = SqlType)]
38/// pub struct Struct {
39///     #[diesel_derive_pg(sql_type  = diesel::sql_types::Text)]
40///     field_one: String,
41///
42///     #[diesel_derive_pg(sql_type  = diesel::sql_types::BigInt)]
43///     field_two: i64,
44/// }
45/// ```
46#[proc_macro_derive(PgCustomType, attributes(diesel_derive_pg))]
47pub fn derive_sql_fn(input: TokenStream) -> TokenStream {
48    let mut ast = parse_macro_input!(input as DeriveInput);
49    let opts: Opts = deluxe::extract_attributes(&mut ast).expect("Wrong options");
50
51    let ident = &ast.ident;
52    let sql_type = opts.sql_type;
53
54    let (to_sql_impl, from_sql_impl) = match &ast.data {
55        syn::Data::Struct(data_struct) => match &data_struct.fields {
56            syn::Fields::Named(fields_named) => impl_struct(fields_named),
57            syn::Fields::Unnamed(fields_unnamed) => {
58                if fields_unnamed.unnamed.len() == 1 {
59                    impl_newtype(&fields_unnamed.unnamed[0])
60                } else {
61                    unimplemented!("Tuples are not implemented yet")
62                }
63            }
64            syn::Fields::Unit => unimplemented!("Units are unsupported"),
65        },
66        syn::Data::Enum(_data_enum) => unimplemented!("Enums are not implemented yet"),
67        syn::Data::Union(_data_union) => unimplemented!("Unions are unsupported"),
68    };
69
70    let (impl_generics, ty_generics, where_clause) = &ast.generics.split_for_impl();
71
72    let expanded = quote! {
73        impl #impl_generics diesel::deserialize::FromSql<#sql_type, diesel::pg::Pg> for #ident #ty_generics #where_clause {
74            fn from_sql(bytes: diesel::pg::PgValue) -> diesel::deserialize::Result<Self> {
75                #from_sql_impl
76            }
77        }
78
79        impl #impl_generics diesel::serialize::ToSql<#sql_type, diesel::pg::Pg> for #ident #ty_generics #where_clause {
80            fn to_sql<'b>(
81                &'b self,
82                out: &mut diesel::serialize::Output<'b, '_, diesel::pg::Pg>,
83            ) -> diesel::serialize::Result {
84                #to_sql_impl
85            }
86        }
87    };
88
89    TokenStream::from(expanded)
90}
91
92fn impl_newtype(field: &syn::Field) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) {
93    let inner_type = &field.ty;
94
95    let attrs: FieldAttrs =
96        deluxe::extract_attributes(&mut field.clone()).expect("Wrong field attributes");
97    let sql_inner_type = attrs.sql_type;
98
99    let to_sql_impl = quote! {
100        <#inner_type as diesel::serialize::ToSql<#sql_inner_type, diesel::pg::Pg>>::to_sql(
101            &self.0,
102            &mut out.reborrow(),
103        )
104
105    };
106
107    let from_sql_impl = quote! {
108        let inner =
109            diesel::deserialize::FromSql::<#sql_inner_type, diesel::pg::Pg>::from_sql(
110                bytes,
111            )?;
112        Ok(Self(inner))
113    };
114
115    (to_sql_impl, from_sql_impl)
116}
117
118fn impl_struct(
119    fields_named: &syn::FieldsNamed,
120    // sql_inner_types: Vec<syn::Type>,
121) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) {
122    let named = &fields_named.named;
123
124    let sql_inner_types: Vec<_> = named
125        .clone()
126        .iter_mut()
127        .map(|field| {
128            let attrs: FieldAttrs =
129                deluxe::extract_attributes(field).expect("Wrong field attributes");
130            attrs.sql_type
131        })
132        .collect();
133
134    let cloned_fields = named
135        .iter()
136        .map(|field| &field.ident)
137        .map(|field_ident| quote! { self.#field_ident.clone() });
138
139    let to_sql_impl = quote! {
140        diesel::serialize::WriteTuple::<(
141            #(#sql_inner_types),*
142        )>::write_tuple(
143            &(#(#cloned_fields),*),
144            &mut out.reborrow(),
145        )
146    };
147
148    let field_idents = named.iter().map(|field| &field.ident);
149    let field_idents2 = field_idents.clone();
150
151    let from_sql_impl = quote! {
152        let (#(#field_idents),*) =
153            diesel::deserialize::FromSql::<diesel::sql_types::Record<(#(#sql_inner_types),*)>, diesel::pg::Pg>::from_sql(bytes)?;
154        Ok(Self {
155            #(#field_idents2),*
156        })
157    };
158
159    (to_sql_impl, from_sql_impl)
160}