pgx_sql_entity_graph/pg_trigger/
mod.rs1pub 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 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 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}