pgx_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::pgx_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::enrich::{ToEntityGraphTokens, ToRustCodeTokens};
13use crate::{CodeEnrichment, ToSqlConfig};
14use attribute::PgTriggerAttribute;
15use proc_macro2::{Span, TokenStream as TokenStream2};
16use quote::quote;
17use syn::{ItemFn, Token};
18
19#[derive(Debug, Clone)]
20pub struct PgTrigger {
21    func: syn::ItemFn,
22    to_sql_config: ToSqlConfig,
23}
24
25impl PgTrigger {
26    pub fn new(
27        func: ItemFn,
28        attributes: syn::punctuated::Punctuated<PgTriggerAttribute, Token![,]>,
29    ) -> Result<CodeEnrichment<Self>, syn::Error> {
30        if attributes.len() > 1 {
31            return Err(syn::Error::new(
32                Span::call_site(),
33                "Multiple `sql` arguments found, it must be unique",
34            ));
35        };
36        let to_sql_config = attributes
37            .first()
38            .cloned()
39            .map(|PgTriggerAttribute::Sql(mut config)| {
40                if let Some(ref mut content) = config.content {
41                    let value = content.value();
42                    let updated_value = value
43                        .replace("@FUNCTION_NAME@", &*(func.sig.ident.to_string() + "_wrapper"))
44                        + "\n";
45                    *content = syn::LitStr::new(&updated_value, Span::call_site());
46                };
47                config
48            })
49            .unwrap_or_default();
50
51        if !to_sql_config.overrides_default() {
52            crate::ident_is_acceptable_to_postgres(&func.sig.ident)?;
53        }
54
55        Ok(CodeEnrichment(PgTrigger { func, to_sql_config }))
56    }
57
58    pub fn wrapper_tokens(&self) -> Result<ItemFn, syn::Error> {
59        let function_ident = &self.func.sig.ident;
60        let extern_func_ident = syn::Ident::new(
61            &format!("{}_wrapper", self.func.sig.ident.to_string()),
62            self.func.sig.ident.span(),
63        );
64        let tokens = quote! {
65            #[no_mangle]
66            #[::pgx::pgx_macros::pg_guard]
67            unsafe extern "C" fn #extern_func_ident(fcinfo: ::pgx::pg_sys::FunctionCallInfo) -> ::pgx::pg_sys::Datum {
68                let fcinfo_ref = unsafe {
69                    // SAFETY:  The caller should be Postgres in this case and it will give us a valid "fcinfo" pointer
70                    fcinfo.as_ref().expect("fcinfo was NULL from Postgres")
71                };
72                let maybe_pg_trigger = unsafe { ::pgx::trigger_support::PgTrigger::from_fcinfo(fcinfo_ref) };
73                let pg_trigger = maybe_pg_trigger.expect("PgTrigger::from_fcinfo failed");
74                let trigger_fn_result: Result<
75                    Option<::pgx::heap_tuple::PgHeapTuple<'_, _>>,
76                    _,
77                > = #function_ident(&pg_trigger);
78
79
80                // The trigger "protocol" allows a function to return the null pointer, but NOT to
81                // set the isnull result flag.  This is why we return `Datum::from(0)` in the None cases
82                let trigger_retval = trigger_fn_result.expect("Trigger function panic");
83                match trigger_retval {
84                    None => unsafe { ::pgx::pg_sys::Datum::from(0) },
85                    Some(trigger_retval) => match trigger_retval.into_trigger_datum() {
86                        None => unsafe { ::pgx::pg_sys::Datum::from(0) },
87                        Some(datum) => datum,
88                    }
89                }
90            }
91
92        };
93        syn::parse2(tokens)
94    }
95
96    pub fn finfo_tokens(&self) -> Result<ItemFn, syn::Error> {
97        let finfo_name = syn::Ident::new(
98            &format!("pg_finfo_{}_wrapper", self.func.sig.ident),
99            proc_macro2::Span::call_site(),
100        );
101        let tokens = quote! {
102            #[no_mangle]
103            #[doc(hidden)]
104            pub extern "C" fn #finfo_name() -> &'static ::pgx::pg_sys::Pg_finfo_record {
105                const V1_API: ::pgx::pg_sys::Pg_finfo_record = ::pgx::pg_sys::Pg_finfo_record { api_version: 1 };
106                &V1_API
107            }
108        };
109        syn::parse2(tokens)
110    }
111}
112
113impl ToEntityGraphTokens for PgTrigger {
114    fn to_entity_graph_tokens(&self) -> TokenStream2 {
115        let sql_graph_entity_fn_name = syn::Ident::new(
116            &format!("__pgx_internals_trigger_{}", self.func.sig.ident.to_string()),
117            self.func.sig.ident.span(),
118        );
119        let func_sig_ident = &self.func.sig.ident;
120        let function_name = func_sig_ident.to_string();
121        let to_sql_config = &self.to_sql_config;
122
123        quote! {
124            #[no_mangle]
125            #[doc(hidden)]
126            pub extern "Rust" fn #sql_graph_entity_fn_name() -> ::pgx::pgx_sql_entity_graph::SqlGraphEntity {
127                use core::any::TypeId;
128                extern crate alloc;
129                use alloc::vec::Vec;
130                use alloc::vec;
131                let submission = ::pgx::pgx_sql_entity_graph::PgTriggerEntity {
132                    function_name: #function_name,
133                    file: file!(),
134                    line: line!(),
135                    full_path: concat!(module_path!(), "::", stringify!(#func_sig_ident)),
136                    module_path: module_path!(),
137                    to_sql_config: #to_sql_config,
138                };
139                ::pgx::pgx_sql_entity_graph::SqlGraphEntity::Trigger(submission)
140            }
141        }
142    }
143}
144
145impl ToRustCodeTokens for PgTrigger {
146    fn to_rust_code_tokens(&self) -> TokenStream2 {
147        let wrapper_func = self.wrapper_tokens().expect("Generating wrappper function for trigger");
148        let finfo_func = self.finfo_tokens().expect("Generating finfo function for trigger");
149        let func = &self.func;
150
151        quote! {
152            #func
153            #wrapper_func
154            #finfo_func
155        }
156    }
157}