pgx_sql_entity_graph/extension_sql/
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`pgx::extension_sql!()` 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
17*/
18pub mod entity;
19
20use crate::positioning_ref::PositioningRef;
21
22use crate::enrich::{CodeEnrichment, ToEntityGraphTokens, ToRustCodeTokens};
23use proc_macro2::{Ident, Span, TokenStream as TokenStream2};
24use quote::{quote, ToTokens, TokenStreamExt};
25use syn::parse::{Parse, ParseStream};
26use syn::punctuated::Punctuated;
27use syn::{LitStr, Token};
28
29/// A parsed `extension_sql_file!()` item.
30///
31/// It should be used with [`syn::parse::Parse`] functions.
32///
33/// Using [`quote::ToTokens`] will output the declaration for a [`ExtensionSqlEntity`][crate::ExtensionSqlEntity].
34///
35/// ```rust
36/// use syn::{Macro, parse::Parse, parse_quote, parse};
37/// use quote::{quote, ToTokens};
38/// use pgx_sql_entity_graph::ExtensionSqlFile;
39///
40/// # fn main() -> eyre::Result<()> {
41/// use pgx_sql_entity_graph::CodeEnrichment;
42/// let parsed: Macro = parse_quote! {
43///     extension_sql_file!("sql/example.sql", name = "example", bootstrap)
44/// };
45/// let inner_tokens = parsed.tokens;
46/// let inner: CodeEnrichment<ExtensionSqlFile> = parse_quote! {
47///     #inner_tokens
48/// };
49/// let sql_graph_entity_tokens = inner.to_token_stream();
50/// # Ok(())
51/// # }
52/// ```
53#[derive(Debug, Clone)]
54pub struct ExtensionSqlFile {
55    pub path: LitStr,
56    pub attrs: Punctuated<ExtensionSqlAttribute, Token![,]>,
57}
58
59impl ToEntityGraphTokens for ExtensionSqlFile {
60    fn to_entity_graph_tokens(&self) -> TokenStream2 {
61        let path = &self.path;
62        let mut name = None;
63        let mut bootstrap = false;
64        let mut finalize = false;
65        let mut requires = vec![];
66        let mut creates = vec![];
67        for attr in &self.attrs {
68            match attr {
69                ExtensionSqlAttribute::Creates(items) => {
70                    creates.append(&mut items.iter().map(|x| x.to_token_stream()).collect());
71                }
72                ExtensionSqlAttribute::Requires(items) => {
73                    requires.append(&mut items.iter().map(|x| x.to_token_stream()).collect());
74                }
75                ExtensionSqlAttribute::Bootstrap => {
76                    bootstrap = true;
77                }
78                ExtensionSqlAttribute::Finalize => {
79                    finalize = true;
80                }
81                ExtensionSqlAttribute::Name(found_name) => {
82                    name = Some(found_name.value());
83                }
84            }
85        }
86        let name = name.unwrap_or(
87            std::path::PathBuf::from(path.value())
88                .file_stem()
89                .expect("No file name for extension_sql_file!()")
90                .to_str()
91                .expect("No UTF-8 file name for extension_sql_file!()")
92                .to_string(),
93        );
94        let requires_iter = requires.iter();
95        let creates_iter = creates.iter();
96        let sql_graph_entity_fn_name =
97            syn::Ident::new(&format!("__pgx_internals_sql_{}", name.clone()), Span::call_site());
98        quote! {
99            #[no_mangle]
100            #[doc(hidden)]
101            pub extern "Rust" fn  #sql_graph_entity_fn_name() -> ::pgx::pgx_sql_entity_graph::SqlGraphEntity {
102                extern crate alloc;
103                use alloc::vec::Vec;
104                use alloc::vec;
105                let submission = ::pgx::pgx_sql_entity_graph::ExtensionSqlEntity {
106                    sql: include_str!(#path),
107                    module_path: module_path!(),
108                    full_path: concat!(file!(), ':', line!()),
109                    file: file!(),
110                    line: line!(),
111                    name: #name,
112                    bootstrap: #bootstrap,
113                    finalize: #finalize,
114                    requires: vec![#(#requires_iter),*],
115                    creates: vec![#(#creates_iter),*],
116                };
117                ::pgx::pgx_sql_entity_graph::SqlGraphEntity::CustomSql(submission)
118            }
119        }
120    }
121}
122
123impl ToRustCodeTokens for ExtensionSqlFile {}
124
125impl Parse for CodeEnrichment<ExtensionSqlFile> {
126    fn parse(input: ParseStream) -> Result<Self, syn::Error> {
127        let path = input.parse()?;
128        let _after_sql_comma: Option<Token![,]> = input.parse()?;
129        let attrs = input.parse_terminated(ExtensionSqlAttribute::parse)?;
130        Ok(CodeEnrichment(ExtensionSqlFile { path, attrs }))
131    }
132}
133
134/// A parsed `extension_sql!()` item.
135///
136/// It should be used with [`syn::parse::Parse`] functions.
137///
138/// Using [`quote::ToTokens`] will output the declaration for a `pgx::pgx_sql_entity_graph::ExtensionSqlEntity`.
139///
140/// ```rust
141/// use syn::{Macro, parse::Parse, parse_quote, parse};
142/// use quote::{quote, ToTokens};
143/// use pgx_sql_entity_graph::ExtensionSql;
144///
145/// # fn main() -> eyre::Result<()> {
146/// use pgx_sql_entity_graph::CodeEnrichment;
147/// let parsed: Macro = parse_quote! {
148///     extension_sql!("-- Example content", name = "example", bootstrap)
149/// };
150/// let inner_tokens = parsed.tokens;
151/// let inner: CodeEnrichment<ExtensionSql> = parse_quote! {
152///     #inner_tokens
153/// };
154/// let sql_graph_entity_tokens = inner.to_token_stream();
155/// # Ok(())
156/// # }
157/// ```
158#[derive(Debug, Clone)]
159pub struct ExtensionSql {
160    pub sql: LitStr,
161    pub name: LitStr,
162    pub attrs: Punctuated<ExtensionSqlAttribute, Token![,]>,
163}
164
165impl ToEntityGraphTokens for ExtensionSql {
166    fn to_entity_graph_tokens(&self) -> TokenStream2 {
167        let sql = &self.sql;
168        let mut bootstrap = false;
169        let mut finalize = false;
170        let mut creates = vec![];
171        let mut requires = vec![];
172        for attr in &self.attrs {
173            match attr {
174                ExtensionSqlAttribute::Requires(items) => {
175                    requires.append(&mut items.iter().map(|x| x.to_token_stream()).collect());
176                }
177                ExtensionSqlAttribute::Creates(items) => {
178                    creates.append(&mut items.iter().map(|x| x.to_token_stream()).collect());
179                }
180                ExtensionSqlAttribute::Bootstrap => {
181                    bootstrap = true;
182                }
183                ExtensionSqlAttribute::Finalize => {
184                    finalize = true;
185                }
186                ExtensionSqlAttribute::Name(_found_name) => (), // Already done
187            }
188        }
189        let requires_iter = requires.iter();
190        let creates_iter = creates.iter();
191        let name = &self.name;
192
193        let sql_graph_entity_fn_name =
194            syn::Ident::new(&format!("__pgx_internals_sql_{}", name.value()), Span::call_site());
195        quote! {
196            #[no_mangle]
197            pub extern "Rust" fn  #sql_graph_entity_fn_name() -> ::pgx::pgx_sql_entity_graph::SqlGraphEntity {
198                extern crate alloc;
199                use alloc::vec::Vec;
200                use alloc::vec;
201                let submission = ::pgx::pgx_sql_entity_graph::ExtensionSqlEntity {
202                    sql: #sql,
203                    module_path: module_path!(),
204                    full_path: concat!(file!(), ':', line!()),
205                    file: file!(),
206                    line: line!(),
207                    name: #name,
208                    bootstrap: #bootstrap,
209                    finalize: #finalize,
210                    requires: vec![#(#requires_iter),*],
211                    creates: vec![#(#creates_iter),*],
212                };
213                ::pgx::pgx_sql_entity_graph::SqlGraphEntity::CustomSql(submission)
214            }
215        }
216    }
217}
218
219impl ToRustCodeTokens for ExtensionSql {}
220
221impl Parse for CodeEnrichment<ExtensionSql> {
222    fn parse(input: ParseStream) -> Result<Self, syn::Error> {
223        let sql = input.parse()?;
224        let _after_sql_comma: Option<Token![,]> = input.parse()?;
225        let attrs = input.parse_terminated(ExtensionSqlAttribute::parse)?;
226        let mut name = None;
227        for attr in &attrs {
228            match attr {
229                ExtensionSqlAttribute::Name(found_name) => {
230                    name = Some(found_name.clone());
231                }
232                _ => (),
233            }
234        }
235        let name =
236            name.ok_or_else(|| syn::Error::new(input.span(), "expected `name` to be set"))?;
237        Ok(CodeEnrichment(ExtensionSql { sql, attrs, name }))
238    }
239}
240
241impl ToTokens for ExtensionSql {
242    fn to_tokens(&self, tokens: &mut TokenStream2) {
243        tokens.append_all(self.to_entity_graph_tokens())
244    }
245}
246
247#[derive(Debug, Clone)]
248pub enum ExtensionSqlAttribute {
249    Requires(Punctuated<PositioningRef, Token![,]>),
250    Creates(Punctuated<SqlDeclared, Token![,]>),
251    Bootstrap,
252    Finalize,
253    Name(LitStr),
254}
255
256impl Parse for ExtensionSqlAttribute {
257    fn parse(input: ParseStream) -> Result<Self, syn::Error> {
258        let ident: Ident = input.parse()?;
259        let found = match ident.to_string().as_str() {
260            "creates" => {
261                let _eq: syn::token::Eq = input.parse()?;
262                let content;
263                let _bracket = syn::bracketed!(content in input);
264                Self::Creates(content.parse_terminated(SqlDeclared::parse)?)
265            }
266            "requires" => {
267                let _eq: syn::token::Eq = input.parse()?;
268                let content;
269                let _bracket = syn::bracketed!(content in input);
270                Self::Requires(content.parse_terminated(PositioningRef::parse)?)
271            }
272            "bootstrap" => Self::Bootstrap,
273            "finalize" => Self::Finalize,
274            "name" => {
275                let _eq: syn::token::Eq = input.parse()?;
276                Self::Name(input.parse()?)
277            }
278            other => {
279                return Err(syn::Error::new(
280                    ident.span(),
281                    &format!("Unknown extension_sql attribute: {}", other),
282                ))
283            }
284        };
285        Ok(found)
286    }
287}
288
289#[derive(Debug, Clone, Hash, PartialEq, Eq, Ord, PartialOrd)]
290pub enum SqlDeclared {
291    Type(String),
292    Enum(String),
293    Function(String),
294}
295
296impl ToEntityGraphTokens for SqlDeclared {
297    fn to_entity_graph_tokens(&self) -> TokenStream2 {
298        let (variant, identifier) = match &self {
299            SqlDeclared::Type(val) => ("Type", val),
300            SqlDeclared::Enum(val) => ("Enum", val),
301            SqlDeclared::Function(val) => ("Function", val),
302        };
303        let identifier_split = identifier.split("::").collect::<Vec<_>>();
304        let identifier = if identifier_split.len() == 1 {
305            let identifier_infer =
306                Ident::new(identifier_split.last().unwrap(), proc_macro2::Span::call_site());
307            quote! { concat!(module_path!(), "::", stringify!(#identifier_infer)) }
308        } else {
309            quote! { stringify!(#identifier) }
310        };
311        quote! {
312            ::pgx::pgx_sql_entity_graph::SqlDeclaredEntity::build(#variant, #identifier).unwrap()
313        }
314    }
315}
316
317impl ToRustCodeTokens for SqlDeclared {}
318
319impl Parse for SqlDeclared {
320    fn parse(input: ParseStream) -> syn::Result<Self> {
321        let variant: Ident = input.parse()?;
322        let content;
323        let _bracket: syn::token::Paren = syn::parenthesized!(content in input);
324        let identifier_path: syn::Path = content.parse()?;
325        let identifier_str = {
326            let mut identifier_segments = Vec::new();
327            for segment in identifier_path.segments {
328                identifier_segments.push(segment.ident.to_string())
329            }
330            identifier_segments.join("::")
331        };
332        let this = match variant.to_string().as_str() {
333            "Type" => SqlDeclared::Type(identifier_str),
334            "Enum" => SqlDeclared::Enum(identifier_str),
335            "Function" => SqlDeclared::Function(identifier_str),
336            _ => return Err(syn::Error::new(
337                variant.span(),
338                "SQL declared entities must be `Type(ident)`, `Enum(ident)`, or `Function(ident)`",
339            )),
340        };
341        Ok(this)
342    }
343}
344
345impl ToTokens for SqlDeclared {
346    fn to_tokens(&self, tokens: &mut TokenStream2) {
347        tokens.append_all(self.to_entity_graph_tokens())
348    }
349}