pgx_sql_entity_graph/extension_sql/
mod.rs1pub 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#[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#[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) => (), }
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}