Skip to main content

pgrx_sql_entity_graph/postgres_type/
mod.rs

1//LICENSE Portions Copyright 2019-2021 ZomboDB, LLC.
2//LICENSE
3//LICENSE Portions Copyright 2021-2023 Technology Concepts & Design, Inc.
4//LICENSE
5//LICENSE Portions Copyright 2023-2023 PgCentral Foundation, Inc. <contact@pgcentral.org>
6//LICENSE
7//LICENSE All rights reserved.
8//LICENSE
9//LICENSE Use of this source code is governed by the MIT license that can be found in the LICENSE file.
10/*!
11
12`#[derive(PostgresType)]` related macro expansion for Rust to SQL translation
13
14> Like all of the [`sql_entity_graph`][crate] APIs, this is considered **internal**
15> to the `pgrx` framework and very subject to change between versions. While you may use this, please do it with caution.
16
17*/
18pub mod entity;
19
20use crate::enrich::{ToEntityGraphTokens, ToRustCodeTokens};
21use proc_macro2::{Ident, TokenStream as TokenStream2};
22use quote::{format_ident, quote};
23use syn::parse::{Parse, ParseStream};
24use syn::{DeriveInput, Generics, ItemStruct, Lifetime, LifetimeParam};
25
26pub use crate::postgres_type::entity::Alignment;
27use crate::{CodeEnrichment, ToSqlConfig};
28
29/// A parsed `#[derive(PostgresType)]` item.
30///
31/// It should be used with [`syn::parse::Parse`] functions.
32///
33/// Using [`quote::ToTokens`] will output the declaration for a [`PostgresTypeEntity`][crate::PostgresTypeEntity].
34///
35/// ```rust
36/// use syn::{Macro, parse::Parse, parse_quote, parse};
37/// use quote::{quote, ToTokens};
38/// use pgrx_sql_entity_graph::PostgresTypeDerive;
39///
40/// # fn main() -> eyre::Result<()> {
41/// use pgrx_sql_entity_graph::CodeEnrichment;
42/// let parsed: CodeEnrichment<PostgresTypeDerive> = parse_quote! {
43///     #[derive(PostgresType)]
44///     struct Example<'a> {
45///         demo: &'a str,
46///     }
47/// };
48/// let sql_graph_entity_tokens = parsed.to_token_stream();
49/// # Ok(())
50/// # }
51/// ```
52#[derive(Debug, Clone)]
53pub struct PostgresTypeDerive {
54    name: Ident,
55    generics: Generics,
56    in_fn: Ident,
57    out_fn: Ident,
58    receive_fn: Option<Ident>,
59    send_fn: Option<Ident>,
60    to_sql_config: ToSqlConfig,
61    alignment: Alignment,
62}
63
64impl PostgresTypeDerive {
65    pub fn new(
66        name: Ident,
67        generics: Generics,
68        in_fn: Ident,
69        out_fn: Ident,
70        receive_fn: Option<Ident>,
71        send_fn: Option<Ident>,
72        to_sql_config: ToSqlConfig,
73        alignment: Alignment,
74    ) -> Result<CodeEnrichment<Self>, syn::Error> {
75        if !to_sql_config.overrides_default() {
76            crate::ident_is_acceptable_to_postgres(&name)?;
77        }
78        Ok(CodeEnrichment(Self {
79            generics,
80            name,
81            in_fn,
82            out_fn,
83            receive_fn,
84            send_fn,
85            to_sql_config,
86            alignment,
87        }))
88    }
89
90    pub fn from_derive_input(
91        derive_input: DeriveInput,
92        pg_binary_protocol: bool,
93    ) -> Result<CodeEnrichment<Self>, syn::Error> {
94        match derive_input.data {
95            syn::Data::Struct(_) | syn::Data::Enum(_) => {}
96            syn::Data::Union(_) => {
97                return Err(syn::Error::new(derive_input.ident.span(), "expected struct or enum"));
98            }
99        };
100        let to_sql_config =
101            ToSqlConfig::from_attributes(derive_input.attrs.as_slice())?.unwrap_or_default();
102        let funcname_in = Ident::new(
103            &format!("{}_in", derive_input.ident).to_lowercase(),
104            derive_input.ident.span(),
105        );
106        let funcname_out = Ident::new(
107            &format!("{}_out", derive_input.ident).to_lowercase(),
108            derive_input.ident.span(),
109        );
110        let funcname_receive = (pg_binary_protocol).then(|| {
111            Ident::new(
112                &format!("{}_recv", derive_input.ident).to_lowercase(),
113                derive_input.ident.span(),
114            )
115        });
116        let funcname_send = (pg_binary_protocol).then(|| {
117            Ident::new(
118                &format!("{}_send", derive_input.ident).to_lowercase(),
119                derive_input.ident.span(),
120            )
121        });
122        let alignment = Alignment::from_attributes(derive_input.attrs.as_slice())?;
123        Self::new(
124            derive_input.ident,
125            derive_input.generics,
126            funcname_in,
127            funcname_out,
128            funcname_receive,
129            funcname_send,
130            to_sql_config,
131            alignment,
132        )
133    }
134}
135
136impl ToEntityGraphTokens for PostgresTypeDerive {
137    fn to_entity_graph_tokens(&self) -> TokenStream2 {
138        let name = &self.name;
139        let generics = self.generics.clone();
140        let (impl_generics, ty_generics, where_clauses) = generics.split_for_impl();
141
142        // We need some generics we can use inside a fn without a lifetime for qualified paths.
143        let mut anon_generics = generics.clone();
144        anon_generics.params = anon_generics
145            .params
146            .into_iter()
147            .flat_map(|param| match param {
148                item @ syn::GenericParam::Type(_) | item @ syn::GenericParam::Const(_) => {
149                    Some(item)
150                }
151                syn::GenericParam::Lifetime(lt_def) => Some(syn::GenericParam::Lifetime(
152                    LifetimeParam::new(Lifetime::new("'_", lt_def.lifetime.span())),
153                )),
154            })
155            .collect();
156        let (_, anon_ty_gen, _) = anon_generics.split_for_impl();
157
158        let in_fn = &self.in_fn;
159        let out_fn = &self.out_fn;
160        let stringify_receive_fn = self
161            .receive_fn
162            .as_ref()
163            .map(|f| quote! { Some(stringify!(#f)) })
164            .unwrap_or_else(|| quote! { None });
165        let stringify_send_fn = self
166            .send_fn
167            .as_ref()
168            .map(|f| quote! { Some(stringify!(#f)) })
169            .unwrap_or_else(|| quote! { None });
170        let receive_fn_module_path = self
171            .receive_fn
172            .as_ref()
173            .map(|f| {
174                quote! {Some({
175                    let in_fn = stringify!(#f);
176                    let mut path_items: Vec<_> = in_fn.split("::").collect();
177                    let _ = path_items.pop(); // Drop the one we don't want.
178                    path_items.join("::")
179                })}
180            })
181            .unwrap_or_else(|| quote! { None });
182        let send_fn_module_path = self
183            .send_fn
184            .as_ref()
185            .map(|f| {
186                quote! {Some({
187                    let out_fn = stringify!(#f);
188                    let mut path_items: Vec<_> = out_fn.split("::").collect();
189                    let _ = path_items.pop(); // Drop the one we don't want.
190                    path_items.join("::")
191                })}
192            })
193            .unwrap_or_else(|| quote! { None });
194
195        let sql_graph_entity_fn_name = format_ident!("__pgrx_internals_type_{}", self.name);
196
197        let to_sql_config = &self.to_sql_config;
198
199        let alignment = match &self.alignment {
200            Alignment::On => quote! { Some(::std::mem::align_of::<#name>()) },
201            Alignment::Off => quote! { None },
202        };
203
204        quote! {
205            unsafe impl #impl_generics ::pgrx::pgrx_sql_entity_graph::metadata::SqlTranslatable for #name #ty_generics #where_clauses {
206                fn argument_sql() -> core::result::Result<::pgrx::pgrx_sql_entity_graph::metadata::SqlMapping, ::pgrx::pgrx_sql_entity_graph::metadata::ArgumentError> {
207                    Ok(::pgrx::pgrx_sql_entity_graph::metadata::SqlMapping::As(String::from(stringify!(#name))))
208                }
209
210                fn return_sql() -> core::result::Result<::pgrx::pgrx_sql_entity_graph::metadata::Returns, ::pgrx::pgrx_sql_entity_graph::metadata::ReturnsError> {
211                    Ok(::pgrx::pgrx_sql_entity_graph::metadata::Returns::One(::pgrx::pgrx_sql_entity_graph::metadata::SqlMapping::As(String::from(stringify!(#name)))))
212                }
213            }
214
215
216            #[unsafe(no_mangle)]
217            #[doc(hidden)]
218            #[allow(nonstandard_style, unknown_lints, clippy::no_mangle_with_rust_abi)]
219            pub extern "Rust" fn  #sql_graph_entity_fn_name() -> ::pgrx::pgrx_sql_entity_graph::SqlGraphEntity {
220                extern crate alloc;
221                use alloc::vec::Vec;
222                use alloc::vec;
223                use alloc::string::{String, ToString};
224                use ::pgrx::datum::WithTypeIds;
225
226                let mut mappings = Default::default();
227                <#name #anon_ty_gen as ::pgrx::datum::WithTypeIds>::register_with_refs(
228                    &mut mappings,
229                    stringify!(#name).to_string()
230                );
231                ::pgrx::datum::WithSizedTypeIds::<#name #anon_ty_gen>::register_sized_with_refs(
232                    &mut mappings,
233                    stringify!(#name).to_string()
234                );
235                ::pgrx::datum::WithArrayTypeIds::<#name #anon_ty_gen>::register_array_with_refs(
236                    &mut mappings,
237                    stringify!(#name).to_string()
238                );
239                ::pgrx::datum::WithVarlenaTypeIds::<#name #anon_ty_gen>::register_varlena_with_refs(
240                    &mut mappings,
241                    stringify!(#name).to_string()
242                );
243                let submission = ::pgrx::pgrx_sql_entity_graph::PostgresTypeEntity {
244                    name: stringify!(#name),
245                    file: file!(),
246                    line: line!(),
247                    module_path: module_path!(),
248                    full_path: core::any::type_name::<#name #anon_ty_gen>(),
249                    mappings: mappings.into_iter().collect(),
250                    in_fn: stringify!(#in_fn),
251                    in_fn_module_path: {
252                        let in_fn = stringify!(#in_fn);
253                        let mut path_items: Vec<_> = in_fn.split("::").collect();
254                        let _ = path_items.pop(); // Drop the one we don't want.
255                        path_items.join("::")
256                    },
257                    out_fn: stringify!(#out_fn),
258                    out_fn_module_path: {
259                        let out_fn = stringify!(#out_fn);
260                        let mut path_items: Vec<_> = out_fn.split("::").collect();
261                        let _ = path_items.pop(); // Drop the one we don't want.
262                        path_items.join("::")
263                    },
264                    receive_fn: #stringify_receive_fn,
265                    receive_fn_module_path: #receive_fn_module_path,
266                    send_fn: #stringify_send_fn,
267                    send_fn_module_path: #send_fn_module_path,
268                    to_sql_config: #to_sql_config,
269                    alignment: #alignment,
270                };
271                ::pgrx::pgrx_sql_entity_graph::SqlGraphEntity::Type(submission)
272            }
273        }
274    }
275}
276
277impl ToRustCodeTokens for PostgresTypeDerive {}
278
279impl Parse for CodeEnrichment<PostgresTypeDerive> {
280    fn parse(input: ParseStream) -> Result<Self, syn::Error> {
281        let ItemStruct { attrs, ident, generics, .. } = input.parse()?;
282
283        let pg_binary_protocol = attrs.iter().any(|a| a.path().is_ident("pg_binary_protocol"));
284
285        let to_sql_config = ToSqlConfig::from_attributes(attrs.as_slice())?.unwrap_or_default();
286        let in_fn = Ident::new(&format!("{ident}_in").to_lowercase(), ident.span());
287        let out_fn = Ident::new(&format!("{ident}_out").to_lowercase(), ident.span());
288        let receive_fn = (pg_binary_protocol)
289            .then(|| Ident::new(&format!("{ident}_recv").to_lowercase(), ident.span()));
290        let send_fn = (pg_binary_protocol)
291            .then(|| Ident::new(&format!("{ident}_send").to_lowercase(), ident.span()));
292        let alignment = Alignment::from_attributes(attrs.as_slice())?;
293        PostgresTypeDerive::new(
294            ident,
295            generics,
296            in_fn,
297            out_fn,
298            receive_fn,
299            send_fn,
300            to_sql_config,
301            alignment,
302        )
303    }
304}