pgx_utils/sql_entity_graph/postgres_enum/
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(PostgresEnum)]` 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 crate::sql_entity_graph::ToSqlConfig;
20use proc_macro2::{Span, TokenStream as TokenStream2};
21use quote::{quote, ToTokens, TokenStreamExt};
22use syn::parse::{Parse, ParseStream};
23use syn::punctuated::Punctuated;
24use syn::{DeriveInput, Generics, Ident, ItemEnum, Token};
25
26/// A parsed `#[derive(PostgresEnum)]` item.
27///
28/// It should be used with [`syn::parse::Parse`] functions.
29///
30/// Using [`quote::ToTokens`] will output the declaration for a `pgx::datum::sql_entity_graph::PostgresEnumEntity`.
31///
32/// ```rust
33/// use syn::{Macro, parse::Parse, parse_quote, parse};
34/// use quote::{quote, ToTokens};
35/// use pgx_utils::sql_entity_graph::PostgresEnum;
36///
37/// # fn main() -> eyre::Result<()> {
38/// let parsed: PostgresEnum = parse_quote! {
39///     #[derive(PostgresEnum)]
40///     enum Demo {
41///         Example,
42///     }
43/// };
44/// let sql_graph_entity_tokens = parsed.to_token_stream();
45/// # Ok(())
46/// # }
47/// ```
48#[derive(Debug, Clone)]
49pub struct PostgresEnum {
50    name: Ident,
51    generics: Generics,
52    variants: Punctuated<syn::Variant, Token![,]>,
53    to_sql_config: ToSqlConfig,
54}
55
56impl PostgresEnum {
57    pub fn new(
58        name: Ident,
59        generics: Generics,
60        variants: Punctuated<syn::Variant, Token![,]>,
61        to_sql_config: ToSqlConfig,
62    ) -> Result<Self, syn::Error> {
63        if !to_sql_config.overrides_default() {
64            crate::ident_is_acceptable_to_postgres(&name)?;
65        }
66
67        Ok(Self { name, generics, variants, to_sql_config })
68    }
69
70    pub fn from_derive_input(derive_input: DeriveInput) -> Result<Self, syn::Error> {
71        let to_sql_config =
72            ToSqlConfig::from_attributes(derive_input.attrs.as_slice())?.unwrap_or_default();
73        let data_enum = match derive_input.data {
74            syn::Data::Enum(data_enum) => data_enum,
75            syn::Data::Union(_) | syn::Data::Struct(_) => {
76                return Err(syn::Error::new(derive_input.ident.span(), "expected enum"))
77            }
78        };
79        Self::new(derive_input.ident, derive_input.generics, data_enum.variants, to_sql_config)
80    }
81}
82
83impl Parse for PostgresEnum {
84    fn parse(input: ParseStream) -> Result<Self, syn::Error> {
85        let parsed: ItemEnum = input.parse()?;
86        let to_sql_config =
87            ToSqlConfig::from_attributes(parsed.attrs.as_slice())?.unwrap_or_default();
88        Self::new(parsed.ident, parsed.generics, parsed.variants, to_sql_config)
89    }
90}
91
92impl ToTokens for PostgresEnum {
93    fn to_tokens(&self, tokens: &mut TokenStream2) {
94        // It's important we remap all lifetimes we spot to `'static` so they can be used during inventory submission.
95        let name = self.name.clone();
96        let mut static_generics = self.generics.clone();
97        static_generics.params = static_generics
98            .params
99            .clone()
100            .into_iter()
101            .flat_map(|param| match param {
102                item @ syn::GenericParam::Type(_) | item @ syn::GenericParam::Const(_) => {
103                    Some(item)
104                }
105                syn::GenericParam::Lifetime(mut lifetime) => {
106                    lifetime.lifetime.ident = Ident::new("static", Span::call_site());
107                    Some(syn::GenericParam::Lifetime(lifetime))
108                }
109            })
110            .collect();
111        let mut staticless_generics = self.generics.clone();
112        staticless_generics.params = static_generics
113            .params
114            .clone()
115            .into_iter()
116            .flat_map(|param| match param {
117                item @ syn::GenericParam::Type(_) | item @ syn::GenericParam::Const(_) => {
118                    Some(item)
119                }
120                syn::GenericParam::Lifetime(_) => None,
121            })
122            .collect();
123        let (staticless_impl_generics, _staticless_ty_generics, _staticless_where_clauses) =
124            staticless_generics.split_for_impl();
125        let (_static_impl_generics, static_ty_generics, static_where_clauses) =
126            static_generics.split_for_impl();
127
128        let variants = self.variants.iter();
129        let sql_graph_entity_fn_name =
130            syn::Ident::new(&format!("__pgx_internals_enum_{}", name), Span::call_site());
131
132        let to_sql_config = &self.to_sql_config;
133
134        let inv = quote! {
135            unsafe impl #staticless_impl_generics ::pgx::utils::sql_entity_graph::metadata::SqlTranslatable for #name #static_ty_generics #static_where_clauses {
136                fn argument_sql() -> core::result::Result<::pgx::utils::sql_entity_graph::metadata::SqlMapping, ::pgx::utils::sql_entity_graph::metadata::ArgumentError> {
137                    Ok(::pgx::utils::sql_entity_graph::metadata::SqlMapping::As(String::from(stringify!(#name))))
138                }
139
140                fn return_sql() -> core::result::Result<::pgx::utils::sql_entity_graph::metadata::Returns, ::pgx::utils::sql_entity_graph::metadata::ReturnsError> {
141                    Ok(::pgx::utils::sql_entity_graph::metadata::Returns::One(::pgx::utils::sql_entity_graph::metadata::SqlMapping::As(String::from(stringify!(#name)))))
142                }
143            }
144
145            #[no_mangle]
146            #[doc(hidden)]
147            pub extern "Rust" fn  #sql_graph_entity_fn_name() -> ::pgx::utils::sql_entity_graph::SqlGraphEntity {
148                extern crate alloc;
149                use alloc::vec::Vec;
150                use alloc::vec;
151                use ::pgx::WithTypeIds;
152                let mut mappings = Default::default();
153                <#name #static_ty_generics as ::pgx::datum::WithTypeIds>::register_with_refs(&mut mappings, stringify!(#name).to_string());
154                ::pgx::datum::WithSizedTypeIds::<#name #static_ty_generics>::register_sized_with_refs(&mut mappings, stringify!(#name).to_string());
155                ::pgx::datum::WithArrayTypeIds::<#name #static_ty_generics>::register_array_with_refs(&mut mappings, stringify!(#name).to_string());
156                ::pgx::datum::WithVarlenaTypeIds::<#name #static_ty_generics>::register_varlena_with_refs(&mut mappings, stringify!(#name).to_string());
157
158                let submission = ::pgx::utils::sql_entity_graph::PostgresEnumEntity {
159                    name: stringify!(#name),
160                    file: file!(),
161                    line: line!(),
162                    module_path: module_path!(),
163                    full_path: core::any::type_name::<#name #static_ty_generics>(),
164                    mappings,
165                    variants: vec![ #(  stringify!(#variants)  ),* ],
166                    to_sql_config: #to_sql_config,
167                };
168                ::pgx::utils::sql_entity_graph::SqlGraphEntity::Enum(submission)
169            }
170        };
171        tokens.append_all(inv);
172    }
173}