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