Skip to main content

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;
14use std::ffi::CString;
15
16use proc_macro2::Ident;
17use quote::{ToTokens, format_ident, quote};
18use syn::spanned::Spanned;
19use syn::{Attribute, Data, DeriveInput, Item, ItemImpl, parse_macro_input};
20
21use operators::{deriving_postgres_eq, deriving_postgres_hash, deriving_postgres_ord};
22use pgrx_sql_entity_graph as sql_gen;
23use sql_gen::{
24    CodeEnrichment, ExtensionSql, ExtensionSqlFile, ExternArgs, PgAggregate, PgCast, PgExtern,
25    PostgresEnum, Schema, parse_extern_attributes,
26};
27
28mod operators;
29mod rewriter;
30
31/// Declare a function as `#[pg_guard]` to indicate that it is called from a Postgres `extern "C-unwind"`
32/// function so that Rust `panic!()`s (and Postgres `elog(ERROR)`s) will be properly handled by `pgrx`
33#[proc_macro_attribute]
34pub fn pg_guard(attr: TokenStream, item: TokenStream) -> TokenStream {
35    // get a usable token stream
36    let ast = parse_macro_input!(item as syn::Item);
37
38    let res = match ast {
39        // this is for processing the members of extern "C-unwind" { } blocks
40        // functions inside the block get wrapped as public, top-level unsafe functions that are not "extern"
41        Item::ForeignMod(block) => Ok(rewriter::extern_block(block)),
42
43        // process top-level functions
44        Item::Fn(func) => rewriter::item_fn_without_rewrite(func, attr),
45        unknown => Err(syn::Error::new(
46            unknown.span(),
47            "#[pg_guard] can only be applied to extern \"C-unwind\" blocks and top-level functions",
48        )),
49    };
50    res.unwrap_or_else(|e| e.into_compile_error()).into()
51}
52
53/// `#[pg_test]` functions are test functions (akin to `#[test]`), but they run in-process inside
54/// Postgres during `cargo pgrx test`.
55///
56/// This can be combined with test attributes like [`#[should_panic(expected = "..")]`][expected].
57///
58/// [expected]: https://doc.rust-lang.org/reference/attributes/testing.html#the-should_panic-attribute
59#[proc_macro_attribute]
60pub fn pg_test(attr: TokenStream, item: TokenStream) -> TokenStream {
61    let mut stream = proc_macro2::TokenStream::new();
62    let args = parse_extern_attributes(proc_macro2::TokenStream::from(attr.clone()));
63
64    let mut expected_error = None;
65    args.into_iter().for_each(|v| {
66        if let ExternArgs::ShouldPanic(message) = v {
67            expected_error = Some(message)
68        }
69    });
70
71    let ast = parse_macro_input!(item as syn::Item);
72
73    match ast {
74        Item::Fn(mut func) => {
75            // Here we need to break out attributes into test and non-test attributes,
76            // so the generated #[test] attributes are in the appropriate place.
77            let (test_attributes, non_test_attributes) =
78                func.attrs.into_iter().partition::<Vec<Attribute>, _>(|attr| {
79                    attr.path()
80                        .get_ident()
81                        .is_some_and(|ident| ident == "ignore" || ident == "should_panic")
82                });
83
84            func.attrs = non_test_attributes;
85
86            stream.extend(proc_macro2::TokenStream::from(pg_extern(
87                attr,
88                Item::Fn(func.clone()).to_token_stream().into(),
89            )));
90
91            let expected_error = match expected_error {
92                Some(msg) => quote! {Some(#msg)},
93                None => quote! {None},
94            };
95
96            let sql_funcname = func.sig.ident.to_string();
97            let test_func_name = format_ident!("pg_{}", func.sig.ident);
98
99            let attributes = func.attrs;
100            let mut att_stream = proc_macro2::TokenStream::new();
101
102            for a in attributes.iter() {
103                let as_str = a.to_token_stream().to_string();
104                att_stream.extend(quote! {
105                    options.push(#as_str);
106                });
107            }
108
109            stream.extend(quote! {
110                #[test]
111                #(#test_attributes)*
112                fn #test_func_name() {
113                    let mut options = Vec::new();
114                    #att_stream
115
116                    crate::pg_test::setup(options);
117                    let res = pgrx_tests::run_test(#sql_funcname, #expected_error, crate::pg_test::postgresql_conf_options());
118                    match res {
119                        Ok(()) => (),
120                        Err(e) => panic!("{e:?}")
121                    }
122                }
123            });
124        }
125
126        thing => {
127            return syn::Error::new(
128                thing.span(),
129                "#[pg_test] can only be applied to top-level functions",
130            )
131            .into_compile_error()
132            .into();
133        }
134    }
135
136    stream.into()
137}
138
139/// Associated macro for `#[pg_test]` to provide context back to your test framework to indicate
140/// that the test system is being initialized
141#[proc_macro_attribute]
142pub fn initialize(_attr: TokenStream, item: TokenStream) -> TokenStream {
143    item
144}
145
146/**
147Declare a function as `#[pg_cast]` to indicate that it represents a Postgres [cast](https://www.postgresql.org/docs/current/sql-createcast.html).
148
149* `assignment`: Corresponds to [`AS ASSIGNMENT`](https://www.postgresql.org/docs/current/sql-createcast.html).
150* `implicit`: Corresponds to [`AS IMPLICIT`](https://www.postgresql.org/docs/current/sql-createcast.html).
151
152By default if no attribute is specified, the cast function can only be used in an explicit cast.
153
154Functions MUST accept and return exactly one value whose type MUST be a `pgrx` supported type. `pgrx` supports many PostgreSQL types by default.
155New types can be defined via [`macro@PostgresType`] or [`macro@PostgresEnum`].
156
157`#[pg_cast]` also supports all the attributes supported by the [`macro@pg_extern]` macro, which are
158passed down to the underlying function.
159
160Example usage:
161```rust,ignore
162use pgrx::*;
163#[pg_cast(implicit)]
164fn cast_json_to_int(input: Json) -> i32 { todo!() }
165*/
166#[proc_macro_attribute]
167pub fn pg_cast(attr: TokenStream, item: TokenStream) -> TokenStream {
168    fn wrapped(attr: TokenStream, item: TokenStream) -> Result<TokenStream, syn::Error> {
169        use syn::parse::Parser;
170        use syn::punctuated::Punctuated;
171
172        let mut cast = None;
173        let mut pg_extern_attrs = proc_macro2::TokenStream::new();
174
175        // look for the attributes `#[pg_cast]` directly understands
176        match Punctuated::<syn::Path, syn::Token![,]>::parse_terminated.parse(attr) {
177            Ok(paths) => {
178                let mut new_paths = Punctuated::<syn::Path, syn::Token![,]>::new();
179                for path in paths {
180                    match (PgCast::try_from(path), &cast) {
181                        (Ok(style), None) => cast = Some(style),
182                        (Ok(_), Some(cast)) => {
183                            panic!("The cast type has already been set to `{cast:?}`")
184                        }
185
186                        // ... and anything it doesn't understand is blindly passed through to the
187                        // underlying `#[pg_extern]` function that gets created, which will ultimately
188                        // decide what's naughty and what's nice
189                        (Err(unknown), _) => {
190                            new_paths.push(unknown);
191                        }
192                    }
193                }
194
195                pg_extern_attrs.extend(new_paths.into_token_stream());
196            }
197            Err(err) => {
198                panic!("Failed to parse attribute to pg_cast: {err}")
199            }
200        }
201
202        let pg_extern = PgExtern::new(pg_extern_attrs, item.clone().into())?.0;
203        Ok(CodeEnrichment(pg_extern.as_cast(cast.unwrap_or_default())).to_token_stream().into())
204    }
205
206    wrapped(attr, item).unwrap_or_else(|e: syn::Error| e.into_compile_error().into())
207}
208
209/// Declare a function as `#[pg_operator]` to indicate that it represents a Postgres operator
210/// `cargo pgrx schema` will automatically generate the underlying SQL
211#[proc_macro_attribute]
212pub fn pg_operator(attr: TokenStream, item: TokenStream) -> TokenStream {
213    pg_extern(attr, item)
214}
215
216/// Used with `#[pg_operator]`.  1 value which is the operator name itself
217#[proc_macro_attribute]
218pub fn opname(_attr: TokenStream, item: TokenStream) -> TokenStream {
219    item
220}
221
222/// Used with `#[pg_operator]`.  1 value which is the function name
223#[proc_macro_attribute]
224pub fn commutator(_attr: TokenStream, item: TokenStream) -> TokenStream {
225    item
226}
227
228/// Used with `#[pg_operator]`.  1 value which is the function name
229#[proc_macro_attribute]
230pub fn negator(_attr: TokenStream, item: TokenStream) -> TokenStream {
231    item
232}
233
234/// Used with `#[pg_operator]`.  1 value which is the function name
235#[proc_macro_attribute]
236pub fn restrict(_attr: TokenStream, item: TokenStream) -> TokenStream {
237    item
238}
239
240/// Used with `#[pg_operator]`.  1 value which is the function name
241#[proc_macro_attribute]
242pub fn join(_attr: TokenStream, item: TokenStream) -> TokenStream {
243    item
244}
245
246/// Used with `#[pg_operator]`.  no values
247#[proc_macro_attribute]
248pub fn hashes(_attr: TokenStream, item: TokenStream) -> TokenStream {
249    item
250}
251
252/// Used with `#[pg_operator]`.  no values
253#[proc_macro_attribute]
254pub fn merges(_attr: TokenStream, item: TokenStream) -> TokenStream {
255    item
256}
257
258/**
259Declare a Rust module and its contents to be in a schema.
260
261The schema name will always be the `mod`'s identifier. So `mod flop` will create a `flop` schema.
262
263If there is a schema inside a schema, the most specific schema is chosen.
264
265In this example, the created `example` function is in the `dsl_filters` schema.
266
267```rust,ignore
268use pgrx::*;
269
270#[pg_schema]
271mod dsl {
272    use pgrx::*;
273    #[pg_schema]
274    mod dsl_filters {
275        use pgrx::*;
276        #[pg_extern]
277        fn example() { todo!() }
278    }
279}
280```
281
282File modules (like `mod name;`) aren't able to be supported due to [`rust/#54725`](https://github.com/rust-lang/rust/issues/54725).
283
284*/
285#[proc_macro_attribute]
286pub fn pg_schema(_attr: TokenStream, input: TokenStream) -> TokenStream {
287    fn wrapped(input: TokenStream) -> Result<TokenStream, syn::Error> {
288        let pgrx_schema: Schema = syn::parse(input)?;
289        Ok(pgrx_schema.to_token_stream().into())
290    }
291
292    wrapped(input).unwrap_or_else(|e: syn::Error| e.into_compile_error().into())
293}
294
295/**
296Declare SQL to be included in generated extension script.
297
298Accepts a String literal, a `name` attribute, and optionally others:
299
300* `name = "item"`: Set the unique identifier to `"item"` for use in `requires` declarations.
301* `requires = [item, item_two]`: References to other `name`s or Rust items which this SQL should be present after.
302* `creates = [ Type(submod::Cust), Enum(Pre), Function(defined)]`: Communicates that this SQL block creates certain entities.
303  Please note it **does not** create matching Rust types.
304* `bootstrap` (**Unique**): Communicates that this is SQL intended to go before all other generated SQL.
305* `finalize` (**Unique**): Communicates that this is SQL intended to go after all other generated SQL.
306
307You can declare some SQL without any positioning information, meaning it can end up anywhere in the generated SQL:
308
309```rust,ignore
310use pgrx_macros::extension_sql;
311
312extension_sql!(
313    r#"
314    -- SQL statements
315    "#,
316    name = "demo",
317);
318```
319
320To cause the SQL to be output at the start of the generated SQL:
321
322```rust,ignore
323use pgrx_macros::extension_sql;
324
325extension_sql!(
326    r#"
327    -- SQL statements
328    "#,
329    name = "demo",
330    bootstrap,
331);
332```
333
334To cause the SQL to be output at the end of the generated SQL:
335
336```rust,ignore
337use pgrx_macros::extension_sql;
338
339extension_sql!(
340    r#"
341    -- SQL statements
342    "#,
343    name = "demo",
344    finalize,
345);
346```
347
348To declare the SQL dependent, or a dependency of, other items:
349
350```rust,ignore
351use pgrx_macros::extension_sql;
352
353struct Treat;
354
355mod dog_characteristics {
356    enum DogAlignment {
357        Good
358    }
359}
360
361extension_sql!(r#"
362    -- SQL statements
363    "#,
364    name = "named_one",
365);
366
367extension_sql!(r#"
368    -- SQL statements
369    "#,
370    name = "demo",
371    requires = [ "named_one", dog_characteristics::DogAlignment ],
372);
373```
374
375To declare the SQL defines some entity (**Caution:** This is not recommended usage):
376
377```rust,ignore
378use pgrx::stringinfo::StringInfo;
379use pgrx::*;
380use pgrx_utils::get_named_capture;
381
382#[derive(Debug)]
383#[repr(C)]
384struct Complex {
385    x: f64,
386    y: f64,
387}
388
389extension_sql!(r#"\
390        CREATE TYPE complex;\
391    "#,
392    name = "create_complex_type",
393    creates = [Type(Complex)],
394);
395
396#[pg_extern(immutable)]
397fn complex_in(input: &core::ffi::CStr) -> PgBox<Complex> {
398    todo!()
399}
400
401#[pg_extern(immutable)]
402fn complex_out(complex: PgBox<Complex>) -> &'static ::core::ffi::CStr {
403    todo!()
404}
405
406extension_sql!(r#"\
407        CREATE TYPE complex (
408            internallength = 16,
409            input = complex_in,
410            output = complex_out,
411            alignment = double
412        );\
413    "#,
414    name = "demo",
415    requires = ["create_complex_type", complex_in, complex_out],
416);
417
418```
419*/
420#[proc_macro]
421pub fn extension_sql(input: TokenStream) -> TokenStream {
422    fn wrapped(input: TokenStream) -> Result<TokenStream, syn::Error> {
423        let ext_sql: CodeEnrichment<ExtensionSql> = syn::parse(input)?;
424        Ok(ext_sql.to_token_stream().into())
425    }
426
427    wrapped(input).unwrap_or_else(|e: syn::Error| e.into_compile_error().into())
428}
429
430/**
431Declare SQL (from a file) to be included in generated extension script.
432
433Accepts the same options as [`macro@extension_sql`]. `name` is automatically set to the file name (not the full path).
434
435You can declare some SQL without any positioning information, meaning it can end up anywhere in the generated SQL:
436
437```rust,ignore
438use pgrx_macros::extension_sql_file;
439extension_sql_file!(
440    "../static/demo.sql",
441);
442```
443
444To override the default name:
445
446```rust,ignore
447use pgrx_macros::extension_sql_file;
448
449extension_sql_file!(
450    "../static/demo.sql",
451    name = "singular",
452);
453```
454
455For all other options, and examples of them, see [`macro@extension_sql`].
456*/
457#[proc_macro]
458pub fn extension_sql_file(input: TokenStream) -> TokenStream {
459    fn wrapped(input: TokenStream) -> Result<TokenStream, syn::Error> {
460        let ext_sql: CodeEnrichment<ExtensionSqlFile> = syn::parse(input)?;
461        Ok(ext_sql.to_token_stream().into())
462    }
463
464    wrapped(input).unwrap_or_else(|e: syn::Error| e.into_compile_error().into())
465}
466
467/// Associated macro for `#[pg_extern]` or `#[macro@pg_operator]`.  Used to set the `SEARCH_PATH` option
468/// on the `CREATE FUNCTION` statement.
469#[proc_macro_attribute]
470pub fn search_path(_attr: TokenStream, item: TokenStream) -> TokenStream {
471    item
472}
473
474/**
475Declare a function as `#[pg_extern]` to indicate that it can be used by Postgres as a UDF.
476
477Optionally accepts the following attributes:
478
479* `immutable`: Corresponds to [`IMMUTABLE`](https://www.postgresql.org/docs/current/sql-createfunction.html).
480* `strict`: Corresponds to [`STRICT`](https://www.postgresql.org/docs/current/sql-createfunction.html).
481  + In most cases, `#[pg_extern]` can detect when no `Option<T>`s are used, and automatically set this.
482* `stable`: Corresponds to [`STABLE`](https://www.postgresql.org/docs/current/sql-createfunction.html).
483* `volatile`: Corresponds to [`VOLATILE`](https://www.postgresql.org/docs/current/sql-createfunction.html).
484* `raw`: Corresponds to [`RAW`](https://www.postgresql.org/docs/current/sql-createfunction.html).
485* `support`: Corresponds to [`SUPPORT`](https://www.postgresql.org/docs/current/sql-createfunction.html) and is the Rust path to a function to act as the SUPPORT function
486* `security_definer`: Corresponds to [`SECURITY DEFINER`](https://www.postgresql.org/docs/current/sql-createfunction.html)
487* `security_invoker`: Corresponds to [`SECURITY INVOKER`](https://www.postgresql.org/docs/current/sql-createfunction.html)
488* `parallel_safe`: Corresponds to [`PARALLEL SAFE`](https://www.postgresql.org/docs/current/sql-createfunction.html).
489* `parallel_unsafe`: Corresponds to [`PARALLEL UNSAFE`](https://www.postgresql.org/docs/current/sql-createfunction.html).
490* `parallel_restricted`: Corresponds to [`PARALLEL RESTRICTED`](https://www.postgresql.org/docs/current/sql-createfunction.html).
491* `no_guard`: Do not use `#[pg_guard]` with the function.
492* `sql`: Same arguments as [`#[pgrx(sql = ..)]`](macro@pgrx).
493* `name`: Specifies target function name. Defaults to Rust function name.
494
495Functions can accept and return any type which `pgrx` supports. `pgrx` supports many PostgreSQL types by default.
496New types can be defined via [`macro@PostgresType`] or [`macro@PostgresEnum`].
497
498
499Without any arguments or returns:
500```rust,ignore
501use pgrx::*;
502#[pg_extern]
503fn foo() { todo!() }
504```
505
506# Arguments
507It's possible to pass even complex arguments:
508
509```rust,ignore
510use pgrx::*;
511#[pg_extern]
512fn boop(
513    a: i32,
514    b: Option<i32>,
515    c: Vec<i32>,
516    d: Option<Vec<Option<i32>>>
517) { todo!() }
518```
519
520It's possible to set argument defaults, set by PostgreSQL when the function is invoked:
521
522```rust,ignore
523use pgrx::*;
524#[pg_extern]
525fn boop(a: default!(i32, 11111)) { todo!() }
526#[pg_extern]
527fn doop(
528    a: default!(Vec<Option<&str>>, "ARRAY[]::text[]"),
529    b: default!(String, "'note the inner quotes!'")
530) { todo!() }
531```
532
533The `default!()` macro may only be used in argument position.
534
535It accepts 2 arguments:
536
537* A type
538* A `bool`, numeric, or SQL string to represent the default. `"NULL"` is a possible value, as is `"'string'"`
539
540**If the default SQL entity created by the extension:** ensure it is added to `requires` as a dependency:
541
542```rust,ignore
543use pgrx::*;
544#[pg_extern]
545fn default_value() -> i32 { todo!() }
546
547#[pg_extern(
548    requires = [ default_value, ],
549)]
550fn do_it(
551    a: default!(i32, "default_value()"),
552) { todo!() }
553```
554
555# Returns
556
557It's possible to return even complex values, as well:
558
559```rust,ignore
560use pgrx::*;
561#[pg_extern]
562fn boop() -> i32 { todo!() }
563#[pg_extern]
564fn doop() -> Option<i32> { todo!() }
565#[pg_extern]
566fn swoop() -> Option<Vec<Option<i32>>> { todo!() }
567#[pg_extern]
568fn floop() -> (i32, i32) { todo!() }
569```
570
571Like in PostgreSQL, it's possible to return tables using iterators and the `name!()` macro:
572
573```rust,ignore
574use pgrx::*;
575#[pg_extern]
576fn floop<'a>() -> TableIterator<'a, (name!(a, i32), name!(b, i32))> {
577    TableIterator::new(None.into_iter())
578}
579
580#[pg_extern]
581fn singular_floop() -> (name!(a, i32), name!(b, i32)) {
582    todo!()
583}
584```
585
586The `name!()` macro may only be used in return position inside the `T` of a `TableIterator<'a, T>`.
587
588It accepts 2 arguments:
589
590* A name, such as `example`
591* A type
592
593# Special Cases
594
595`pg_sys::Oid` is a special cased type alias, in order to use it as an argument or return it must be
596passed with it's full module path (`pg_sys::Oid`) in order to be resolved.
597
598```rust,ignore
599use pgrx::*;
600
601#[pg_extern]
602fn example_arg(animals: pg_sys::Oid) {
603    todo!()
604}
605
606#[pg_extern]
607fn example_return() -> pg_sys::Oid {
608    todo!()
609}
610```
611
612*/
613#[proc_macro_attribute]
614#[track_caller]
615pub fn pg_extern(attr: TokenStream, item: TokenStream) -> TokenStream {
616    fn wrapped(attr: TokenStream, item: TokenStream) -> Result<TokenStream, syn::Error> {
617        let pg_extern_item = PgExtern::new(attr.into(), item.into())?;
618        Ok(pg_extern_item.to_token_stream().into())
619    }
620
621    wrapped(attr, item).unwrap_or_else(|e: syn::Error| e.into_compile_error().into())
622}
623
624/**
625Generate necessary bindings for using the enum with PostgreSQL.
626
627```rust,ignore
628# use pgrx_pg_sys as pg_sys;
629use pgrx::*;
630use serde::{Deserialize, Serialize};
631#[derive(Debug, Serialize, Deserialize, PostgresEnum)]
632enum DogNames {
633    Nami,
634    Brandy,
635}
636```
637
638*/
639#[proc_macro_derive(PostgresEnum, attributes(requires, pgrx))]
640pub fn postgres_enum(input: TokenStream) -> TokenStream {
641    let ast = parse_macro_input!(input as syn::DeriveInput);
642
643    impl_postgres_enum(ast).unwrap_or_else(|e| e.into_compile_error()).into()
644}
645
646fn impl_postgres_enum(ast: DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
647    let mut stream = proc_macro2::TokenStream::new();
648    let sql_graph_entity_ast = ast.clone();
649    let generics = &ast.generics.clone();
650    let enum_ident = &ast.ident;
651    let enum_name = enum_ident.to_string();
652
653    // validate that we're only operating on an enum
654    let Data::Enum(enum_data) = ast.data else {
655        return Err(syn::Error::new(
656            ast.span(),
657            "#[derive(PostgresEnum)] can only be applied to enums",
658        ));
659    };
660
661    let mut from_datum = proc_macro2::TokenStream::new();
662    let mut into_datum = proc_macro2::TokenStream::new();
663
664    for d in enum_data.variants.clone() {
665        let label_ident = &d.ident;
666        let label_string = label_ident.to_string();
667
668        from_datum.extend(quote! { #label_string => Some(#enum_ident::#label_ident), });
669        into_datum.extend(quote! { #enum_ident::#label_ident => Some(::pgrx::enum_helper::lookup_enum_by_label(#enum_name, #label_string)), });
670    }
671
672    // We need another variant of the params for the ArgAbi impl
673    let fcx_lt = syn::Lifetime::new("'fcx", proc_macro2::Span::mixed_site());
674    let mut generics_with_fcx = generics.clone();
675    // so that we can bound on Self: 'fcx
676    generics_with_fcx.make_where_clause().predicates.push(syn::WherePredicate::Type(
677        syn::PredicateType {
678            lifetimes: None,
679            bounded_ty: syn::parse_quote! { Self },
680            colon_token: syn::Token![:](proc_macro2::Span::mixed_site()),
681            bounds: syn::parse_quote! { #fcx_lt },
682        },
683    ));
684    let (impl_gens, ty_gens, where_clause) = generics_with_fcx.split_for_impl();
685    let mut impl_gens: syn::Generics = syn::parse_quote! { #impl_gens };
686    impl_gens
687        .params
688        .insert(0, syn::GenericParam::Lifetime(syn::LifetimeParam::new(fcx_lt.clone())));
689
690    stream.extend(quote! {
691        impl ::pgrx::datum::FromDatum for #enum_ident {
692            #[inline]
693            unsafe fn from_polymorphic_datum(datum: ::pgrx::pg_sys::Datum, is_null: bool, _typeoid: ::pgrx::pg_sys::Oid) -> Option<#enum_ident> {
694                if is_null {
695                    None
696                } else {
697                    // GREPME: non-primitive cast u64 as Oid
698                    let (name, _, _) = ::pgrx::enum_helper::lookup_enum_by_oid(unsafe { ::pgrx::pg_sys::Oid::from_datum(datum, is_null)? } );
699                    match name.as_str() {
700                        #from_datum
701                        _ => panic!("invalid enum value: {name}")
702                    }
703                }
704            }
705        }
706
707        unsafe impl #impl_gens ::pgrx::callconv::ArgAbi<#fcx_lt> for #enum_ident #ty_gens #where_clause {
708            unsafe fn unbox_arg_unchecked(arg: ::pgrx::callconv::Arg<'_, #fcx_lt>) -> Self {
709                let index = arg.index();
710                unsafe { arg.unbox_arg_using_from_datum().unwrap_or_else(|| panic!("argument {index} must not be null")) }
711            }
712
713        }
714
715        unsafe impl #generics ::pgrx::datum::UnboxDatum for #enum_ident #generics {
716            type As<'dat> = #enum_ident #generics where Self: 'dat;
717            #[inline]
718            unsafe fn unbox<'dat>(d: ::pgrx::datum::Datum<'dat>) -> Self::As<'dat> where Self: 'dat {
719                <Self as ::pgrx::datum::FromDatum>::from_datum(::core::mem::transmute(d), false).unwrap()
720            }
721        }
722
723        impl ::pgrx::datum::IntoDatum for #enum_ident {
724            #[inline]
725            fn into_datum(self) -> Option<::pgrx::pg_sys::Datum> {
726                match self {
727                    #into_datum
728                }
729            }
730
731            fn type_oid() -> ::pgrx::pg_sys::Oid {
732                ::pgrx::wrappers::regtypein(#enum_name)
733            }
734
735        }
736
737        unsafe impl ::pgrx::callconv::BoxRet for #enum_ident {
738            unsafe fn box_into<'fcx>(self, fcinfo: &mut ::pgrx::callconv::FcInfo<'fcx>) -> ::pgrx::datum::Datum<'fcx> {
739                match ::pgrx::datum::IntoDatum::into_datum(self) {
740                    None => fcinfo.return_null(),
741                    Some(datum) => unsafe { fcinfo.return_raw_datum(datum) },
742                }
743            }
744        }
745    });
746
747    let sql_graph_entity_item = PostgresEnum::from_derive_input(sql_graph_entity_ast)?;
748    sql_graph_entity_item.to_tokens(&mut stream);
749
750    Ok(stream)
751}
752
753/**
754Generate necessary bindings for using the type with PostgreSQL.
755
756```rust,ignore
757# use pgrx_pg_sys as pg_sys;
758use pgrx::*;
759use serde::{Deserialize, Serialize};
760#[derive(Debug, Serialize, Deserialize, PostgresType)]
761struct Dog {
762    treats_received: i64,
763    pets_gotten: i64,
764}
765
766#[derive(Debug, Serialize, Deserialize, PostgresType)]
767enum Animal {
768    Dog(Dog),
769}
770```
771
772Optionally accepts the following attributes:
773
774* `inoutfuncs(some_in_fn, some_out_fn)`: Define custom in/out functions for the type.
775* `pgvarlena_inoutfuncs(some_in_fn, some_out_fn)`: Define custom in/out functions for the `PgVarlena` of this type.
776* `pg_binary_protocol`: Use the binary protocol for this type.
777* `pgrx(alignment = "<align>")`: Derive Postgres alignment from Rust type. One of `"on"`, or `"off"`.
778* `sql`: Same arguments as [`#[pgrx(sql = ..)]`](macro@pgrx).
779*/
780#[proc_macro_derive(
781    PostgresType,
782    attributes(
783        inoutfuncs,
784        pgvarlena_inoutfuncs,
785        pg_binary_protocol,
786        bikeshed_postgres_type_manually_impl_from_into_datum,
787        requires,
788        pgrx
789    )
790)]
791pub fn postgres_type(input: TokenStream) -> TokenStream {
792    let ast = parse_macro_input!(input as syn::DeriveInput);
793
794    impl_postgres_type(ast).unwrap_or_else(|e| e.into_compile_error()).into()
795}
796
797fn impl_postgres_type(ast: DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
798    let name = &ast.ident;
799    let generics = &ast.generics.clone();
800    let has_lifetimes = generics.lifetimes().next();
801    let funcname_in = Ident::new(&format!("{name}_in").to_lowercase(), name.span());
802    let funcname_out = Ident::new(&format!("{name}_out").to_lowercase(), name.span());
803    let funcname_recv = Ident::new(&format!("{name}_recv").to_lowercase(), name.span());
804    let funcname_send = Ident::new(&format!("{name}_send").to_lowercase(), name.span());
805
806    let mut args = parse_postgres_type_args(&ast.attrs);
807    let mut stream = proc_macro2::TokenStream::new();
808
809    // validate that we're only operating on a struct
810    match ast.data {
811        Data::Struct(_) => { /* this is okay */ }
812        Data::Enum(_) => {
813            // this is okay and if there's an attempt to implement PostgresEnum,
814            // it will result in compile-time error of conflicting implementation
815            // of traits (IntoDatum, inout, etc.)
816        }
817        _ => {
818            return Err(syn::Error::new(
819                ast.span(),
820                "#[derive(PostgresType)] can only be applied to structs or enums",
821            ));
822        }
823    }
824
825    if !args.contains(&PostgresTypeAttribute::InOutFuncs)
826        && !args.contains(&PostgresTypeAttribute::PgVarlenaInOutFuncs)
827    {
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 as ::pgrx::datum::FromDatum>::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                    if let Some(m) = <#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                    if let Some(m) = <#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    if args.contains(&PostgresTypeAttribute::PgBinaryProtocol) {
1003        // At this time, the `PostgresTypeAttribute` does not impact the way we generate
1004        // the `recv` and `send` functions.
1005        stream.extend(quote! {
1006            #[doc(hidden)]
1007            #[::pgrx::pgrx_macros::pg_extern(immutable, strict, parallel_safe)]
1008            pub fn #funcname_recv #generics(
1009                mut internal: ::pgrx::datum::Internal,
1010            ) -> #name #generics {
1011                let buf = unsafe { internal.get_mut::<::pgrx::pg_sys::StringInfoData>().unwrap() };
1012
1013                let mut serialized = ::pgrx::StringInfo::new();
1014
1015                serialized.push_bytes(&[0u8; ::pgrx::pg_sys::VARHDRSZ]); // reserve space for the header
1016                serialized.push_bytes(unsafe {
1017                    core::slice::from_raw_parts(
1018                        buf.data as *const u8,
1019                        buf.len as usize
1020                    )
1021                });
1022
1023                let size = serialized.len();
1024                let varlena = serialized.into_char_ptr();
1025
1026                unsafe{
1027                    ::pgrx::set_varsize_4b(varlena as *mut ::pgrx::pg_sys::varlena, size as i32);
1028                    buf.cursor = buf.len;
1029                    ::pgrx::datum::cbor_decode(varlena as *mut ::pgrx::pg_sys::varlena)
1030                }
1031            }
1032            #[doc(hidden)]
1033            #[::pgrx::pgrx_macros::pg_extern(immutable, strict, parallel_safe)]
1034            pub fn #funcname_send #generics(input: #name #generics) -> Vec<u8> {
1035                use ::pgrx::datum::{FromDatum, IntoDatum};
1036                let Some(datum): Option<::pgrx::pg_sys::Datum> = input.into_datum() else {
1037                    ::pgrx::error!("Datum of type `{}` is unexpectedly NULL.", stringify!(#name));
1038                };
1039                unsafe {
1040                    let Some(serialized): Option<Vec<u8>> = FromDatum::from_datum(datum, false) else {
1041                        ::pgrx::error!("Failed to CBOR-serialize Datum to type `{}`.", stringify!(#name));
1042                    };
1043                    serialized
1044                }
1045            }
1046        });
1047    }
1048
1049    let sql_graph_entity_item = sql_gen::PostgresTypeDerive::from_derive_input(
1050        ast,
1051        args.contains(&PostgresTypeAttribute::PgBinaryProtocol),
1052    )?;
1053    sql_graph_entity_item.to_tokens(&mut stream);
1054
1055    Ok(stream)
1056}
1057
1058/// Derives the `GucEnum` trait, so that normal Rust enums can be used as a GUC.
1059#[proc_macro_derive(PostgresGucEnum, attributes(name, hidden))]
1060pub fn postgres_guc_enum(input: TokenStream) -> TokenStream {
1061    let ast = parse_macro_input!(input as syn::DeriveInput);
1062
1063    impl_guc_enum(ast).unwrap_or_else(|e| e.into_compile_error()).into()
1064}
1065
1066fn impl_guc_enum(ast: DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
1067    use std::str::FromStr;
1068    use syn::parse::Parse;
1069
1070    enum GucEnumAttribute {
1071        Name(CString),
1072        Hidden(bool),
1073    }
1074
1075    impl GucEnumAttribute {
1076        fn is_guc_enum_attribute(attribute: &str) -> bool {
1077            matches!(attribute, "name" | "hidden")
1078        }
1079    }
1080
1081    impl Parse for GucEnumAttribute {
1082        fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
1083            let ident: Ident = input.parse()?;
1084            let _: syn::token::Eq = input.parse()?;
1085            match ident.to_string().as_str() {
1086                "name" => input.parse::<syn::LitCStr>().map(|val| Self::Name(val.value())),
1087                "hidden" => input.parse::<syn::LitBool>().map(|val| Self::Hidden(val.value())),
1088                x => Err(syn::Error::new(input.span(), format!("unknown attribute {x}"))),
1089            }
1090        }
1091    }
1092
1093    // validate that we're only operating on an enum
1094    let Data::Enum(data) = ast.data.clone() else {
1095        return Err(syn::Error::new(
1096            ast.span(),
1097            "#[derive(PostgresGucEnum)] can only be applied to enums",
1098        ));
1099    };
1100    let ident = ast.ident.clone();
1101    let mut config = Vec::new();
1102    for (index, variant) in data.variants.iter().enumerate() {
1103        let default_name = CString::from_str(&variant.ident.to_string())
1104            .expect("the identifier contains a null character.");
1105        let default_val = index as i32;
1106        let default_hidden = false;
1107        let mut name = None;
1108        let mut hidden = None;
1109
1110        for attr in variant.attrs.iter() {
1111            if let Some(ident) = attr.path().get_ident()
1112                && GucEnumAttribute::is_guc_enum_attribute(&ident.to_string())
1113            {
1114                let pair: GucEnumAttribute = syn::parse2(attr.meta.to_token_stream())?;
1115                match pair {
1116                    GucEnumAttribute::Name(value) => {
1117                        if name.replace(value).is_some() {
1118                            return Err(syn::Error::new(ast.span(), "too many #[name] attributes"));
1119                        }
1120                    }
1121                    GucEnumAttribute::Hidden(value) => {
1122                        if hidden.replace(value).is_some() {
1123                            return Err(syn::Error::new(
1124                                ast.span(),
1125                                "too many #[hidden] attributes",
1126                            ));
1127                        }
1128                    }
1129                }
1130            }
1131        }
1132        let ident = variant.ident.clone();
1133        let name = name.unwrap_or(default_name);
1134        let val = default_val;
1135        let hidden = hidden.unwrap_or(default_hidden);
1136        config.push((ident, name, val, hidden));
1137    }
1138    let config_idents = config.iter().map(|x| &x.0).collect::<Vec<_>>();
1139    let config_names = config.iter().map(|x| &x.1).collect::<Vec<_>>();
1140    let config_vals = config.iter().map(|x| &x.2).collect::<Vec<_>>();
1141    let config_hiddens = config.iter().map(|x| &x.3).collect::<Vec<_>>();
1142
1143    Ok(quote! {
1144        unsafe impl ::pgrx::guc::GucEnum for #ident {
1145            fn from_ordinal(ordinal: i32) -> Self {
1146                match ordinal {
1147                    #(#config_vals => Self::#config_idents,)*
1148                    _ => panic!("Unrecognized ordinal"),
1149                }
1150            }
1151
1152            fn to_ordinal(&self) -> i32 {
1153                match self {
1154                    #(Self::#config_idents => #config_vals,)*
1155                }
1156            }
1157
1158            const CONFIG_ENUM_ENTRY: *const ::pgrx::pg_sys::config_enum_entry = [
1159                #(
1160                    ::pgrx::pg_sys::config_enum_entry {
1161                        name: #config_names.as_ptr(),
1162                        val: #config_vals,
1163                        hidden: #config_hiddens,
1164                    },
1165                )*
1166                ::pgrx::pg_sys::config_enum_entry {
1167                    name: core::ptr::null(),
1168                    val: 0,
1169                    hidden: false,
1170                },
1171            ].as_ptr();
1172        }
1173    })
1174}
1175
1176#[derive(Debug, Hash, Ord, PartialOrd, Eq, PartialEq)]
1177enum PostgresTypeAttribute {
1178    InOutFuncs,
1179    PgBinaryProtocol,
1180    PgVarlenaInOutFuncs,
1181    Default,
1182    ManualFromIntoDatum,
1183}
1184
1185fn parse_postgres_type_args(attributes: &[Attribute]) -> HashSet<PostgresTypeAttribute> {
1186    let mut categorized_attributes = HashSet::new();
1187
1188    for a in attributes {
1189        let path = &a.path();
1190        let path = quote! {#path}.to_string();
1191        match path.as_str() {
1192            "inoutfuncs" => {
1193                categorized_attributes.insert(PostgresTypeAttribute::InOutFuncs);
1194            }
1195            "pg_binary_protocol" => {
1196                categorized_attributes.insert(PostgresTypeAttribute::PgBinaryProtocol);
1197            }
1198            "pgvarlena_inoutfuncs" => {
1199                categorized_attributes.insert(PostgresTypeAttribute::PgVarlenaInOutFuncs);
1200            }
1201            "bikeshed_postgres_type_manually_impl_from_into_datum" => {
1202                categorized_attributes.insert(PostgresTypeAttribute::ManualFromIntoDatum);
1203            }
1204            _ => {
1205                // we can just ignore attributes we don't understand
1206            }
1207        };
1208    }
1209
1210    categorized_attributes
1211}
1212
1213/**
1214Generate necessary code using the type in operators like `==` and `!=`.
1215
1216```rust,ignore
1217# use pgrx_pg_sys as pg_sys;
1218use pgrx::*;
1219use serde::{Deserialize, Serialize};
1220#[derive(Debug, Serialize, Deserialize, PostgresEnum, PartialEq, Eq, PostgresEq)]
1221enum DogNames {
1222    Nami,
1223    Brandy,
1224}
1225```
1226Optionally accepts the following attributes:
1227
1228* `sql`: Same arguments as [`#[pgrx(sql = ..)]`](macro@pgrx).
1229
1230# No bounds?
1231Unlike some derives, this does not implement a "real" Rust trait, thus
1232PostgresEq cannot be used in trait bounds, nor can it be manually implemented.
1233*/
1234#[proc_macro_derive(PostgresEq, attributes(pgrx))]
1235pub fn derive_postgres_eq(input: TokenStream) -> TokenStream {
1236    let ast = parse_macro_input!(input as syn::DeriveInput);
1237    deriving_postgres_eq(ast).unwrap_or_else(syn::Error::into_compile_error).into()
1238}
1239
1240/**
1241Generate necessary code using the type in operators like `>`, `<`, `<=`, and `>=`.
1242
1243```rust,ignore
1244# use pgrx_pg_sys as pg_sys;
1245use pgrx::*;
1246use serde::{Deserialize, Serialize};
1247#[derive(
1248    Debug, Serialize, Deserialize, PartialEq, Eq,
1249     PartialOrd, Ord, PostgresEnum, PostgresOrd
1250)]
1251enum DogNames {
1252    Nami,
1253    Brandy,
1254}
1255```
1256Optionally accepts the following attributes:
1257
1258* `sql`: Same arguments as [`#[pgrx(sql = ..)]`](macro@pgrx).
1259
1260# No bounds?
1261Unlike some derives, this does not implement a "real" Rust trait, thus
1262PostgresOrd cannot be used in trait bounds, nor can it be manually implemented.
1263*/
1264#[proc_macro_derive(PostgresOrd, attributes(pgrx))]
1265pub fn derive_postgres_ord(input: TokenStream) -> TokenStream {
1266    let ast = parse_macro_input!(input as syn::DeriveInput);
1267    deriving_postgres_ord(ast).unwrap_or_else(syn::Error::into_compile_error).into()
1268}
1269
1270/**
1271Generate necessary code for stable hashing the type so it can be used with `USING hash` indexes.
1272
1273```rust,ignore
1274# use pgrx_pg_sys as pg_sys;
1275use pgrx::*;
1276use serde::{Deserialize, Serialize};
1277#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Hash, PostgresEnum, PostgresHash)]
1278enum DogNames {
1279    Nami,
1280    Brandy,
1281}
1282```
1283Optionally accepts the following attributes:
1284
1285* `sql`: Same arguments as [`#[pgrx(sql = ..)]`](macro@pgrx).
1286
1287# No bounds?
1288Unlike some derives, this does not implement a "real" Rust trait, thus
1289PostgresHash cannot be used in trait bounds, nor can it be manually implemented.
1290*/
1291#[proc_macro_derive(PostgresHash, attributes(pgrx))]
1292pub fn derive_postgres_hash(input: TokenStream) -> TokenStream {
1293    let ast = parse_macro_input!(input as syn::DeriveInput);
1294    deriving_postgres_hash(ast).unwrap_or_else(syn::Error::into_compile_error).into()
1295}
1296
1297/// Derives the `ToAggregateName` trait.
1298#[proc_macro_derive(AggregateName, attributes(aggregate_name))]
1299pub fn derive_aggregate_name(input: TokenStream) -> TokenStream {
1300    let ast = parse_macro_input!(input as syn::DeriveInput);
1301
1302    impl_aggregate_name(ast).unwrap_or_else(|e| e.into_compile_error()).into()
1303}
1304
1305fn impl_aggregate_name(ast: DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
1306    let name = &ast.ident;
1307
1308    let mut custom_name_value: Option<String> = None;
1309
1310    for attr in &ast.attrs {
1311        if attr.path().is_ident("aggregate_name") {
1312            let meta = &attr.meta;
1313            match meta {
1314                syn::Meta::NameValue(syn::MetaNameValue {
1315                    value: syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Str(s), .. }),
1316                    ..
1317                }) => {
1318                    custom_name_value = Some(s.value());
1319                    break;
1320                }
1321                _ => {
1322                    return Err(syn::Error::new_spanned(
1323                        attr,
1324                        "#[aggregate_name] must be in the form `#[aggregate_name = \"string_literal\"]`",
1325                    ));
1326                }
1327            }
1328        }
1329    }
1330
1331    let name_str = custom_name_value.unwrap_or(name.to_string());
1332
1333    let expanded = quote! {
1334        impl ::pgrx::aggregate::ToAggregateName for #name {
1335            const NAME: &'static str = #name_str;
1336        }
1337    };
1338
1339    Ok(expanded)
1340}
1341
1342/**
1343Declare a `pgrx::Aggregate` implementation on a type as able to used by Postgres as an aggregate.
1344
1345Functions inside the `impl` may use the [`#[pgrx]`](macro@pgrx) attribute.
1346*/
1347#[proc_macro_attribute]
1348pub fn pg_aggregate(_attr: TokenStream, item: TokenStream) -> TokenStream {
1349    // We don't care about `_attr` as we can find it in the `ItemMod`.
1350    fn wrapped(item_impl: ItemImpl) -> Result<TokenStream, syn::Error> {
1351        let sql_graph_entity_item = PgAggregate::new(item_impl)?;
1352
1353        Ok(sql_graph_entity_item.to_token_stream().into())
1354    }
1355
1356    let parsed_base = parse_macro_input!(item as syn::ItemImpl);
1357    wrapped(parsed_base).unwrap_or_else(|e| e.into_compile_error().into())
1358}
1359
1360/**
1361A helper attribute for various contexts.
1362
1363## Usage with [`#[pg_aggregate]`](macro@pg_aggregate).
1364
1365It can be decorated on functions inside a [`#[pg_aggregate]`](macro@pg_aggregate) implementation.
1366In this position, it takes the same args as [`#[pg_extern]`](macro@pg_extern), and those args have the same effect.
1367
1368## Usage for configuring SQL generation
1369
1370This attribute can be used to control the behavior of the SQL generator on a decorated item,
1371e.g. `#[pgrx(sql = false)]`
1372
1373Currently `sql` can be provided one of the following:
1374
1375* Disable SQL generation with `#[pgrx(sql = false)]`
1376* Call custom SQL generator function with `#[pgrx(sql = path::to_function)]`
1377* Render a specific fragment of SQL with a string `#[pgrx(sql = "CREATE FUNCTION ...")]`
1378
1379*/
1380#[proc_macro_attribute]
1381pub fn pgrx(_attr: TokenStream, item: TokenStream) -> TokenStream {
1382    item
1383}
1384
1385/**
1386Create a [PostgreSQL trigger function](https://www.postgresql.org/docs/current/plpgsql-trigger.html)
1387
1388Review the `pgrx::trigger_support::PgTrigger` documentation for use.
1389
1390 */
1391#[proc_macro_attribute]
1392pub fn pg_trigger(attrs: TokenStream, input: TokenStream) -> TokenStream {
1393    fn wrapped(attrs: TokenStream, input: TokenStream) -> Result<TokenStream, syn::Error> {
1394        use pgrx_sql_entity_graph::{PgTrigger, PgTriggerAttribute};
1395        use syn::Token;
1396        use syn::parse::Parser;
1397        use syn::punctuated::Punctuated;
1398
1399        let attributes =
1400            Punctuated::<PgTriggerAttribute, Token![,]>::parse_terminated.parse(attrs)?;
1401        let item_fn: syn::ItemFn = syn::parse(input)?;
1402        let trigger_item = PgTrigger::new(item_fn, attributes)?;
1403        let trigger_tokens = trigger_item.to_token_stream();
1404
1405        Ok(trigger_tokens.into())
1406    }
1407
1408    wrapped(attrs, input).unwrap_or_else(|e| e.into_compile_error().into())
1409}