pgx_utils/sql_entity_graph/postgres_type/
mod.rs

1/*
2Portions Copyright 2019-2021 ZomboDB, LLC.
3Portions Copyright 2021-2022 Technology Concepts & Design, Inc. <support@tcdi.com>
4
5All rights reserved.
6
7Use of this source code is governed by the MIT license that can be found in the LICENSE file.
8*/
9/*!
10
11`#[derive(PostgresType)]` related macro expansion for Rust to SQL translation
12
13> Like all of the [`sql_entity_graph`][crate::sql_entity_graph] APIs, this is considered **internal**
14to the `pgx` framework and very subject to change between versions. While you may use this, please do it with caution.
15
16*/
17pub mod entity;
18
19use proc_macro2::{Ident, Span, TokenStream as TokenStream2};
20use quote::{quote, ToTokens, TokenStreamExt};
21use syn::parse::{Parse, ParseStream};
22use syn::{DeriveInput, Generics, ItemStruct};
23
24use crate::sql_entity_graph::ToSqlConfig;
25
26/// A parsed `#[derive(PostgresType)]` item.
27///
28/// It should be used with [`syn::parse::Parse`] functions.
29///
30/// Using [`quote::ToTokens`] will output the declaration for a [`PostgresTypeEntity`][crate::sql_entity_graph::PostgresTypeEntity].
31///
32/// ```rust
33/// use syn::{Macro, parse::Parse, parse_quote, parse};
34/// use quote::{quote, ToTokens};
35/// use pgx_utils::sql_entity_graph::PostgresType;
36///
37/// # fn main() -> eyre::Result<()> {
38/// let parsed: PostgresType = parse_quote! {
39///     #[derive(PostgresType)]
40///     struct Example<'a> {
41///         demo: &'a str,
42///     }
43/// };
44/// let sql_graph_entity_tokens = parsed.to_token_stream();
45/// # Ok(())
46/// # }
47/// ```
48#[derive(Debug, Clone)]
49pub struct PostgresType {
50    name: Ident,
51    generics: Generics,
52    in_fn: Ident,
53    out_fn: Ident,
54    to_sql_config: ToSqlConfig,
55}
56
57impl PostgresType {
58    pub fn new(
59        name: Ident,
60        generics: Generics,
61        in_fn: Ident,
62        out_fn: Ident,
63        to_sql_config: ToSqlConfig,
64    ) -> Result<Self, syn::Error> {
65        if !to_sql_config.overrides_default() {
66            crate::ident_is_acceptable_to_postgres(&name)?;
67        }
68        Ok(Self { generics, name, in_fn, out_fn, to_sql_config })
69    }
70
71    pub fn from_derive_input(derive_input: DeriveInput) -> Result<Self, syn::Error> {
72        match derive_input.data {
73            syn::Data::Struct(_) | syn::Data::Enum(_) => {}
74            syn::Data::Union(_) => {
75                return Err(syn::Error::new(derive_input.ident.span(), "expected struct or enum"))
76            }
77        };
78        let to_sql_config =
79            ToSqlConfig::from_attributes(derive_input.attrs.as_slice())?.unwrap_or_default();
80        let funcname_in = Ident::new(
81            &format!("{}_in", derive_input.ident).to_lowercase(),
82            derive_input.ident.span(),
83        );
84        let funcname_out = Ident::new(
85            &format!("{}_out", derive_input.ident).to_lowercase(),
86            derive_input.ident.span(),
87        );
88        Self::new(
89            derive_input.ident,
90            derive_input.generics,
91            funcname_in,
92            funcname_out,
93            to_sql_config,
94        )
95    }
96}
97
98impl Parse for PostgresType {
99    fn parse(input: ParseStream) -> Result<Self, syn::Error> {
100        let parsed: ItemStruct = input.parse()?;
101        let to_sql_config =
102            ToSqlConfig::from_attributes(parsed.attrs.as_slice())?.unwrap_or_default();
103        let funcname_in =
104            Ident::new(&format!("{}_in", parsed.ident).to_lowercase(), parsed.ident.span());
105        let funcname_out =
106            Ident::new(&format!("{}_out", parsed.ident).to_lowercase(), parsed.ident.span());
107        Self::new(parsed.ident, parsed.generics, funcname_in, funcname_out, to_sql_config)
108    }
109}
110
111impl ToTokens for PostgresType {
112    fn to_tokens(&self, tokens: &mut TokenStream2) {
113        let name = &self.name;
114        let mut static_generics = self.generics.clone();
115        static_generics.params = static_generics
116            .params
117            .clone()
118            .into_iter()
119            .flat_map(|param| match param {
120                item @ syn::GenericParam::Type(_) | item @ syn::GenericParam::Const(_) => {
121                    Some(item)
122                }
123                syn::GenericParam::Lifetime(mut lifetime) => {
124                    lifetime.lifetime.ident = Ident::new("static", Span::call_site());
125                    Some(syn::GenericParam::Lifetime(lifetime))
126                }
127            })
128            .collect();
129        let mut staticless_generics = self.generics.clone();
130        staticless_generics.params = static_generics
131            .params
132            .clone()
133            .into_iter()
134            .flat_map(|param| match param {
135                item @ syn::GenericParam::Type(_) | item @ syn::GenericParam::Const(_) => {
136                    Some(item)
137                }
138                syn::GenericParam::Lifetime(_) => None,
139            })
140            .collect();
141        let (staticless_impl_generics, _staticless_ty_generics, _staticless_where_clauses) =
142            staticless_generics.split_for_impl();
143        let (_static_impl_generics, static_ty_generics, static_where_clauses) =
144            static_generics.split_for_impl();
145
146        let in_fn = &self.in_fn;
147        let out_fn = &self.out_fn;
148
149        let sql_graph_entity_fn_name =
150            syn::Ident::new(&format!("__pgx_internals_type_{}", self.name), Span::call_site());
151
152        let to_sql_config = &self.to_sql_config;
153
154        let inv = quote! {
155            unsafe impl #staticless_impl_generics ::pgx::utils::sql_entity_graph::metadata::SqlTranslatable for #name #static_ty_generics #static_where_clauses {
156                fn argument_sql() -> core::result::Result<::pgx::utils::sql_entity_graph::metadata::SqlMapping, ::pgx::utils::sql_entity_graph::metadata::ArgumentError> {
157                    Ok(::pgx::utils::sql_entity_graph::metadata::SqlMapping::As(String::from(stringify!(#name))))
158                }
159
160                fn return_sql() -> core::result::Result<::pgx::utils::sql_entity_graph::metadata::Returns, ::pgx::utils::sql_entity_graph::metadata::ReturnsError> {
161                    Ok(::pgx::utils::sql_entity_graph::metadata::Returns::One(::pgx::utils::sql_entity_graph::metadata::SqlMapping::As(String::from(stringify!(#name)))))
162                }
163            }
164
165
166            #[no_mangle]
167            #[doc(hidden)]
168            pub extern "Rust" fn  #sql_graph_entity_fn_name() -> ::pgx::utils::sql_entity_graph::SqlGraphEntity {
169                extern crate alloc;
170                use alloc::vec::Vec;
171                use alloc::vec;
172                use alloc::string::{String, ToString};
173                use ::pgx::WithTypeIds;
174
175                let mut mappings = Default::default();
176                <#name #static_ty_generics as pgx::datum::WithTypeIds>::register_with_refs(
177                    &mut mappings,
178                    stringify!(#name).to_string()
179                );
180                pgx::datum::WithSizedTypeIds::<#name #static_ty_generics>::register_sized_with_refs(
181                    &mut mappings,
182                    stringify!(#name).to_string()
183                );
184                pgx::datum::WithArrayTypeIds::<#name #static_ty_generics>::register_array_with_refs(
185                    &mut mappings,
186                    stringify!(#name).to_string()
187                );
188                pgx::datum::WithVarlenaTypeIds::<#name #static_ty_generics>::register_varlena_with_refs(
189                    &mut mappings,
190                    stringify!(#name).to_string()
191                );
192                let submission = ::pgx::utils::sql_entity_graph::PostgresTypeEntity {
193                    name: stringify!(#name),
194                    file: file!(),
195                    line: line!(),
196                    module_path: module_path!(),
197                    full_path: core::any::type_name::<#name #static_ty_generics>(),
198                    mappings,
199                    in_fn: stringify!(#in_fn),
200                    in_fn_module_path: {
201                        let in_fn = stringify!(#in_fn);
202                        let mut path_items: Vec<_> = in_fn.split("::").collect();
203                        let _ = path_items.pop(); // Drop the one we don't want.
204                        path_items.join("::")
205                    },
206                    out_fn: stringify!(#out_fn),
207                    out_fn_module_path: {
208                        let out_fn = stringify!(#out_fn);
209                        let mut path_items: Vec<_> = out_fn.split("::").collect();
210                        let _ = path_items.pop(); // Drop the one we don't want.
211                        path_items.join("::")
212                    },
213                    to_sql_config: #to_sql_config,
214                };
215                ::pgx::utils::sql_entity_graph::SqlGraphEntity::Type(submission)
216            }
217        };
218        tokens.append_all(inv);
219    }
220}