pgrx_macros/
lib.rs

1//LICENSE Portions Copyright 2019-2021 ZomboDB, LLC.
2//LICENSE
3//LICENSE Portions Copyright 2021-2023 Technology Concepts & Design, Inc.
4//LICENSE
5//LICENSE Portions Copyright 2023-2023 PgCentral Foundation, Inc. <contact@pgcentral.org>
6//LICENSE
7//LICENSE All rights reserved.
8//LICENSE
9//LICENSE Use of this source code is governed by the MIT license that can be found in the LICENSE file.
10extern crate proc_macro;
11
12use proc_macro::TokenStream;
13use std::collections::HashSet;
14
15use proc_macro2::Ident;
16use quote::{format_ident, quote, ToTokens};
17use syn::spanned::Spanned;
18use syn::{parse_macro_input, Attribute, Data, DeriveInput, Item, ItemImpl};
19
20use operators::{deriving_postgres_eq, deriving_postgres_hash, deriving_postgres_ord};
21use pgrx_sql_entity_graph as sql_gen;
22use sql_gen::{
23    parse_extern_attributes, CodeEnrichment, ExtensionSql, ExtensionSqlFile, ExternArgs,
24    PgAggregate, PgCast, PgExtern, PostgresEnum, Schema,
25};
26
27mod operators;
28mod rewriter;
29
30/// Declare a function as `#[pg_guard]` to indicate that it is called from a Postgres `extern "C"`
31/// function so that Rust `panic!()`s (and Postgres `elog(ERROR)`s) will be properly handled by `pgrx`
32#[proc_macro_attribute]
33pub fn pg_guard(_attr: TokenStream, item: TokenStream) -> TokenStream {
34    // get a usable token stream
35    let ast = parse_macro_input!(item as syn::Item);
36
37    let res = match ast {
38        // this is for processing the members of extern "C" { } blocks
39        // functions inside the block get wrapped as public, top-level unsafe functions that are not "extern"
40        Item::ForeignMod(block) => Ok(rewriter::extern_block(block)),
41
42        // process top-level functions
43        Item::Fn(func) => rewriter::item_fn_without_rewrite(func),
44        unknown => Err(syn::Error::new(
45            unknown.span(),
46            "#[pg_guard] can only be applied to extern \"C\" blocks and top-level functions",
47        )),
48    };
49    res.unwrap_or_else(|e| e.into_compile_error()).into()
50}
51
52/// `#[pg_test]` functions are test functions (akin to `#[test]`), but they run in-process inside
53/// Postgres during `cargo pgrx test`.
54///
55/// This can be combined with test attributes like [`#[should_panic(expected = "..")]`][expected].
56///
57/// [expected]: https://doc.rust-lang.org/reference/attributes/testing.html#the-should_panic-attribute
58#[proc_macro_attribute]
59pub fn pg_test(attr: TokenStream, item: TokenStream) -> TokenStream {
60    let mut stream = proc_macro2::TokenStream::new();
61    let args = parse_extern_attributes(proc_macro2::TokenStream::from(attr.clone()));
62
63    let mut expected_error = None;
64    args.into_iter().for_each(|v| {
65        if let ExternArgs::ShouldPanic(message) = v {
66            expected_error = Some(message)
67        }
68    });
69
70    let ast = parse_macro_input!(item as syn::Item);
71
72    match ast {
73        Item::Fn(mut func) => {
74            // Here we need to break out attributes into test and non-test attributes,
75            // so the generated #[test] attributes are in the appropriate place.
76            let mut test_attributes = Vec::new();
77            let mut non_test_attributes = Vec::new();
78
79            for attribute in func.attrs.iter() {
80                if let Some(ident) = attribute.path().get_ident() {
81                    let ident_str = ident.to_string();
82
83                    if ident_str == "ignore" || ident_str == "should_panic" {
84                        test_attributes.push(attribute.clone());
85                    } else {
86                        non_test_attributes.push(attribute.clone());
87                    }
88                } else {
89                    non_test_attributes.push(attribute.clone());
90                }
91            }
92
93            func.attrs = non_test_attributes;
94
95            stream.extend(proc_macro2::TokenStream::from(pg_extern(
96                attr,
97                Item::Fn(func.clone()).to_token_stream().into(),
98            )));
99
100            let expected_error = match expected_error {
101                Some(msg) => quote! {Some(#msg)},
102                None => quote! {None},
103            };
104
105            let sql_funcname = func.sig.ident.to_string();
106            let test_func_name = format_ident!("pg_{}", func.sig.ident);
107
108            let attributes = func.attrs;
109            let mut att_stream = proc_macro2::TokenStream::new();
110
111            for a in attributes.iter() {
112                let as_str = a.to_token_stream().to_string();
113                att_stream.extend(quote! {
114                    options.push(#as_str);
115                });
116            }
117
118            stream.extend(quote! {
119                #[test]
120                #(#test_attributes)*
121                fn #test_func_name() {
122                    let mut options = Vec::new();
123                    #att_stream
124
125                    crate::pg_test::setup(options);
126                    let res = pgrx_tests::run_test(#sql_funcname, #expected_error, crate::pg_test::postgresql_conf_options());
127                    match res {
128                        Ok(()) => (),
129                        Err(e) => panic!("{e:?}")
130                    }
131                }
132            });
133        }
134
135        thing => {
136            return syn::Error::new(
137                thing.span(),
138                "#[pg_test] can only be applied to top-level functions",
139            )
140            .into_compile_error()
141            .into()
142        }
143    }
144
145    stream.into()
146}
147
148/// Associated macro for `#[pg_test]` to provide context back to your test framework to indicate
149/// that the test system is being initialized
150#[proc_macro_attribute]
151pub fn initialize(_attr: TokenStream, item: TokenStream) -> TokenStream {
152    item
153}
154
155/**
156Declare a function as `#[pg_cast]` to indicate that it represents a Postgres [cast](https://www.postgresql.org/docs/current/sql-createcast.html).
157
158* `assignment`: Corresponds to [`AS ASSIGNMENT`](https://www.postgresql.org/docs/current/sql-createcast.html).
159* `implicit`: Corresponds to [`AS IMPLICIT`](https://www.postgresql.org/docs/current/sql-createcast.html).
160
161By default if no attribute is specified, the cast function can only be used in an explicit cast.
162
163Functions MUST accept and return exactly one value whose type MUST be a `pgrx` supported type. `pgrx` supports many PostgreSQL types by default.
164New types can be defined via [`macro@PostgresType`] or [`macro@PostgresEnum`].
165
166`#[pg_cast]` also supports all the attributes supported by the [`macro@pg_extern]` macro, which are
167passed down to the underlying function.
168
169Example usage:
170```rust,ignore
171use pgrx::*;
172#[pg_cast(implicit)]
173fn cast_json_to_int(input: Json) -> i32 { todo!() }
174*/
175#[proc_macro_attribute]
176pub fn pg_cast(attr: TokenStream, item: TokenStream) -> TokenStream {
177    fn wrapped(attr: TokenStream, item: TokenStream) -> Result<TokenStream, syn::Error> {
178        use syn::parse::Parser;
179        use syn::punctuated::Punctuated;
180
181        let mut cast = None;
182        let mut pg_extern_attrs = proc_macro2::TokenStream::new();
183
184        // look for the attributes `#[pg_cast]` directly understands
185        match Punctuated::<syn::Path, syn::Token![,]>::parse_terminated.parse(attr) {
186            Ok(paths) => {
187                let mut new_paths = Punctuated::<syn::Path, syn::Token![,]>::new();
188                for path in paths {
189                    match (PgCast::try_from(path), &cast) {
190                        (Ok(style), None) => cast = Some(style),
191                        (Ok(_), Some(cast)) => {
192                            panic!("The cast type has already been set to `{cast:?}`")
193                        }
194
195                        // ... and anything it doesn't understand is blindly passed through to the
196                        // underlying `#[pg_extern]` function that gets created, which will ultimately
197                        // decide what's naughty and what's nice
198                        (Err(unknown), _) => {
199                            new_paths.push(unknown);
200                        }
201                    }
202                }
203
204                pg_extern_attrs.extend(new_paths.into_token_stream());
205            }
206            Err(err) => {
207                panic!("Failed to parse attribute to pg_cast: {err}")
208            }
209        }
210
211        let pg_extern = PgExtern::new(pg_extern_attrs.into(), item.clone().into())?.0;
212        Ok(CodeEnrichment(pg_extern.as_cast(cast.unwrap_or_default())).to_token_stream().into())
213    }
214
215    wrapped(attr, item).unwrap_or_else(|e: syn::Error| e.into_compile_error().into())
216}
217
218/// Declare a function as `#[pg_operator]` to indicate that it represents a Postgres operator
219/// `cargo pgrx schema` will automatically generate the underlying SQL
220#[proc_macro_attribute]
221pub fn pg_operator(attr: TokenStream, item: TokenStream) -> TokenStream {
222    pg_extern(attr, item)
223}
224
225/// Used with `#[pg_operator]`.  1 value which is the operator name itself
226#[proc_macro_attribute]
227pub fn opname(_attr: TokenStream, item: TokenStream) -> TokenStream {
228    item
229}
230
231/// Used with `#[pg_operator]`.  1 value which is the function name
232#[proc_macro_attribute]
233pub fn commutator(_attr: TokenStream, item: TokenStream) -> TokenStream {
234    item
235}
236
237/// Used with `#[pg_operator]`.  1 value which is the function name
238#[proc_macro_attribute]
239pub fn negator(_attr: TokenStream, item: TokenStream) -> TokenStream {
240    item
241}
242
243/// Used with `#[pg_operator]`.  1 value which is the function name
244#[proc_macro_attribute]
245pub fn restrict(_attr: TokenStream, item: TokenStream) -> TokenStream {
246    item
247}
248
249/// Used with `#[pg_operator]`.  1 value which is the function name
250#[proc_macro_attribute]
251pub fn join(_attr: TokenStream, item: TokenStream) -> TokenStream {
252    item
253}
254
255/// Used with `#[pg_operator]`.  no values
256#[proc_macro_attribute]
257pub fn hashes(_attr: TokenStream, item: TokenStream) -> TokenStream {
258    item
259}
260
261/// Used with `#[pg_operator]`.  no values
262#[proc_macro_attribute]
263pub fn merges(_attr: TokenStream, item: TokenStream) -> TokenStream {
264    item
265}
266
267/**
268Declare a Rust module and its contents to be in a schema.
269
270The schema name will always be the `mod`'s identifier. So `mod flop` will create a `flop` schema.
271
272If there is a schema inside a schema, the most specific schema is chosen.
273
274In this example, the created `example` function is in the `dsl_filters` schema.
275
276```rust,ignore
277use pgrx::*;
278
279#[pg_schema]
280mod dsl {
281    use pgrx::*;
282    #[pg_schema]
283    mod dsl_filters {
284        use pgrx::*;
285        #[pg_extern]
286        fn example() { todo!() }
287    }
288}
289```
290
291File modules (like `mod name;`) aren't able to be supported due to [`rust/#54725`](https://github.com/rust-lang/rust/issues/54725).
292
293*/
294#[proc_macro_attribute]
295pub fn pg_schema(_attr: TokenStream, input: TokenStream) -> TokenStream {
296    fn wrapped(input: TokenStream) -> Result<TokenStream, syn::Error> {
297        let pgrx_schema: Schema = syn::parse(input)?;
298        Ok(pgrx_schema.to_token_stream().into())
299    }
300
301    wrapped(input).unwrap_or_else(|e: syn::Error| e.into_compile_error().into())
302}
303
304/**
305Declare SQL to be included in generated extension script.
306
307Accepts a String literal, a `name` attribute, and optionally others:
308
309* `name = "item"`: Set the unique identifier to `"item"` for use in `requires` declarations.
310* `requires = [item, item_two]`: References to other `name`s or Rust items which this SQL should be present after.
311* `creates = [ Type(submod::Cust), Enum(Pre), Function(defined)]`: Communicates that this SQL block creates certain entities.
312  Please note it **does not** create matching Rust types.
313* `bootstrap` (**Unique**): Communicates that this is SQL intended to go before all other generated SQL.
314* `finalize` (**Unique**): Communicates that this is SQL intended to go after all other generated SQL.
315
316You can declare some SQL without any positioning information, meaning it can end up anywhere in the generated SQL:
317
318```rust,ignore
319use pgrx_macros::extension_sql;
320
321extension_sql!(
322    r#"
323    -- SQL statements
324    "#,
325    name = "demo",
326);
327```
328
329To cause the SQL to be output at the start of the generated SQL:
330
331```rust,ignore
332use pgrx_macros::extension_sql;
333
334extension_sql!(
335    r#"
336    -- SQL statements
337    "#,
338    name = "demo",
339    bootstrap,
340);
341```
342
343To cause the SQL to be output at the end of the generated SQL:
344
345```rust,ignore
346use pgrx_macros::extension_sql;
347
348extension_sql!(
349    r#"
350    -- SQL statements
351    "#,
352    name = "demo",
353    finalize,
354);
355```
356
357To declare the SQL dependent, or a dependency of, other items:
358
359```rust,ignore
360use pgrx_macros::extension_sql;
361
362struct Treat;
363
364mod dog_characteristics {
365    enum DogAlignment {
366        Good
367    }
368}
369
370extension_sql!(r#"
371    -- SQL statements
372    "#,
373    name = "named_one",
374);
375
376extension_sql!(r#"
377    -- SQL statements
378    "#,
379    name = "demo",
380    requires = [ "named_one", dog_characteristics::DogAlignment ],
381);
382```
383
384To declare the SQL defines some entity (**Caution:** This is not recommended usage):
385
386```rust,ignore
387use pgrx::stringinfo::StringInfo;
388use pgrx::*;
389use pgrx_utils::get_named_capture;
390
391#[derive(Debug)]
392#[repr(C)]
393struct Complex {
394    x: f64,
395    y: f64,
396}
397
398extension_sql!(r#"\
399        CREATE TYPE complex;\
400    "#,
401    name = "create_complex_type",
402    creates = [Type(Complex)],
403);
404
405#[pg_extern(immutable)]
406fn complex_in(input: &core::ffi::CStr) -> PgBox<Complex> {
407    todo!()
408}
409
410#[pg_extern(immutable)]
411fn complex_out(complex: PgBox<Complex>) -> &'static ::core::ffi::CStr {
412    todo!()
413}
414
415extension_sql!(r#"\
416        CREATE TYPE complex (
417            internallength = 16,
418            input = complex_in,
419            output = complex_out,
420            alignment = double
421        );\
422    "#,
423    name = "demo",
424    requires = ["create_complex_type", complex_in, complex_out],
425);
426
427```
428*/
429#[proc_macro]
430pub fn extension_sql(input: TokenStream) -> TokenStream {
431    fn wrapped(input: TokenStream) -> Result<TokenStream, syn::Error> {
432        let ext_sql: CodeEnrichment<ExtensionSql> = syn::parse(input)?;
433        Ok(ext_sql.to_token_stream().into())
434    }
435
436    wrapped(input).unwrap_or_else(|e: syn::Error| e.into_compile_error().into())
437}
438
439/**
440Declare SQL (from a file) to be included in generated extension script.
441
442Accepts the same options as [`macro@extension_sql`]. `name` is automatically set to the file name (not the full path).
443
444You can declare some SQL without any positioning information, meaning it can end up anywhere in the generated SQL:
445
446```rust,ignore
447use pgrx_macros::extension_sql_file;
448extension_sql_file!(
449    "../static/demo.sql",
450);
451```
452
453To override the default name:
454
455```rust,ignore
456use pgrx_macros::extension_sql_file;
457
458extension_sql_file!(
459    "../static/demo.sql",
460    name = "singular",
461);
462```
463
464For all other options, and examples of them, see [`macro@extension_sql`].
465*/
466#[proc_macro]
467pub fn extension_sql_file(input: TokenStream) -> TokenStream {
468    fn wrapped(input: TokenStream) -> Result<TokenStream, syn::Error> {
469        let ext_sql: CodeEnrichment<ExtensionSqlFile> = syn::parse(input)?;
470        Ok(ext_sql.to_token_stream().into())
471    }
472
473    wrapped(input).unwrap_or_else(|e: syn::Error| e.into_compile_error().into())
474}
475
476/// Associated macro for `#[pg_extern]` or `#[macro@pg_operator]`.  Used to set the `SEARCH_PATH` option
477/// on the `CREATE FUNCTION` statement.
478#[proc_macro_attribute]
479pub fn search_path(_attr: TokenStream, item: TokenStream) -> TokenStream {
480    item
481}
482
483/**
484Declare a function as `#[pg_extern]` to indicate that it can be used by Postgres as a UDF.
485
486Optionally accepts the following attributes:
487
488* `immutable`: Corresponds to [`IMMUTABLE`](https://www.postgresql.org/docs/current/sql-createfunction.html).
489* `strict`: Corresponds to [`STRICT`](https://www.postgresql.org/docs/current/sql-createfunction.html).
490  + In most cases, `#[pg_extern]` can detect when no `Option<T>`s are used, and automatically set this.
491* `stable`: Corresponds to [`STABLE`](https://www.postgresql.org/docs/current/sql-createfunction.html).
492* `volatile`: Corresponds to [`VOLATILE`](https://www.postgresql.org/docs/current/sql-createfunction.html).
493* `raw`: Corresponds to [`RAW`](https://www.postgresql.org/docs/current/sql-createfunction.html).
494* `security_definer`: Corresponds to [`SECURITY DEFINER`](https://www.postgresql.org/docs/current/sql-createfunction.html)
495* `security_invoker`: Corresponds to [`SECURITY INVOKER`](https://www.postgresql.org/docs/current/sql-createfunction.html)
496* `parallel_safe`: Corresponds to [`PARALLEL SAFE`](https://www.postgresql.org/docs/current/sql-createfunction.html).
497* `parallel_unsafe`: Corresponds to [`PARALLEL UNSAFE`](https://www.postgresql.org/docs/current/sql-createfunction.html).
498* `parallel_restricted`: Corresponds to [`PARALLEL RESTRICTED`](https://www.postgresql.org/docs/current/sql-createfunction.html).
499* `no_guard`: Do not use `#[pg_guard]` with the function.
500* `sql`: Same arguments as [`#[pgrx(sql = ..)]`](macro@pgrx).
501* `name`: Specifies target function name. Defaults to Rust function name.
502
503Functions can accept and return any type which `pgrx` supports. `pgrx` supports many PostgreSQL types by default.
504New types can be defined via [`macro@PostgresType`] or [`macro@PostgresEnum`].
505
506
507Without any arguments or returns:
508```rust,ignore
509use pgrx::*;
510#[pg_extern]
511fn foo() { todo!() }
512```
513
514# Arguments
515It's possible to pass even complex arguments:
516
517```rust,ignore
518use pgrx::*;
519#[pg_extern]
520fn boop(
521    a: i32,
522    b: Option<i32>,
523    c: Vec<i32>,
524    d: Option<Vec<Option<i32>>>
525) { todo!() }
526```
527
528It's possible to set argument defaults, set by PostgreSQL when the function is invoked:
529
530```rust,ignore
531use pgrx::*;
532#[pg_extern]
533fn boop(a: default!(i32, 11111)) { todo!() }
534#[pg_extern]
535fn doop(
536    a: default!(Vec<Option<&str>>, "ARRAY[]::text[]"),
537    b: default!(String, "'note the inner quotes!'")
538) { todo!() }
539```
540
541The `default!()` macro may only be used in argument position.
542
543It accepts 2 arguments:
544
545* A type
546* A `bool`, numeric, or SQL string to represent the default. `"NULL"` is a possible value, as is `"'string'"`
547
548**If the default SQL entity created by the extension:** ensure it is added to `requires` as a dependency:
549
550```rust,ignore
551use pgrx::*;
552#[pg_extern]
553fn default_value() -> i32 { todo!() }
554
555#[pg_extern(
556    requires = [ default_value, ],
557)]
558fn do_it(
559    a: default!(i32, "default_value()"),
560) { todo!() }
561```
562
563# Returns
564
565It's possible to return even complex values, as well:
566
567```rust,ignore
568use pgrx::*;
569#[pg_extern]
570fn boop() -> i32 { todo!() }
571#[pg_extern]
572fn doop() -> Option<i32> { todo!() }
573#[pg_extern]
574fn swoop() -> Option<Vec<Option<i32>>> { todo!() }
575#[pg_extern]
576fn floop() -> (i32, i32) { todo!() }
577```
578
579Like in PostgreSQL, it's possible to return tables using iterators and the `name!()` macro:
580
581```rust,ignore
582use pgrx::*;
583#[pg_extern]
584fn floop<'a>() -> TableIterator<'a, (name!(a, i32), name!(b, i32))> {
585    TableIterator::new(None.into_iter())
586}
587
588#[pg_extern]
589fn singular_floop() -> (name!(a, i32), name!(b, i32)) {
590    todo!()
591}
592```
593
594The `name!()` macro may only be used in return position inside the `T` of a `TableIterator<'a, T>`.
595
596It accepts 2 arguments:
597
598* A name, such as `example`
599* A type
600
601# Special Cases
602
603`pg_sys::Oid` is a special cased type alias, in order to use it as an argument or return it must be
604passed with it's full module path (`pg_sys::Oid`) in order to be resolved.
605
606```rust,ignore
607use pgrx::*;
608
609#[pg_extern]
610fn example_arg(animals: pg_sys::Oid) {
611    todo!()
612}
613
614#[pg_extern]
615fn example_return() -> pg_sys::Oid {
616    todo!()
617}
618```
619
620*/
621#[proc_macro_attribute]
622#[track_caller]
623pub fn pg_extern(attr: TokenStream, item: TokenStream) -> TokenStream {
624    fn wrapped(attr: TokenStream, item: TokenStream) -> Result<TokenStream, syn::Error> {
625        let pg_extern_item = PgExtern::new(attr.into(), item.into())?;
626        Ok(pg_extern_item.to_token_stream().into())
627    }
628
629    wrapped(attr, item).unwrap_or_else(|e: syn::Error| e.into_compile_error().into())
630}
631
632/**
633Generate necessary bindings for using the enum with PostgreSQL.
634
635```rust,ignore
636# use pgrx_pg_sys as pg_sys;
637use pgrx::*;
638use serde::{Deserialize, Serialize};
639#[derive(Debug, Serialize, Deserialize, PostgresEnum)]
640enum DogNames {
641    Nami,
642    Brandy,
643}
644```
645
646*/
647#[proc_macro_derive(PostgresEnum, attributes(requires, pgrx))]
648pub fn postgres_enum(input: TokenStream) -> TokenStream {
649    let ast = parse_macro_input!(input as syn::DeriveInput);
650
651    impl_postgres_enum(ast).unwrap_or_else(|e| e.into_compile_error()).into()
652}
653
654fn impl_postgres_enum(ast: DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
655    let mut stream = proc_macro2::TokenStream::new();
656    let sql_graph_entity_ast = ast.clone();
657    let generics = &ast.generics.clone();
658    let enum_ident = &ast.ident;
659    let enum_name = enum_ident.to_string();
660
661    // validate that we're only operating on an enum
662    let Data::Enum(enum_data) = ast.data else {
663        return Err(syn::Error::new(
664            ast.span(),
665            "#[derive(PostgresEnum)] can only be applied to enums",
666        ));
667    };
668
669    let mut from_datum = proc_macro2::TokenStream::new();
670    let mut into_datum = proc_macro2::TokenStream::new();
671
672    for d in enum_data.variants.clone() {
673        let label_ident = &d.ident;
674        let label_string = label_ident.to_string();
675
676        from_datum.extend(quote! { #label_string => Some(#enum_ident::#label_ident), });
677        into_datum.extend(quote! { #enum_ident::#label_ident => Some(::pgrx::enum_helper::lookup_enum_by_label(#enum_name, #label_string)), });
678    }
679
680    // We need another variant of the params for the ArgAbi impl
681    let fcx_lt = syn::Lifetime::new("'fcx", proc_macro2::Span::mixed_site());
682    let mut generics_with_fcx = generics.clone();
683    // so that we can bound on Self: 'fcx
684    generics_with_fcx.make_where_clause().predicates.push(syn::WherePredicate::Type(
685        syn::PredicateType {
686            lifetimes: None,
687            bounded_ty: syn::parse_quote! { Self },
688            colon_token: syn::Token![:](proc_macro2::Span::mixed_site()),
689            bounds: syn::parse_quote! { #fcx_lt },
690        },
691    ));
692    let (impl_gens, ty_gens, where_clause) = generics_with_fcx.split_for_impl();
693    let mut impl_gens: syn::Generics = syn::parse_quote! { #impl_gens };
694    impl_gens
695        .params
696        .insert(0, syn::GenericParam::Lifetime(syn::LifetimeParam::new(fcx_lt.clone())));
697
698    stream.extend(quote! {
699        impl ::pgrx::datum::FromDatum for #enum_ident {
700            #[inline]
701            unsafe fn from_polymorphic_datum(datum: ::pgrx::pg_sys::Datum, is_null: bool, typeoid: ::pgrx::pg_sys::Oid) -> Option<#enum_ident> {
702                if is_null {
703                    None
704                } else {
705                    // GREPME: non-primitive cast u64 as Oid
706                    let (name, _, _) = ::pgrx::enum_helper::lookup_enum_by_oid(unsafe { ::pgrx::pg_sys::Oid::from_datum(datum, is_null)? } );
707                    match name.as_str() {
708                        #from_datum
709                        _ => panic!("invalid enum value: {name}")
710                    }
711                }
712            }
713        }
714
715        unsafe impl #impl_gens ::pgrx::callconv::ArgAbi<#fcx_lt> for #enum_ident #ty_gens #where_clause {
716            unsafe fn unbox_arg_unchecked(arg: ::pgrx::callconv::Arg<'_, #fcx_lt>) -> Self {
717                let index = arg.index();
718                unsafe { arg.unbox_arg_using_from_datum().unwrap_or_else(|| panic!("argument {index} must not be null")) }
719            }
720
721        }
722
723        unsafe impl #generics ::pgrx::datum::UnboxDatum for #enum_ident #generics {
724            type As<'dat> = #enum_ident #generics where Self: 'dat;
725            #[inline]
726            unsafe fn unbox<'dat>(d: ::pgrx::datum::Datum<'dat>) -> Self::As<'dat> where Self: 'dat {
727                Self::from_datum(::core::mem::transmute(d), false).unwrap()
728            }
729        }
730
731        impl ::pgrx::datum::IntoDatum for #enum_ident {
732            #[inline]
733            fn into_datum(self) -> Option<::pgrx::pg_sys::Datum> {
734                match self {
735                    #into_datum
736                }
737            }
738
739            fn type_oid() -> ::pgrx::pg_sys::Oid {
740                ::pgrx::wrappers::regtypein(#enum_name)
741            }
742
743        }
744
745        unsafe impl ::pgrx::callconv::BoxRet for #enum_ident {
746            unsafe fn box_into<'fcx>(self, fcinfo: &mut ::pgrx::callconv::FcInfo<'fcx>) -> ::pgrx::datum::Datum<'fcx> {
747                match ::pgrx::datum::IntoDatum::into_datum(self) {
748                    None => fcinfo.return_null(),
749                    Some(datum) => unsafe { fcinfo.return_raw_datum(datum) },
750                }
751            }
752        }
753    });
754
755    let sql_graph_entity_item = PostgresEnum::from_derive_input(sql_graph_entity_ast)?;
756    sql_graph_entity_item.to_tokens(&mut stream);
757
758    Ok(stream)
759}
760
761/**
762Generate necessary bindings for using the type with PostgreSQL.
763
764```rust,ignore
765# use pgrx_pg_sys as pg_sys;
766use pgrx::*;
767use serde::{Deserialize, Serialize};
768#[derive(Debug, Serialize, Deserialize, PostgresType)]
769struct Dog {
770    treats_received: i64,
771    pets_gotten: i64,
772}
773
774#[derive(Debug, Serialize, Deserialize, PostgresType)]
775enum Animal {
776    Dog(Dog),
777}
778```
779
780Optionally accepts the following attributes:
781
782* `inoutfuncs(some_in_fn, some_out_fn)`: Define custom in/out functions for the type.
783* `pgvarlena_inoutfuncs(some_in_fn, some_out_fn)`: Define custom in/out functions for the `PgVarlena` of this type.
784* `sql`: Same arguments as [`#[pgrx(sql = ..)]`](macro@pgrx).
785*/
786#[proc_macro_derive(
787    PostgresType,
788    attributes(
789        inoutfuncs,
790        pgvarlena_inoutfuncs,
791        bikeshed_postgres_type_manually_impl_from_into_datum,
792        requires,
793        pgrx
794    )
795)]
796pub fn postgres_type(input: TokenStream) -> TokenStream {
797    let ast = parse_macro_input!(input as syn::DeriveInput);
798
799    impl_postgres_type(ast).unwrap_or_else(|e| e.into_compile_error()).into()
800}
801
802fn impl_postgres_type(ast: DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
803    let name = &ast.ident;
804    let generics = &ast.generics.clone();
805    let has_lifetimes = generics.lifetimes().next();
806    let funcname_in = Ident::new(&format!("{name}_in").to_lowercase(), name.span());
807    let funcname_out = Ident::new(&format!("{name}_out").to_lowercase(), name.span());
808    let mut args = parse_postgres_type_args(&ast.attrs);
809    let mut stream = proc_macro2::TokenStream::new();
810
811    // validate that we're only operating on a struct
812    match ast.data {
813        Data::Struct(_) => { /* this is okay */ }
814        Data::Enum(_) => {
815            // this is okay and if there's an attempt to implement PostgresEnum,
816            // it will result in compile-time error of conflicting implementation
817            // of traits (IntoDatum, inout, etc.)
818        }
819        _ => {
820            return Err(syn::Error::new(
821                ast.span(),
822                "#[derive(PostgresType)] can only be applied to structs or enums",
823            ))
824        }
825    }
826
827    if args.is_empty() {
828        // assume the user wants us to implement the InOutFuncs
829        args.insert(PostgresTypeAttribute::Default);
830    }
831
832    let lifetime = match has_lifetimes {
833        Some(lifetime) => quote! {#lifetime},
834        None => quote! {'_},
835    };
836
837    // We need another variant of the params for the ArgAbi impl
838    let fcx_lt = syn::Lifetime::new("'fcx", proc_macro2::Span::mixed_site());
839    let mut generics_with_fcx = generics.clone();
840    // so that we can bound on Self: 'fcx
841    generics_with_fcx.make_where_clause().predicates.push(syn::WherePredicate::Type(
842        syn::PredicateType {
843            lifetimes: None,
844            bounded_ty: syn::parse_quote! { Self },
845            colon_token: syn::Token![:](proc_macro2::Span::mixed_site()),
846            bounds: syn::parse_quote! { #fcx_lt },
847        },
848    ));
849    let (impl_gens, ty_gens, where_clause) = generics_with_fcx.split_for_impl();
850    let mut impl_gens: syn::Generics = syn::parse_quote! { #impl_gens };
851    impl_gens
852        .params
853        .insert(0, syn::GenericParam::Lifetime(syn::LifetimeParam::new(fcx_lt.clone())));
854
855    // all #[derive(PostgresType)] need to implement that trait
856    // and also the FromDatum and IntoDatum
857    stream.extend(quote! {
858        impl #generics ::pgrx::datum::PostgresType for #name #generics { }
859    });
860
861    if !args.contains(&PostgresTypeAttribute::ManualFromIntoDatum) {
862        stream.extend(
863            quote! {
864                impl #generics ::pgrx::datum::IntoDatum for #name #generics {
865                    fn into_datum(self) -> Option<::pgrx::pg_sys::Datum> {
866                        #[allow(deprecated)]
867                        Some(unsafe { ::pgrx::datum::cbor_encode(&self) }.into())
868                    }
869
870                    fn type_oid() -> ::pgrx::pg_sys::Oid {
871                        ::pgrx::wrappers::rust_regtypein::<Self>()
872                    }
873                }
874
875                unsafe impl #generics ::pgrx::callconv::BoxRet for #name #generics {
876                    unsafe fn box_into<'fcx>(self, fcinfo: &mut ::pgrx::callconv::FcInfo<'fcx>) -> ::pgrx::datum::Datum<'fcx> {
877                        match ::pgrx::datum::IntoDatum::into_datum(self) {
878                            None => fcinfo.return_null(),
879                            Some(datum) => unsafe { fcinfo.return_raw_datum(datum) },
880                        }
881                    }
882                }
883
884                impl #generics ::pgrx::datum::FromDatum for #name #generics {
885                    unsafe fn from_polymorphic_datum(
886                        datum: ::pgrx::pg_sys::Datum,
887                        is_null: bool,
888                        _typoid: ::pgrx::pg_sys::Oid,
889                    ) -> Option<Self> {
890                        if is_null {
891                            None
892                        } else {
893                            #[allow(deprecated)]
894                            ::pgrx::datum::cbor_decode(datum.cast_mut_ptr())
895                        }
896                    }
897
898                    unsafe fn from_datum_in_memory_context(
899                        mut memory_context: ::pgrx::memcxt::PgMemoryContexts,
900                        datum: ::pgrx::pg_sys::Datum,
901                        is_null: bool,
902                        _typoid: ::pgrx::pg_sys::Oid,
903                    ) -> Option<Self> {
904                        if is_null {
905                            None
906                        } else {
907                            memory_context.switch_to(|_| {
908                                // this gets the varlena Datum copied into this memory context
909                                let varlena = ::pgrx::pg_sys::pg_detoast_datum_copy(datum.cast_mut_ptr());
910                                Self::from_datum(varlena.into(), is_null)
911                            })
912                        }
913                    }
914                }
915
916                unsafe impl #generics ::pgrx::datum::UnboxDatum for #name #generics {
917                    type As<'dat> = Self where Self: 'dat;
918                    unsafe fn unbox<'dat>(datum: ::pgrx::datum::Datum<'dat>) -> Self::As<'dat> where Self: 'dat {
919                        <Self as ::pgrx::datum::FromDatum>::from_datum(::core::mem::transmute(datum), false).unwrap()
920                    }
921                }
922
923                unsafe impl #impl_gens ::pgrx::callconv::ArgAbi<#fcx_lt> for #name #ty_gens #where_clause
924                {
925                        unsafe fn unbox_arg_unchecked(arg: ::pgrx::callconv::Arg<'_, #fcx_lt>) -> Self {
926                        let index = arg.index();
927                        unsafe { arg.unbox_arg_using_from_datum().unwrap_or_else(|| panic!("argument {index} must not be null")) }
928                    }
929                }
930            }
931        )
932    }
933
934    // and if we don't have custom inout/funcs, we use the JsonInOutFuncs trait
935    // which implements _in and _out #[pg_extern] functions that just return the type itself
936    if args.contains(&PostgresTypeAttribute::Default) {
937        stream.extend(quote! {
938            #[doc(hidden)]
939            #[::pgrx::pgrx_macros::pg_extern(immutable,parallel_safe)]
940            pub fn #funcname_in #generics(input: Option<&#lifetime ::core::ffi::CStr>) -> Option<#name #generics> {
941                use ::pgrx::inoutfuncs::json_from_slice;
942                input.map(|cstr| json_from_slice(cstr.to_bytes()).ok()).flatten()
943            }
944
945            #[doc(hidden)]
946            #[::pgrx::pgrx_macros::pg_extern (immutable,parallel_safe)]
947            pub fn #funcname_out #generics(input: #name #generics) -> ::pgrx::ffi::CString {
948                use ::pgrx::inoutfuncs::json_to_vec;
949                let mut bytes = json_to_vec(&input).unwrap();
950                bytes.push(0); // terminate
951                ::pgrx::ffi::CString::from_vec_with_nul(bytes).unwrap()
952            }
953        });
954    } else if args.contains(&PostgresTypeAttribute::InOutFuncs) {
955        // otherwise if it's InOutFuncs our _in/_out functions use an owned type instance
956        stream.extend(quote! {
957            #[doc(hidden)]
958            #[::pgrx::pgrx_macros::pg_extern(immutable,parallel_safe)]
959            pub fn #funcname_in #generics(input: Option<&::core::ffi::CStr>) -> Option<#name #generics> {
960                input.map_or_else(|| {
961                    for m in <#name as ::pgrx::inoutfuncs::InOutFuncs>::NULL_ERROR_MESSAGE {
962                        ::pgrx::pg_sys::error!("{m}");
963                    }
964                    None
965                }, |i| Some(<#name as ::pgrx::inoutfuncs::InOutFuncs>::input(i)))
966            }
967
968            #[doc(hidden)]
969            #[::pgrx::pgrx_macros::pg_extern(immutable,parallel_safe)]
970            pub fn #funcname_out #generics(input: #name #generics) -> ::pgrx::ffi::CString {
971                let mut buffer = ::pgrx::stringinfo::StringInfo::new();
972                ::pgrx::inoutfuncs::InOutFuncs::output(&input, &mut buffer);
973                // SAFETY: We just constructed this StringInfo ourselves
974                unsafe { buffer.leak_cstr().to_owned() }
975            }
976        });
977    } else if args.contains(&PostgresTypeAttribute::PgVarlenaInOutFuncs) {
978        // otherwise if it's PgVarlenaInOutFuncs our _in/_out functions use a PgVarlena
979        stream.extend(quote! {
980            #[doc(hidden)]
981            #[::pgrx::pgrx_macros::pg_extern(immutable,parallel_safe)]
982            pub fn #funcname_in #generics(input: Option<&::core::ffi::CStr>) -> Option<::pgrx::datum::PgVarlena<#name #generics>> {
983                input.map_or_else(|| {
984                    for m in <#name as ::pgrx::inoutfuncs::PgVarlenaInOutFuncs>::NULL_ERROR_MESSAGE {
985                        ::pgrx::pg_sys::error!("{m}");
986                    }
987                    None
988                }, |i| Some(<#name as ::pgrx::inoutfuncs::PgVarlenaInOutFuncs>::input(i)))
989            }
990
991            #[doc(hidden)]
992            #[::pgrx::pgrx_macros::pg_extern(immutable,parallel_safe)]
993            pub fn #funcname_out #generics(input: ::pgrx::datum::PgVarlena<#name #generics>) -> ::pgrx::ffi::CString {
994                let mut buffer = ::pgrx::stringinfo::StringInfo::new();
995                ::pgrx::inoutfuncs::PgVarlenaInOutFuncs::output(&*input, &mut buffer);
996                // SAFETY: We just constructed this StringInfo ourselves
997                unsafe { buffer.leak_cstr().to_owned() }
998            }
999        });
1000    }
1001
1002    let sql_graph_entity_item = sql_gen::PostgresTypeDerive::from_derive_input(ast)?;
1003    sql_graph_entity_item.to_tokens(&mut stream);
1004
1005    Ok(stream)
1006}
1007
1008/// Derives the `GucEnum` trait, so that normal Rust enums can be used as a GUC.
1009#[proc_macro_derive(PostgresGucEnum, attributes(hidden))]
1010pub fn postgres_guc_enum(input: TokenStream) -> TokenStream {
1011    let ast = parse_macro_input!(input as syn::DeriveInput);
1012
1013    impl_guc_enum(ast).unwrap_or_else(|e| e.into_compile_error()).into()
1014}
1015
1016fn impl_guc_enum(ast: DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
1017    let mut stream = proc_macro2::TokenStream::new();
1018
1019    // validate that we're only operating on an enum
1020    let enum_data = match ast.data {
1021        Data::Enum(e) => e,
1022        _ => {
1023            return Err(syn::Error::new(
1024                ast.span(),
1025                "#[derive(PostgresGucEnum)] can only be applied to enums",
1026            ))
1027        }
1028    };
1029    let enum_name = ast.ident;
1030    let enum_len = enum_data.variants.len();
1031
1032    let mut from_match_arms = proc_macro2::TokenStream::new();
1033    for (idx, e) in enum_data.variants.iter().enumerate() {
1034        let label = &e.ident;
1035        let idx = idx as i32;
1036        from_match_arms.extend(quote! { #idx => #enum_name::#label, })
1037    }
1038    from_match_arms.extend(quote! { _ => panic!("Unrecognized ordinal ")});
1039
1040    let mut ordinal_match_arms = proc_macro2::TokenStream::new();
1041    for (idx, e) in enum_data.variants.iter().enumerate() {
1042        let label = &e.ident;
1043        let idx = idx as i32;
1044        ordinal_match_arms.extend(quote! { #enum_name::#label => #idx, });
1045    }
1046
1047    let mut build_array_body = proc_macro2::TokenStream::new();
1048    for (idx, e) in enum_data.variants.iter().enumerate() {
1049        let label = e.ident.to_string();
1050        let mut hidden = false;
1051
1052        for att in e.attrs.iter() {
1053            let att = quote! {#att}.to_string();
1054            if att == "# [hidden]" {
1055                hidden = true;
1056                break;
1057            }
1058        }
1059
1060        build_array_body.extend(quote! {
1061            ::pgrx::pgbox::PgBox::<_, ::pgrx::pgbox::AllocatedByPostgres>::with(&mut slice[#idx], |v| {
1062                v.name = ::pgrx::memcxt::PgMemoryContexts::TopMemoryContext.pstrdup(#label);
1063                v.val = #idx as i32;
1064                v.hidden = #hidden;
1065            });
1066        });
1067    }
1068
1069    stream.extend(quote! {
1070        impl ::pgrx::guc::GucEnum<#enum_name> for #enum_name {
1071            fn from_ordinal(ordinal: i32) -> #enum_name {
1072                match ordinal {
1073                    #from_match_arms
1074                }
1075            }
1076
1077            fn to_ordinal(&self) -> i32 {
1078                match *self {
1079                    #ordinal_match_arms
1080                }
1081            }
1082
1083            unsafe fn config_matrix(&self) -> *const ::pgrx::pg_sys::config_enum_entry {
1084                let slice = ::pgrx::memcxt::PgMemoryContexts::TopMemoryContext.palloc0_slice::<::pgrx::pg_sys::config_enum_entry>(#enum_len + 1usize);
1085
1086                #build_array_body
1087
1088                slice.as_ptr()
1089            }
1090        }
1091    });
1092
1093    Ok(stream)
1094}
1095
1096#[derive(Debug, Hash, Ord, PartialOrd, Eq, PartialEq)]
1097enum PostgresTypeAttribute {
1098    InOutFuncs,
1099    PgVarlenaInOutFuncs,
1100    Default,
1101    ManualFromIntoDatum,
1102}
1103
1104fn parse_postgres_type_args(attributes: &[Attribute]) -> HashSet<PostgresTypeAttribute> {
1105    let mut categorized_attributes = HashSet::new();
1106
1107    for a in attributes {
1108        let path = &a.path();
1109        let path = quote! {#path}.to_string();
1110        match path.as_str() {
1111            "inoutfuncs" => {
1112                categorized_attributes.insert(PostgresTypeAttribute::InOutFuncs);
1113            }
1114            "pgvarlena_inoutfuncs" => {
1115                categorized_attributes.insert(PostgresTypeAttribute::PgVarlenaInOutFuncs);
1116            }
1117            "bikeshed_postgres_type_manually_impl_from_into_datum" => {
1118                categorized_attributes.insert(PostgresTypeAttribute::ManualFromIntoDatum);
1119            }
1120            _ => {
1121                // we can just ignore attributes we don't understand
1122            }
1123        };
1124    }
1125
1126    categorized_attributes
1127}
1128
1129/**
1130Generate necessary code using the type in operators like `==` and `!=`.
1131
1132```rust,ignore
1133# use pgrx_pg_sys as pg_sys;
1134use pgrx::*;
1135use serde::{Deserialize, Serialize};
1136#[derive(Debug, Serialize, Deserialize, PostgresEnum, PartialEq, Eq, PostgresEq)]
1137enum DogNames {
1138    Nami,
1139    Brandy,
1140}
1141```
1142Optionally accepts the following attributes:
1143
1144* `sql`: Same arguments as [`#[pgrx(sql = ..)]`](macro@pgrx).
1145
1146# No bounds?
1147Unlike some derives, this does not implement a "real" Rust trait, thus
1148PostgresEq cannot be used in trait bounds, nor can it be manually implemented.
1149*/
1150#[proc_macro_derive(PostgresEq, attributes(pgrx))]
1151pub fn derive_postgres_eq(input: TokenStream) -> TokenStream {
1152    let ast = parse_macro_input!(input as syn::DeriveInput);
1153    deriving_postgres_eq(ast).unwrap_or_else(syn::Error::into_compile_error).into()
1154}
1155
1156/**
1157Generate necessary code using the type in operators like `>`, `<`, `<=`, and `>=`.
1158
1159```rust,ignore
1160# use pgrx_pg_sys as pg_sys;
1161use pgrx::*;
1162use serde::{Deserialize, Serialize};
1163#[derive(
1164    Debug, Serialize, Deserialize, PartialEq, Eq,
1165     PartialOrd, Ord, PostgresEnum, PostgresOrd
1166)]
1167enum DogNames {
1168    Nami,
1169    Brandy,
1170}
1171```
1172Optionally accepts the following attributes:
1173
1174* `sql`: Same arguments as [`#[pgrx(sql = ..)]`](macro@pgrx).
1175
1176# No bounds?
1177Unlike some derives, this does not implement a "real" Rust trait, thus
1178PostgresOrd cannot be used in trait bounds, nor can it be manually implemented.
1179*/
1180#[proc_macro_derive(PostgresOrd, attributes(pgrx))]
1181pub fn derive_postgres_ord(input: TokenStream) -> TokenStream {
1182    let ast = parse_macro_input!(input as syn::DeriveInput);
1183    deriving_postgres_ord(ast).unwrap_or_else(syn::Error::into_compile_error).into()
1184}
1185
1186/**
1187Generate necessary code for stable hashing the type so it can be used with `USING hash` indexes.
1188
1189```rust,ignore
1190# use pgrx_pg_sys as pg_sys;
1191use pgrx::*;
1192use serde::{Deserialize, Serialize};
1193#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Hash, PostgresEnum, PostgresHash)]
1194enum DogNames {
1195    Nami,
1196    Brandy,
1197}
1198```
1199Optionally accepts the following attributes:
1200
1201* `sql`: Same arguments as [`#[pgrx(sql = ..)]`](macro@pgrx).
1202
1203# No bounds?
1204Unlike some derives, this does not implement a "real" Rust trait, thus
1205PostgresHash cannot be used in trait bounds, nor can it be manually implemented.
1206*/
1207#[proc_macro_derive(PostgresHash, attributes(pgrx))]
1208pub fn derive_postgres_hash(input: TokenStream) -> TokenStream {
1209    let ast = parse_macro_input!(input as syn::DeriveInput);
1210    deriving_postgres_hash(ast).unwrap_or_else(syn::Error::into_compile_error).into()
1211}
1212
1213/**
1214Declare a `pgrx::Aggregate` implementation on a type as able to used by Postgres as an aggregate.
1215
1216Functions inside the `impl` may use the [`#[pgrx]`](macro@pgrx) attribute.
1217*/
1218#[proc_macro_attribute]
1219pub fn pg_aggregate(_attr: TokenStream, item: TokenStream) -> TokenStream {
1220    // We don't care about `_attr` as we can find it in the `ItemMod`.
1221    fn wrapped(item_impl: ItemImpl) -> Result<TokenStream, syn::Error> {
1222        let sql_graph_entity_item = PgAggregate::new(item_impl)?;
1223
1224        Ok(sql_graph_entity_item.to_token_stream().into())
1225    }
1226
1227    let parsed_base = parse_macro_input!(item as syn::ItemImpl);
1228    wrapped(parsed_base).unwrap_or_else(|e| e.into_compile_error().into())
1229}
1230
1231/**
1232A helper attribute for various contexts.
1233
1234## Usage with [`#[pg_aggregate]`](macro@pg_aggregate).
1235
1236It can be decorated on functions inside a [`#[pg_aggregate]`](macro@pg_aggregate) implementation.
1237In this position, it takes the same args as [`#[pg_extern]`](macro@pg_extern), and those args have the same effect.
1238
1239## Usage for configuring SQL generation
1240
1241This attribute can be used to control the behavior of the SQL generator on a decorated item,
1242e.g. `#[pgrx(sql = false)]`
1243
1244Currently `sql` can be provided one of the following:
1245
1246* Disable SQL generation with `#[pgrx(sql = false)]`
1247* Call custom SQL generator function with `#[pgrx(sql = path::to_function)]`
1248* Render a specific fragment of SQL with a string `#[pgrx(sql = "CREATE FUNCTION ...")]`
1249
1250*/
1251#[proc_macro_attribute]
1252pub fn pgrx(_attr: TokenStream, item: TokenStream) -> TokenStream {
1253    item
1254}
1255
1256/**
1257Create a [PostgreSQL trigger function](https://www.postgresql.org/docs/current/plpgsql-trigger.html)
1258
1259Review the `pgrx::trigger_support::PgTrigger` documentation for use.
1260
1261 */
1262#[proc_macro_attribute]
1263pub fn pg_trigger(attrs: TokenStream, input: TokenStream) -> TokenStream {
1264    fn wrapped(attrs: TokenStream, input: TokenStream) -> Result<TokenStream, syn::Error> {
1265        use pgrx_sql_entity_graph::{PgTrigger, PgTriggerAttribute};
1266        use syn::parse::Parser;
1267        use syn::punctuated::Punctuated;
1268        use syn::Token;
1269
1270        let attributes =
1271            Punctuated::<PgTriggerAttribute, Token![,]>::parse_terminated.parse(attrs)?;
1272        let item_fn: syn::ItemFn = syn::parse(input)?;
1273        let trigger_item = PgTrigger::new(item_fn, attributes)?;
1274        let trigger_tokens = trigger_item.to_token_stream();
1275
1276        Ok(trigger_tokens.into())
1277    }
1278
1279    wrapped(attrs, input).unwrap_or_else(|e| e.into_compile_error().into())
1280}