pgx_sql_entity_graph/schema/
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`#[pg_schema]` related macro expansion for Rust to SQL translation
12
13> Like all of the [`sql_entity_graph`][crate::pgx_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::TokenStream as TokenStream2;
20use quote::{quote, ToTokens, TokenStreamExt};
21use syn::parse::{Parse, ParseStream};
22use syn::ItemMod;
23
24/// A parsed `#[pg_schema] mod example {}` item.
25///
26/// It should be used with [`syn::parse::Parse`] functions.
27///
28/// Using [`quote::ToTokens`] will output the declaration for a `pgx::datum::pgx_sql_entity_graph::InventorySchema`.
29///
30/// ```rust
31/// use syn::{Macro, parse::Parse, parse_quote, parse};
32/// use quote::{quote, ToTokens};
33/// use pgx_sql_entity_graph::Schema;
34///
35/// # fn main() -> eyre::Result<()> {
36/// let parsed: Schema = parse_quote! {
37///     #[pg_schema] mod example {}
38/// };
39/// let entity_tokens = parsed.to_token_stream();
40/// # Ok(())
41/// # }
42/// ```
43#[derive(Debug, Clone)]
44pub struct Schema {
45    pub module: ItemMod,
46}
47
48impl Schema {
49    /*
50       It's necessary for `Schema` to handle the full `impl ToTokens` generation itself as the sql
51       entity graph code has to be inside the same `mod {}` that the `#[pg_schema]` macro is
52       attached to.
53
54       To facilitate that, we feature flag the `.entity_tokens()` function here to be a no-op if
55       the `no-schema-generation` feature flag is turned on
56    */
57
58    #[cfg(feature = "no-schema-generation")]
59    fn entity_tokens(&self) -> TokenStream2 {
60        quote! {}
61    }
62
63    #[cfg(not(feature = "no-schema-generation"))]
64    fn entity_tokens(&self) -> TokenStream2 {
65        let ident = &self.module.ident;
66        let postfix = {
67            use std::hash::{Hash, Hasher};
68
69            let (_content_brace, content_items) =
70                &self.module.content.as_ref().expect("Can only support `mod {}` right now.");
71
72            // A hack until https://github.com/rust-lang/rust/issues/54725 is fixed.
73            let mut hasher = std::collections::hash_map::DefaultHasher::new();
74            content_items.hash(&mut hasher);
75            hasher.finish()
76            // End of hack
77        };
78
79        let sql_graph_entity_fn_name = syn::Ident::new(
80            &format!("__pgx_internals_schema_{}_{}", ident, postfix),
81            proc_macro2::Span::call_site(),
82        );
83        quote! {
84            #[no_mangle]
85            #[doc(hidden)]
86            pub extern "Rust" fn  #sql_graph_entity_fn_name() -> ::pgx::pgx_sql_entity_graph::SqlGraphEntity {
87                extern crate alloc;
88                use alloc::vec::Vec;
89                use alloc::vec;
90                let submission = ::pgx::pgx_sql_entity_graph::SchemaEntity {
91                        module_path: module_path!(),
92                        name: stringify!(#ident),
93                        file: file!(),
94                        line: line!(),
95                    };
96                ::pgx::pgx_sql_entity_graph::SqlGraphEntity::Schema(submission)
97            }
98        }
99    }
100}
101
102// We can't use the `CodeEnrichment` infrastructure, so we implement [`ToTokens`] directly
103impl ToTokens for Schema {
104    fn to_tokens(&self, tokens: &mut TokenStream2) {
105        let attrs = &self.module.attrs;
106        let vis = &self.module.vis;
107        let mod_token = &self.module.mod_token;
108        let ident = &self.module.ident;
109        let graph_tokens = self.entity_tokens(); // NB:  this could be an empty TokenStream if `no-schema-generation` is turned on
110
111        let (_content_brace, content_items) =
112            &self.module.content.as_ref().expect("Can only support `mod {}` right now.");
113
114        let code = quote! {
115            #(#attrs)*
116            #vis #mod_token #ident {
117                #(#content_items)*
118                #graph_tokens
119            }
120        };
121
122        tokens.append_all(code)
123    }
124}
125
126impl Parse for Schema {
127    fn parse(input: ParseStream) -> Result<Self, syn::Error> {
128        let module: ItemMod = input.parse()?;
129        crate::ident_is_acceptable_to_postgres(&module.ident)?;
130        Ok(Self { module })
131    }
132}