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