pgx_utils/sql_entity_graph/pg_trigger/
mod.rs

1/*!
2
3`#[pg_trigger]` related macro expansion for Rust to SQL translation
4
5> Like all of the [`sql_entity_graph`][crate::sql_entity_graph] APIs, this is considered **internal**
6to the `pgx` framework and very subject to change between versions. While you may use this, please do it with caution.
7
8*/
9pub mod attribute;
10pub mod entity;
11
12use crate::sql_entity_graph::ToSqlConfig;
13use attribute::PgTriggerAttribute;
14use proc_macro2::{Span, TokenStream as TokenStream2};
15use quote::{quote, ToTokens, TokenStreamExt};
16use syn::{ItemFn, Token};
17
18#[derive(Debug, Clone)]
19pub struct PgTrigger {
20    func: syn::ItemFn,
21    to_sql_config: ToSqlConfig,
22}
23
24impl PgTrigger {
25    pub fn new(
26        func: ItemFn,
27        attributes: syn::punctuated::Punctuated<PgTriggerAttribute, Token![,]>,
28    ) -> Result<Self, syn::Error> {
29        if attributes.len() > 1 {
30            return Err(syn::Error::new(
31                Span::call_site(),
32                "Multiple `sql` arguments found, it must be unique",
33            ));
34        };
35        let to_sql_config = attributes
36            .first()
37            .cloned()
38            .map(|PgTriggerAttribute::Sql(mut config)| {
39                if let Some(ref mut content) = config.content {
40                    let value = content.value();
41                    let updated_value = value
42                        .replace("@FUNCTION_NAME@", &*(func.sig.ident.to_string() + "_wrapper"))
43                        + "\n";
44                    *content = syn::LitStr::new(&updated_value, Span::call_site());
45                };
46                config
47            })
48            .unwrap_or_default();
49
50        if !to_sql_config.overrides_default() {
51            crate::ident_is_acceptable_to_postgres(&func.sig.ident)?;
52        }
53
54        Ok(Self { func, to_sql_config })
55    }
56
57    pub fn entity_tokens(&self) -> Result<ItemFn, syn::Error> {
58        let sql_graph_entity_fn_name = syn::Ident::new(
59            &format!("__pgx_internals_trigger_{}", self.func.sig.ident.to_string()),
60            self.func.sig.ident.span(),
61        );
62        let func_sig_ident = &self.func.sig.ident;
63        let function_name = func_sig_ident.to_string();
64        let to_sql_config = &self.to_sql_config;
65
66        let tokens = quote! {
67            #[no_mangle]
68            #[doc(hidden)]
69            pub extern "Rust" fn #sql_graph_entity_fn_name() -> ::pgx::utils::sql_entity_graph::SqlGraphEntity {
70                use core::any::TypeId;
71                extern crate alloc;
72                use alloc::vec::Vec;
73                use alloc::vec;
74                let submission = ::pgx::utils::sql_entity_graph::PgTriggerEntity {
75                    function_name: #function_name,
76                    file: file!(),
77                    line: line!(),
78                    full_path: concat!(module_path!(), "::", stringify!(#func_sig_ident)),
79                    module_path: module_path!(),
80                    to_sql_config: #to_sql_config,
81                };
82                ::pgx::utils::sql_entity_graph::SqlGraphEntity::Trigger(submission)
83            }
84        };
85        syn::parse2(tokens)
86    }
87
88    pub fn wrapper_tokens(&self) -> Result<ItemFn, syn::Error> {
89        let function_ident = &self.func.sig.ident;
90        let extern_func_ident = syn::Ident::new(
91            &format!("{}_wrapper", self.func.sig.ident.to_string()),
92            self.func.sig.ident.span(),
93        );
94        let tokens = quote! {
95            #[no_mangle]
96            #[pgx::pg_guard]
97            extern "C" fn #extern_func_ident(fcinfo: ::pgx::pg_sys::FunctionCallInfo) -> ::pgx::pg_sys::Datum {
98                let maybe_pg_trigger = unsafe { ::pgx::trigger_support::PgTrigger::from_fcinfo(fcinfo) };
99                let pg_trigger = maybe_pg_trigger.expect("PgTrigger::from_fcinfo failed");
100                let trigger_fn_result: Result<
101                    ::pgx::heap_tuple::PgHeapTuple<'_, _>,
102                    _,
103                > = #function_ident(&pg_trigger);
104
105                let trigger_retval = trigger_fn_result.expect("Trigger function panic");
106                match trigger_retval.into_trigger_datum() {
107                    None => ::pgx::pg_return_null(fcinfo),
108                    Some(datum) => datum,
109                }
110            }
111
112        };
113        syn::parse2(tokens)
114    }
115
116    pub fn finfo_tokens(&self) -> Result<ItemFn, syn::Error> {
117        let finfo_name = syn::Ident::new(
118            &format!("pg_finfo_{}_wrapper", self.func.sig.ident),
119            proc_macro2::Span::call_site(),
120        );
121        let tokens = quote! {
122            #[no_mangle]
123            #[doc(hidden)]
124            pub extern "C" fn #finfo_name() -> &'static ::pgx::pg_sys::Pg_finfo_record {
125                const V1_API: ::pgx::pg_sys::Pg_finfo_record = ::pgx::pg_sys::Pg_finfo_record { api_version: 1 };
126                &V1_API
127            }
128        };
129        syn::parse2(tokens)
130    }
131}
132
133impl ToTokens for PgTrigger {
134    fn to_tokens(&self, tokens: &mut TokenStream2) {
135        let entity_func = self.entity_tokens().expect("Generating entity function for trigger");
136        let wrapper_func = self.wrapper_tokens().expect("Generating wrappper function for trigger");
137        let finfo_func = self.finfo_tokens().expect("Generating finfo function for trigger");
138        let func = &self.func;
139
140        let items = quote! {
141            #func
142
143            #wrapper_func
144
145            #finfo_func
146
147            #entity_func
148        };
149        tokens.append_all(items);
150    }
151}