pgrx_sql_entity_graph/pg_trigger/
mod.rs1pub mod attribute;
19pub mod entity;
20
21use crate::enrich::{ToEntityGraphTokens, ToRustCodeTokens};
22use crate::finfo::{finfo_v1_extern_c, finfo_v1_tokens};
23use crate::{CodeEnrichment, ToSqlConfig};
24use attribute::PgTriggerAttribute;
25use proc_macro2::{Ident, Span, TokenStream as TokenStream2};
26use quote::{format_ident, quote};
27use syn::{ItemFn, Token, spanned::Spanned};
28
29#[derive(Debug, Clone)]
30pub struct PgTrigger {
31 func: syn::ItemFn,
32 to_sql_config: ToSqlConfig,
33}
34
35impl PgTrigger {
36 pub fn new(
37 func: ItemFn,
38 attributes: syn::punctuated::Punctuated<PgTriggerAttribute, Token![,]>,
39 ) -> Result<CodeEnrichment<Self>, syn::Error> {
40 if attributes.len() > 1 {
41 return Err(syn::Error::new(
43 func.span(),
44 "Multiple `sql` arguments found, it must be unique",
45 ));
46 };
47 let to_sql_config = attributes
48 .first()
49 .cloned()
50 .map(|PgTriggerAttribute::Sql(mut config)| {
51 if let Some(ref mut content) = config.content {
52 let value = content.value();
53 let span = content.span();
55 let updated_value = value
56 .replace("@FUNCTION_NAME@", &(func.sig.ident.to_string() + "_wrapper"))
57 + "\n";
58 *content = syn::LitStr::new(&updated_value, span);
59 };
60 config
61 })
62 .unwrap_or_default();
63
64 if !to_sql_config.overrides_default() {
65 crate::ident_is_acceptable_to_postgres(&func.sig.ident)?;
66 }
67
68 Ok(CodeEnrichment(PgTrigger { func, to_sql_config }))
69 }
70
71 pub fn wrapper_tokens(&self) -> Result<ItemFn, syn::Error> {
72 let function_ident = self.func.sig.ident.clone();
73 let fcinfo_ident =
74 Ident::new("_fcinfo", Span::mixed_site().located_at(function_ident.span()));
75
76 let tokens = quote! {
77 fn _internal(fcinfo: ::pgrx::pg_sys::FunctionCallInfo) -> ::pgrx::pg_sys::Datum {
78 let fcinfo_ref = unsafe {
79 fcinfo.as_ref().expect("fcinfo was NULL from Postgres")
81 };
82 let maybe_pg_trigger = unsafe { ::pgrx::trigger_support::PgTrigger::from_fcinfo(fcinfo_ref) };
83 let pg_trigger = maybe_pg_trigger.expect("PgTrigger::from_fcinfo failed");
84 let trigger_fn_result: Result<
85 Option<::pgrx::heap_tuple::PgHeapTuple<'_, _>>,
86 _,
87 > = #function_ident(&pg_trigger);
88
89
90 let trigger_retval = trigger_fn_result.expect("Trigger function panic");
93 match trigger_retval {
94 None => unsafe { ::pgrx::pg_sys::Datum::from(0) },
95 Some(trigger_retval) => match trigger_retval.into_trigger_datum() {
96 None => unsafe { ::pgrx::pg_sys::Datum::from(0) },
97 Some(datum) => datum,
98 }
99 }
100 }
101 ::pgrx::pg_sys::submodules::panic::pgrx_extern_c_guard(move || _internal(#fcinfo_ident))
102 };
103
104 finfo_v1_extern_c(&self.func, fcinfo_ident, tokens)
105 }
106}
107
108impl ToEntityGraphTokens for PgTrigger {
109 fn to_entity_graph_tokens(&self) -> TokenStream2 {
110 let func_sig_ident = &self.func.sig.ident;
111 let sql_graph_entity_fn_name = format_ident!("__pgrx_schema_trigger_{}", func_sig_ident);
112 let function_name = func_sig_ident.to_string();
113 let to_sql_config = &self.to_sql_config;
114 let to_sql_config_len = to_sql_config.section_len_tokens();
115 let payload_len = quote! {
116 ::pgrx::pgrx_sql_entity_graph::section::u8_len()
117 + ::pgrx::pgrx_sql_entity_graph::section::str_len(#function_name)
118 + ::pgrx::pgrx_sql_entity_graph::section::str_len(file!())
119 + ::pgrx::pgrx_sql_entity_graph::section::u32_len()
120 + ::pgrx::pgrx_sql_entity_graph::section::str_len(module_path!())
121 + ::pgrx::pgrx_sql_entity_graph::section::str_len(concat!(module_path!(), "::", stringify!(#func_sig_ident)))
122 + (#to_sql_config_len)
123 };
124 let total_len = quote! {
125 ::pgrx::pgrx_sql_entity_graph::section::u32_len() + (#payload_len)
126 };
127 let writer = to_sql_config.section_writer_tokens(quote! {
128 ::pgrx::pgrx_sql_entity_graph::section::EntryWriter::<{ #total_len }>::new()
129 .u32((#payload_len) as u32)
130 .u8(::pgrx::pgrx_sql_entity_graph::section::ENTITY_TRIGGER)
131 .str(#function_name)
132 .str(file!())
133 .u32(line!())
134 .str(module_path!())
135 .str(concat!(module_path!(), "::", stringify!(#func_sig_ident)))
136 });
137
138 quote! {
139 ::pgrx::pgrx_sql_entity_graph::__pgrx_schema_entry!(
140 #sql_graph_entity_fn_name,
141 #total_len,
142 #writer.finish()
143 );
144 }
145 }
146}
147
148impl ToRustCodeTokens for PgTrigger {
149 fn to_rust_code_tokens(&self) -> TokenStream2 {
150 let wrapper_func = self.wrapper_tokens().expect("Generating wrapper function for trigger");
151 let finfo_func = finfo_v1_tokens(wrapper_func.sig.ident.clone()).unwrap();
152 let func = &self.func;
153
154 quote! {
155 #func
156 #wrapper_func
157 #finfo_func
158 }
159 }
160}