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* `pgrx(alignment = "<align>")`: Derive Postgres alignment from Rust type. One of `"on"`, or `"off"`.
785* `sql`: Same arguments as [`#[pgrx(sql = ..)]`](macro@pgrx).
786*/
787#[proc_macro_derive(
788    PostgresType,
789    attributes(
790        inoutfuncs,
791        pgvarlena_inoutfuncs,
792        bikeshed_postgres_type_manually_impl_from_into_datum,
793        requires,
794        pgrx
795    )
796)]
797pub fn postgres_type(input: TokenStream) -> TokenStream {
798    let ast = parse_macro_input!(input as syn::DeriveInput);
799
800    impl_postgres_type(ast).unwrap_or_else(|e| e.into_compile_error()).into()
801}
802
803fn impl_postgres_type(ast: DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
804    let name = &ast.ident;
805    let generics = &ast.generics.clone();
806    let has_lifetimes = generics.lifetimes().next();
807    let funcname_in = Ident::new(&format!("{name}_in").to_lowercase(), name.span());
808    let funcname_out = Ident::new(&format!("{name}_out").to_lowercase(), name.span());
809    let mut args = parse_postgres_type_args(&ast.attrs);
810    let mut stream = proc_macro2::TokenStream::new();
811
812    // validate that we're only operating on a struct
813    match ast.data {
814        Data::Struct(_) => { /* this is okay */ }
815        Data::Enum(_) => {
816            // this is okay and if there's an attempt to implement PostgresEnum,
817            // it will result in compile-time error of conflicting implementation
818            // of traits (IntoDatum, inout, etc.)
819        }
820        _ => {
821            return Err(syn::Error::new(
822                ast.span(),
823                "#[derive(PostgresType)] can only be applied to structs or enums",
824            ))
825        }
826    }
827
828    if args.is_empty() {
829        // assume the user wants us to implement the InOutFuncs
830        args.insert(PostgresTypeAttribute::Default);
831    }
832
833    let lifetime = match has_lifetimes {
834        Some(lifetime) => quote! {#lifetime},
835        None => quote! {'_},
836    };
837
838    // We need another variant of the params for the ArgAbi impl
839    let fcx_lt = syn::Lifetime::new("'fcx", proc_macro2::Span::mixed_site());
840    let mut generics_with_fcx = generics.clone();
841    // so that we can bound on Self: 'fcx
842    generics_with_fcx.make_where_clause().predicates.push(syn::WherePredicate::Type(
843        syn::PredicateType {
844            lifetimes: None,
845            bounded_ty: syn::parse_quote! { Self },
846            colon_token: syn::Token![:](proc_macro2::Span::mixed_site()),
847            bounds: syn::parse_quote! { #fcx_lt },
848        },
849    ));
850    let (impl_gens, ty_gens, where_clause) = generics_with_fcx.split_for_impl();
851    let mut impl_gens: syn::Generics = syn::parse_quote! { #impl_gens };
852    impl_gens
853        .params
854        .insert(0, syn::GenericParam::Lifetime(syn::LifetimeParam::new(fcx_lt.clone())));
855
856    // all #[derive(PostgresType)] need to implement that trait
857    // and also the FromDatum and IntoDatum
858    stream.extend(quote! {
859        impl #generics ::pgrx::datum::PostgresType for #name #generics { }
860    });
861
862    if !args.contains(&PostgresTypeAttribute::ManualFromIntoDatum) {
863        stream.extend(
864            quote! {
865                impl #generics ::pgrx::datum::IntoDatum for #name #generics {
866                    fn into_datum(self) -> Option<::pgrx::pg_sys::Datum> {
867                        #[allow(deprecated)]
868                        Some(unsafe { ::pgrx::datum::cbor_encode(&self) }.into())
869                    }
870
871                    fn type_oid() -> ::pgrx::pg_sys::Oid {
872                        ::pgrx::wrappers::rust_regtypein::<Self>()
873                    }
874                }
875
876                unsafe impl #generics ::pgrx::callconv::BoxRet for #name #generics {
877                    unsafe fn box_into<'fcx>(self, fcinfo: &mut ::pgrx::callconv::FcInfo<'fcx>) -> ::pgrx::datum::Datum<'fcx> {
878                        match ::pgrx::datum::IntoDatum::into_datum(self) {
879                            None => fcinfo.return_null(),
880                            Some(datum) => unsafe { fcinfo.return_raw_datum(datum) },
881                        }
882                    }
883                }
884
885                impl #generics ::pgrx::datum::FromDatum for #name #generics {
886                    unsafe fn from_polymorphic_datum(
887                        datum: ::pgrx::pg_sys::Datum,
888                        is_null: bool,
889                        _typoid: ::pgrx::pg_sys::Oid,
890                    ) -> Option<Self> {
891                        if is_null {
892                            None
893                        } else {
894                            #[allow(deprecated)]
895                            ::pgrx::datum::cbor_decode(datum.cast_mut_ptr())
896                        }
897                    }
898
899                    unsafe fn from_datum_in_memory_context(
900                        mut memory_context: ::pgrx::memcxt::PgMemoryContexts,
901                        datum: ::pgrx::pg_sys::Datum,
902                        is_null: bool,
903                        _typoid: ::pgrx::pg_sys::Oid,
904                    ) -> Option<Self> {
905                        if is_null {
906                            None
907                        } else {
908                            memory_context.switch_to(|_| {
909                                // this gets the varlena Datum copied into this memory context
910                                let varlena = ::pgrx::pg_sys::pg_detoast_datum_copy(datum.cast_mut_ptr());
911                                Self::from_datum(varlena.into(), is_null)
912                            })
913                        }
914                    }
915                }
916
917                unsafe impl #generics ::pgrx::datum::UnboxDatum for #name #generics {
918                    type As<'dat> = Self where Self: 'dat;
919                    unsafe fn unbox<'dat>(datum: ::pgrx::datum::Datum<'dat>) -> Self::As<'dat> where Self: 'dat {
920                        <Self as ::pgrx::datum::FromDatum>::from_datum(::core::mem::transmute(datum), false).unwrap()
921                    }
922                }
923
924                unsafe impl #impl_gens ::pgrx::callconv::ArgAbi<#fcx_lt> for #name #ty_gens #where_clause
925                {
926                        unsafe fn unbox_arg_unchecked(arg: ::pgrx::callconv::Arg<'_, #fcx_lt>) -> Self {
927                        let index = arg.index();
928                        unsafe { arg.unbox_arg_using_from_datum().unwrap_or_else(|| panic!("argument {index} must not be null")) }
929                    }
930                }
931            }
932        )
933    }
934
935    // and if we don't have custom inout/funcs, we use the JsonInOutFuncs trait
936    // which implements _in and _out #[pg_extern] functions that just return the type itself
937    if args.contains(&PostgresTypeAttribute::Default) {
938        stream.extend(quote! {
939            #[doc(hidden)]
940            #[::pgrx::pgrx_macros::pg_extern(immutable,parallel_safe)]
941            pub fn #funcname_in #generics(input: Option<&#lifetime ::core::ffi::CStr>) -> Option<#name #generics> {
942                use ::pgrx::inoutfuncs::json_from_slice;
943                input.map(|cstr| json_from_slice(cstr.to_bytes()).ok()).flatten()
944            }
945
946            #[doc(hidden)]
947            #[::pgrx::pgrx_macros::pg_extern (immutable,parallel_safe)]
948            pub fn #funcname_out #generics(input: #name #generics) -> ::pgrx::ffi::CString {
949                use ::pgrx::inoutfuncs::json_to_vec;
950                let mut bytes = json_to_vec(&input).unwrap();
951                bytes.push(0); // terminate
952                ::pgrx::ffi::CString::from_vec_with_nul(bytes).unwrap()
953            }
954        });
955    } else if args.contains(&PostgresTypeAttribute::InOutFuncs) {
956        // otherwise if it's InOutFuncs our _in/_out functions use an owned type instance
957        stream.extend(quote! {
958            #[doc(hidden)]
959            #[::pgrx::pgrx_macros::pg_extern(immutable,parallel_safe)]
960            pub fn #funcname_in #generics(input: Option<&::core::ffi::CStr>) -> Option<#name #generics> {
961                input.map_or_else(|| {
962                    for m in <#name as ::pgrx::inoutfuncs::InOutFuncs>::NULL_ERROR_MESSAGE {
963                        ::pgrx::pg_sys::error!("{m}");
964                    }
965                    None
966                }, |i| Some(<#name as ::pgrx::inoutfuncs::InOutFuncs>::input(i)))
967            }
968
969            #[doc(hidden)]
970            #[::pgrx::pgrx_macros::pg_extern(immutable,parallel_safe)]
971            pub fn #funcname_out #generics(input: #name #generics) -> ::pgrx::ffi::CString {
972                let mut buffer = ::pgrx::stringinfo::StringInfo::new();
973                ::pgrx::inoutfuncs::InOutFuncs::output(&input, &mut buffer);
974                // SAFETY: We just constructed this StringInfo ourselves
975                unsafe { buffer.leak_cstr().to_owned() }
976            }
977        });
978    } else if args.contains(&PostgresTypeAttribute::PgVarlenaInOutFuncs) {
979        // otherwise if it's PgVarlenaInOutFuncs our _in/_out functions use a PgVarlena
980        stream.extend(quote! {
981            #[doc(hidden)]
982            #[::pgrx::pgrx_macros::pg_extern(immutable,parallel_safe)]
983            pub fn #funcname_in #generics(input: Option<&::core::ffi::CStr>) -> Option<::pgrx::datum::PgVarlena<#name #generics>> {
984                input.map_or_else(|| {
985                    for m in <#name as ::pgrx::inoutfuncs::PgVarlenaInOutFuncs>::NULL_ERROR_MESSAGE {
986                        ::pgrx::pg_sys::error!("{m}");
987                    }
988                    None
989                }, |i| Some(<#name as ::pgrx::inoutfuncs::PgVarlenaInOutFuncs>::input(i)))
990            }
991
992            #[doc(hidden)]
993            #[::pgrx::pgrx_macros::pg_extern(immutable,parallel_safe)]
994            pub fn #funcname_out #generics(input: ::pgrx::datum::PgVarlena<#name #generics>) -> ::pgrx::ffi::CString {
995                let mut buffer = ::pgrx::stringinfo::StringInfo::new();
996                ::pgrx::inoutfuncs::PgVarlenaInOutFuncs::output(&*input, &mut buffer);
997                // SAFETY: We just constructed this StringInfo ourselves
998                unsafe { buffer.leak_cstr().to_owned() }
999            }
1000        });
1001    }
1002
1003    let sql_graph_entity_item = sql_gen::PostgresTypeDerive::from_derive_input(ast)?;
1004    sql_graph_entity_item.to_tokens(&mut stream);
1005
1006    Ok(stream)
1007}
1008
1009/// Derives the `GucEnum` trait, so that normal Rust enums can be used as a GUC.
1010#[proc_macro_derive(PostgresGucEnum, attributes(hidden))]
1011pub fn postgres_guc_enum(input: TokenStream) -> TokenStream {
1012    let ast = parse_macro_input!(input as syn::DeriveInput);
1013
1014    impl_guc_enum(ast).unwrap_or_else(|e| e.into_compile_error()).into()
1015}
1016
1017fn impl_guc_enum(ast: DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
1018    let mut stream = proc_macro2::TokenStream::new();
1019
1020    // validate that we're only operating on an enum
1021    let enum_data = match ast.data {
1022        Data::Enum(e) => e,
1023        _ => {
1024            return Err(syn::Error::new(
1025                ast.span(),
1026                "#[derive(PostgresGucEnum)] can only be applied to enums",
1027            ))
1028        }
1029    };
1030    let enum_name = ast.ident;
1031    let enum_len = enum_data.variants.len();
1032
1033    let mut from_match_arms = proc_macro2::TokenStream::new();
1034    for (idx, e) in enum_data.variants.iter().enumerate() {
1035        let label = &e.ident;
1036        let idx = idx as i32;
1037        from_match_arms.extend(quote! { #idx => #enum_name::#label, })
1038    }
1039    from_match_arms.extend(quote! { _ => panic!("Unrecognized ordinal ")});
1040
1041    let mut ordinal_match_arms = proc_macro2::TokenStream::new();
1042    for (idx, e) in enum_data.variants.iter().enumerate() {
1043        let label = &e.ident;
1044        let idx = idx as i32;
1045        ordinal_match_arms.extend(quote! { #enum_name::#label => #idx, });
1046    }
1047
1048    let mut build_array_body = proc_macro2::TokenStream::new();
1049    for (idx, e) in enum_data.variants.iter().enumerate() {
1050        let label = e.ident.to_string();
1051        let mut hidden = false;
1052
1053        for att in e.attrs.iter() {
1054            let att = quote! {#att}.to_string();
1055            if att == "# [hidden]" {
1056                hidden = true;
1057                break;
1058            }
1059        }
1060
1061        build_array_body.extend(quote! {
1062            ::pgrx::pgbox::PgBox::<_, ::pgrx::pgbox::AllocatedByPostgres>::with(&mut slice[#idx], |v| {
1063                v.name = ::pgrx::memcxt::PgMemoryContexts::TopMemoryContext.pstrdup(#label);
1064                v.val = #idx as i32;
1065                v.hidden = #hidden;
1066            });
1067        });
1068    }
1069
1070    stream.extend(quote! {
1071        impl ::pgrx::guc::GucEnum<#enum_name> for #enum_name {
1072            fn from_ordinal(ordinal: i32) -> #enum_name {
1073                match ordinal {
1074                    #from_match_arms
1075                }
1076            }
1077
1078            fn to_ordinal(&self) -> i32 {
1079                match *self {
1080                    #ordinal_match_arms
1081                }
1082            }
1083
1084            unsafe fn config_matrix(&self) -> *const ::pgrx::pg_sys::config_enum_entry {
1085                let slice = ::pgrx::memcxt::PgMemoryContexts::TopMemoryContext.palloc0_slice::<::pgrx::pg_sys::config_enum_entry>(#enum_len + 1usize);
1086
1087                #build_array_body
1088
1089                slice.as_ptr()
1090            }
1091        }
1092    });
1093
1094    Ok(stream)
1095}
1096
1097#[derive(Debug, Hash, Ord, PartialOrd, Eq, PartialEq)]
1098enum PostgresTypeAttribute {
1099    InOutFuncs,
1100    PgVarlenaInOutFuncs,
1101    Default,
1102    ManualFromIntoDatum,
1103}
1104
1105fn parse_postgres_type_args(attributes: &[Attribute]) -> HashSet<PostgresTypeAttribute> {
1106    let mut categorized_attributes = HashSet::new();
1107
1108    for a in attributes {
1109        let path = &a.path();
1110        let path = quote! {#path}.to_string();
1111        match path.as_str() {
1112            "inoutfuncs" => {
1113                categorized_attributes.insert(PostgresTypeAttribute::InOutFuncs);
1114            }
1115            "pgvarlena_inoutfuncs" => {
1116                categorized_attributes.insert(PostgresTypeAttribute::PgVarlenaInOutFuncs);
1117            }
1118            "bikeshed_postgres_type_manually_impl_from_into_datum" => {
1119                categorized_attributes.insert(PostgresTypeAttribute::ManualFromIntoDatum);
1120            }
1121            _ => {
1122                // we can just ignore attributes we don't understand
1123            }
1124        };
1125    }
1126
1127    categorized_attributes
1128}
1129
1130/**
1131Generate necessary code using the type in operators like `==` and `!=`.
1132
1133```rust,ignore
1134# use pgrx_pg_sys as pg_sys;
1135use pgrx::*;
1136use serde::{Deserialize, Serialize};
1137#[derive(Debug, Serialize, Deserialize, PostgresEnum, PartialEq, Eq, PostgresEq)]
1138enum DogNames {
1139    Nami,
1140    Brandy,
1141}
1142```
1143Optionally accepts the following attributes:
1144
1145* `sql`: Same arguments as [`#[pgrx(sql = ..)]`](macro@pgrx).
1146
1147# No bounds?
1148Unlike some derives, this does not implement a "real" Rust trait, thus
1149PostgresEq cannot be used in trait bounds, nor can it be manually implemented.
1150*/
1151#[proc_macro_derive(PostgresEq, attributes(pgrx))]
1152pub fn derive_postgres_eq(input: TokenStream) -> TokenStream {
1153    let ast = parse_macro_input!(input as syn::DeriveInput);
1154    deriving_postgres_eq(ast).unwrap_or_else(syn::Error::into_compile_error).into()
1155}
1156
1157/**
1158Generate necessary code using the type in operators like `>`, `<`, `<=`, and `>=`.
1159
1160```rust,ignore
1161# use pgrx_pg_sys as pg_sys;
1162use pgrx::*;
1163use serde::{Deserialize, Serialize};
1164#[derive(
1165    Debug, Serialize, Deserialize, PartialEq, Eq,
1166     PartialOrd, Ord, PostgresEnum, PostgresOrd
1167)]
1168enum DogNames {
1169    Nami,
1170    Brandy,
1171}
1172```
1173Optionally accepts the following attributes:
1174
1175* `sql`: Same arguments as [`#[pgrx(sql = ..)]`](macro@pgrx).
1176
1177# No bounds?
1178Unlike some derives, this does not implement a "real" Rust trait, thus
1179PostgresOrd cannot be used in trait bounds, nor can it be manually implemented.
1180*/
1181#[proc_macro_derive(PostgresOrd, attributes(pgrx))]
1182pub fn derive_postgres_ord(input: TokenStream) -> TokenStream {
1183    let ast = parse_macro_input!(input as syn::DeriveInput);
1184    deriving_postgres_ord(ast).unwrap_or_else(syn::Error::into_compile_error).into()
1185}
1186
1187/**
1188Generate necessary code for stable hashing the type so it can be used with `USING hash` indexes.
1189
1190```rust,ignore
1191# use pgrx_pg_sys as pg_sys;
1192use pgrx::*;
1193use serde::{Deserialize, Serialize};
1194#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Hash, PostgresEnum, PostgresHash)]
1195enum DogNames {
1196    Nami,
1197    Brandy,
1198}
1199```
1200Optionally accepts the following attributes:
1201
1202* `sql`: Same arguments as [`#[pgrx(sql = ..)]`](macro@pgrx).
1203
1204# No bounds?
1205Unlike some derives, this does not implement a "real" Rust trait, thus
1206PostgresHash cannot be used in trait bounds, nor can it be manually implemented.
1207*/
1208#[proc_macro_derive(PostgresHash, attributes(pgrx))]
1209pub fn derive_postgres_hash(input: TokenStream) -> TokenStream {
1210    let ast = parse_macro_input!(input as syn::DeriveInput);
1211    deriving_postgres_hash(ast).unwrap_or_else(syn::Error::into_compile_error).into()
1212}
1213
1214/**
1215Declare a `pgrx::Aggregate` implementation on a type as able to used by Postgres as an aggregate.
1216
1217Functions inside the `impl` may use the [`#[pgrx]`](macro@pgrx) attribute.
1218*/
1219#[proc_macro_attribute]
1220pub fn pg_aggregate(_attr: TokenStream, item: TokenStream) -> TokenStream {
1221    // We don't care about `_attr` as we can find it in the `ItemMod`.
1222    fn wrapped(item_impl: ItemImpl) -> Result<TokenStream, syn::Error> {
1223        let sql_graph_entity_item = PgAggregate::new(item_impl)?;
1224
1225        Ok(sql_graph_entity_item.to_token_stream().into())
1226    }
1227
1228    let parsed_base = parse_macro_input!(item as syn::ItemImpl);
1229    wrapped(parsed_base).unwrap_or_else(|e| e.into_compile_error().into())
1230}
1231
1232/**
1233A helper attribute for various contexts.
1234
1235## Usage with [`#[pg_aggregate]`](macro@pg_aggregate).
1236
1237It can be decorated on functions inside a [`#[pg_aggregate]`](macro@pg_aggregate) implementation.
1238In this position, it takes the same args as [`#[pg_extern]`](macro@pg_extern), and those args have the same effect.
1239
1240## Usage for configuring SQL generation
1241
1242This attribute can be used to control the behavior of the SQL generator on a decorated item,
1243e.g. `#[pgrx(sql = false)]`
1244
1245Currently `sql` can be provided one of the following:
1246
1247* Disable SQL generation with `#[pgrx(sql = false)]`
1248* Call custom SQL generator function with `#[pgrx(sql = path::to_function)]`
1249* Render a specific fragment of SQL with a string `#[pgrx(sql = "CREATE FUNCTION ...")]`
1250
1251*/
1252#[proc_macro_attribute]
1253pub fn pgrx(_attr: TokenStream, item: TokenStream) -> TokenStream {
1254    item
1255}
1256
1257/**
1258Create a [PostgreSQL trigger function](https://www.postgresql.org/docs/current/plpgsql-trigger.html)
1259
1260Review the `pgrx::trigger_support::PgTrigger` documentation for use.
1261
1262 */
1263#[proc_macro_attribute]
1264pub fn pg_trigger(attrs: TokenStream, input: TokenStream) -> TokenStream {
1265    fn wrapped(attrs: TokenStream, input: TokenStream) -> Result<TokenStream, syn::Error> {
1266        use pgrx_sql_entity_graph::{PgTrigger, PgTriggerAttribute};
1267        use syn::parse::Parser;
1268        use syn::punctuated::Punctuated;
1269        use syn::Token;
1270
1271        let attributes =
1272            Punctuated::<PgTriggerAttribute, Token![,]>::parse_terminated.parse(attrs)?;
1273        let item_fn: syn::ItemFn = syn::parse(input)?;
1274        let trigger_item = PgTrigger::new(item_fn, attributes)?;
1275        let trigger_tokens = trigger_item.to_token_stream();
1276
1277        Ok(trigger_tokens.into())
1278    }
1279
1280    wrapped(attrs, input).unwrap_or_else(|e| e.into_compile_error().into())
1281}