1extern 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#[proc_macro_attribute]
34pub fn pg_guard(attr: TokenStream, item: TokenStream) -> TokenStream {
35 let ast = parse_macro_input!(item as syn::Item);
37
38 let res = match ast {
39 Item::ForeignMod(block) => Ok(rewriter::extern_block(block)),
42
43 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#[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 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#[proc_macro_attribute]
142pub fn initialize(_attr: TokenStream, item: TokenStream) -> TokenStream {
143 item
144}
145
146#[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 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 (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#[proc_macro_attribute]
212pub fn pg_operator(attr: TokenStream, item: TokenStream) -> TokenStream {
213 pg_extern(attr, item)
214}
215
216#[proc_macro_attribute]
218pub fn opname(_attr: TokenStream, item: TokenStream) -> TokenStream {
219 item
220}
221
222#[proc_macro_attribute]
224pub fn commutator(_attr: TokenStream, item: TokenStream) -> TokenStream {
225 item
226}
227
228#[proc_macro_attribute]
230pub fn negator(_attr: TokenStream, item: TokenStream) -> TokenStream {
231 item
232}
233
234#[proc_macro_attribute]
236pub fn restrict(_attr: TokenStream, item: TokenStream) -> TokenStream {
237 item
238}
239
240#[proc_macro_attribute]
242pub fn join(_attr: TokenStream, item: TokenStream) -> TokenStream {
243 item
244}
245
246#[proc_macro_attribute]
248pub fn hashes(_attr: TokenStream, item: TokenStream) -> TokenStream {
249 item
250}
251
252#[proc_macro_attribute]
254pub fn merges(_attr: TokenStream, item: TokenStream) -> TokenStream {
255 item
256}
257
258#[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#[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#[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#[proc_macro_attribute]
470pub fn search_path(_attr: TokenStream, item: TokenStream) -> TokenStream {
471 item
472}
473
474#[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#[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 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 let fcx_lt = syn::Lifetime::new("'fcx", proc_macro2::Span::mixed_site());
674 let mut generics_with_fcx = generics.clone();
675 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),
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 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#[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 match ast.data {
811 Data::Struct(_) => { }
812 Data::Enum(_) => {
813 }
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 args.insert(PostgresTypeAttribute::Default);
830 }
831
832 let lifetime = match has_lifetimes {
833 Some(lifetime) => quote! {#lifetime},
834 None => quote! {'_},
835 };
836
837 let fcx_lt = syn::Lifetime::new("'fcx", proc_macro2::Span::mixed_site());
839 let mut generics_with_fcx = generics.clone();
840 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),
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 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 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 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); ::pgrx::ffi::CString::from_vec_with_nul(bytes).unwrap()
952 }
953 });
954 } else if args.contains(&PostgresTypeAttribute::InOutFuncs) {
955 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 unsafe { buffer.leak_cstr().to_owned() }
975 }
976 });
977 } else if args.contains(&PostgresTypeAttribute::PgVarlenaInOutFuncs) {
978 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 unsafe { buffer.leak_cstr().to_owned() }
998 }
999 });
1000 }
1001
1002 if args.contains(&PostgresTypeAttribute::PgBinaryProtocol) {
1003 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]); 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#[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 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 }
1207 };
1208 }
1209
1210 categorized_attributes
1211}
1212
1213#[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#[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#[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#[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#[proc_macro_attribute]
1348pub fn pg_aggregate(_attr: TokenStream, item: TokenStream) -> TokenStream {
1349 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#[proc_macro_attribute]
1381pub fn pgrx(_attr: TokenStream, item: TokenStream) -> TokenStream {
1382 item
1383}
1384
1385#[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}