1extern crate proc_macro;
11
12use proc_macro::TokenStream;
13use std::collections::HashSet;
14use std::ffi::CString;
15
16use proc_macro2::Ident;
17use quote::{format_ident, quote, ToTokens};
18use syn::spanned::Spanned;
19use syn::{parse_macro_input, Attribute, Data, DeriveInput, Item, ItemImpl};
20
21use operators::{deriving_postgres_eq, deriving_postgres_hash, deriving_postgres_ord};
22use pgrx_sql_entity_graph as sql_gen;
23use sql_gen::{
24 parse_extern_attributes, CodeEnrichment, ExtensionSql, ExtensionSqlFile, ExternArgs,
25 PgAggregate, PgCast, PgExtern, PostgresEnum, Schema,
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),
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 mut test_attributes = Vec::new();
78 let mut non_test_attributes = Vec::new();
79
80 for attribute in func.attrs.iter() {
81 if let Some(ident) = attribute.path().get_ident() {
82 let ident_str = ident.to_string();
83
84 if ident_str == "ignore" || ident_str == "should_panic" {
85 test_attributes.push(attribute.clone());
86 } else {
87 non_test_attributes.push(attribute.clone());
88 }
89 } else {
90 non_test_attributes.push(attribute.clone());
91 }
92 }
93
94 func.attrs = non_test_attributes;
95
96 stream.extend(proc_macro2::TokenStream::from(pg_extern(
97 attr,
98 Item::Fn(func.clone()).to_token_stream().into(),
99 )));
100
101 let expected_error = match expected_error {
102 Some(msg) => quote! {Some(#msg)},
103 None => quote! {None},
104 };
105
106 let sql_funcname = func.sig.ident.to_string();
107 let test_func_name = format_ident!("pg_{}", func.sig.ident);
108
109 let attributes = func.attrs;
110 let mut att_stream = proc_macro2::TokenStream::new();
111
112 for a in attributes.iter() {
113 let as_str = a.to_token_stream().to_string();
114 att_stream.extend(quote! {
115 options.push(#as_str);
116 });
117 }
118
119 stream.extend(quote! {
120 #[test]
121 #(#test_attributes)*
122 fn #test_func_name() {
123 let mut options = Vec::new();
124 #att_stream
125
126 crate::pg_test::setup(options);
127 let res = pgrx_tests::run_test(#sql_funcname, #expected_error, crate::pg_test::postgresql_conf_options());
128 match res {
129 Ok(()) => (),
130 Err(e) => panic!("{e:?}")
131 }
132 }
133 });
134 }
135
136 thing => {
137 return syn::Error::new(
138 thing.span(),
139 "#[pg_test] can only be applied to top-level functions",
140 )
141 .into_compile_error()
142 .into()
143 }
144 }
145
146 stream.into()
147}
148
149#[proc_macro_attribute]
152pub fn initialize(_attr: TokenStream, item: TokenStream) -> TokenStream {
153 item
154}
155
156#[proc_macro_attribute]
177pub fn pg_cast(attr: TokenStream, item: TokenStream) -> TokenStream {
178 fn wrapped(attr: TokenStream, item: TokenStream) -> Result<TokenStream, syn::Error> {
179 use syn::parse::Parser;
180 use syn::punctuated::Punctuated;
181
182 let mut cast = None;
183 let mut pg_extern_attrs = proc_macro2::TokenStream::new();
184
185 match Punctuated::<syn::Path, syn::Token![,]>::parse_terminated.parse(attr) {
187 Ok(paths) => {
188 let mut new_paths = Punctuated::<syn::Path, syn::Token![,]>::new();
189 for path in paths {
190 match (PgCast::try_from(path), &cast) {
191 (Ok(style), None) => cast = Some(style),
192 (Ok(_), Some(cast)) => {
193 panic!("The cast type has already been set to `{cast:?}`")
194 }
195
196 (Err(unknown), _) => {
200 new_paths.push(unknown);
201 }
202 }
203 }
204
205 pg_extern_attrs.extend(new_paths.into_token_stream());
206 }
207 Err(err) => {
208 panic!("Failed to parse attribute to pg_cast: {err}")
209 }
210 }
211
212 let pg_extern = PgExtern::new(pg_extern_attrs, item.clone().into())?.0;
213 Ok(CodeEnrichment(pg_extern.as_cast(cast.unwrap_or_default())).to_token_stream().into())
214 }
215
216 wrapped(attr, item).unwrap_or_else(|e: syn::Error| e.into_compile_error().into())
217}
218
219#[proc_macro_attribute]
222pub fn pg_operator(attr: TokenStream, item: TokenStream) -> TokenStream {
223 pg_extern(attr, item)
224}
225
226#[proc_macro_attribute]
228pub fn opname(_attr: TokenStream, item: TokenStream) -> TokenStream {
229 item
230}
231
232#[proc_macro_attribute]
234pub fn commutator(_attr: TokenStream, item: TokenStream) -> TokenStream {
235 item
236}
237
238#[proc_macro_attribute]
240pub fn negator(_attr: TokenStream, item: TokenStream) -> TokenStream {
241 item
242}
243
244#[proc_macro_attribute]
246pub fn restrict(_attr: TokenStream, item: TokenStream) -> TokenStream {
247 item
248}
249
250#[proc_macro_attribute]
252pub fn join(_attr: TokenStream, item: TokenStream) -> TokenStream {
253 item
254}
255
256#[proc_macro_attribute]
258pub fn hashes(_attr: TokenStream, item: TokenStream) -> TokenStream {
259 item
260}
261
262#[proc_macro_attribute]
264pub fn merges(_attr: TokenStream, item: TokenStream) -> TokenStream {
265 item
266}
267
268#[proc_macro_attribute]
296pub fn pg_schema(_attr: TokenStream, input: TokenStream) -> TokenStream {
297 fn wrapped(input: TokenStream) -> Result<TokenStream, syn::Error> {
298 let pgrx_schema: Schema = syn::parse(input)?;
299 Ok(pgrx_schema.to_token_stream().into())
300 }
301
302 wrapped(input).unwrap_or_else(|e: syn::Error| e.into_compile_error().into())
303}
304
305#[proc_macro]
431pub fn extension_sql(input: TokenStream) -> TokenStream {
432 fn wrapped(input: TokenStream) -> Result<TokenStream, syn::Error> {
433 let ext_sql: CodeEnrichment<ExtensionSql> = syn::parse(input)?;
434 Ok(ext_sql.to_token_stream().into())
435 }
436
437 wrapped(input).unwrap_or_else(|e: syn::Error| e.into_compile_error().into())
438}
439
440#[proc_macro]
468pub fn extension_sql_file(input: TokenStream) -> TokenStream {
469 fn wrapped(input: TokenStream) -> Result<TokenStream, syn::Error> {
470 let ext_sql: CodeEnrichment<ExtensionSqlFile> = syn::parse(input)?;
471 Ok(ext_sql.to_token_stream().into())
472 }
473
474 wrapped(input).unwrap_or_else(|e: syn::Error| e.into_compile_error().into())
475}
476
477#[proc_macro_attribute]
480pub fn search_path(_attr: TokenStream, item: TokenStream) -> TokenStream {
481 item
482}
483
484#[proc_macro_attribute]
624#[track_caller]
625pub fn pg_extern(attr: TokenStream, item: TokenStream) -> TokenStream {
626 fn wrapped(attr: TokenStream, item: TokenStream) -> Result<TokenStream, syn::Error> {
627 let pg_extern_item = PgExtern::new(attr.into(), item.into())?;
628 Ok(pg_extern_item.to_token_stream().into())
629 }
630
631 wrapped(attr, item).unwrap_or_else(|e: syn::Error| e.into_compile_error().into())
632}
633
634#[proc_macro_derive(PostgresEnum, attributes(requires, pgrx))]
650pub fn postgres_enum(input: TokenStream) -> TokenStream {
651 let ast = parse_macro_input!(input as syn::DeriveInput);
652
653 impl_postgres_enum(ast).unwrap_or_else(|e| e.into_compile_error()).into()
654}
655
656fn impl_postgres_enum(ast: DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
657 let mut stream = proc_macro2::TokenStream::new();
658 let sql_graph_entity_ast = ast.clone();
659 let generics = &ast.generics.clone();
660 let enum_ident = &ast.ident;
661 let enum_name = enum_ident.to_string();
662
663 let Data::Enum(enum_data) = ast.data else {
665 return Err(syn::Error::new(
666 ast.span(),
667 "#[derive(PostgresEnum)] can only be applied to enums",
668 ));
669 };
670
671 let mut from_datum = proc_macro2::TokenStream::new();
672 let mut into_datum = proc_macro2::TokenStream::new();
673
674 for d in enum_data.variants.clone() {
675 let label_ident = &d.ident;
676 let label_string = label_ident.to_string();
677
678 from_datum.extend(quote! { #label_string => Some(#enum_ident::#label_ident), });
679 into_datum.extend(quote! { #enum_ident::#label_ident => Some(::pgrx::enum_helper::lookup_enum_by_label(#enum_name, #label_string)), });
680 }
681
682 let fcx_lt = syn::Lifetime::new("'fcx", proc_macro2::Span::mixed_site());
684 let mut generics_with_fcx = generics.clone();
685 generics_with_fcx.make_where_clause().predicates.push(syn::WherePredicate::Type(
687 syn::PredicateType {
688 lifetimes: None,
689 bounded_ty: syn::parse_quote! { Self },
690 colon_token: syn::Token),
691 bounds: syn::parse_quote! { #fcx_lt },
692 },
693 ));
694 let (impl_gens, ty_gens, where_clause) = generics_with_fcx.split_for_impl();
695 let mut impl_gens: syn::Generics = syn::parse_quote! { #impl_gens };
696 impl_gens
697 .params
698 .insert(0, syn::GenericParam::Lifetime(syn::LifetimeParam::new(fcx_lt.clone())));
699
700 stream.extend(quote! {
701 impl ::pgrx::datum::FromDatum for #enum_ident {
702 #[inline]
703 unsafe fn from_polymorphic_datum(datum: ::pgrx::pg_sys::Datum, is_null: bool, _typeoid: ::pgrx::pg_sys::Oid) -> Option<#enum_ident> {
704 if is_null {
705 None
706 } else {
707 let (name, _, _) = ::pgrx::enum_helper::lookup_enum_by_oid(unsafe { ::pgrx::pg_sys::Oid::from_datum(datum, is_null)? } );
709 match name.as_str() {
710 #from_datum
711 _ => panic!("invalid enum value: {name}")
712 }
713 }
714 }
715 }
716
717 unsafe impl #impl_gens ::pgrx::callconv::ArgAbi<#fcx_lt> for #enum_ident #ty_gens #where_clause {
718 unsafe fn unbox_arg_unchecked(arg: ::pgrx::callconv::Arg<'_, #fcx_lt>) -> Self {
719 let index = arg.index();
720 unsafe { arg.unbox_arg_using_from_datum().unwrap_or_else(|| panic!("argument {index} must not be null")) }
721 }
722
723 }
724
725 unsafe impl #generics ::pgrx::datum::UnboxDatum for #enum_ident #generics {
726 type As<'dat> = #enum_ident #generics where Self: 'dat;
727 #[inline]
728 unsafe fn unbox<'dat>(d: ::pgrx::datum::Datum<'dat>) -> Self::As<'dat> where Self: 'dat {
729 <Self as ::pgrx::datum::FromDatum>::from_datum(::core::mem::transmute(d), false).unwrap()
730 }
731 }
732
733 impl ::pgrx::datum::IntoDatum for #enum_ident {
734 #[inline]
735 fn into_datum(self) -> Option<::pgrx::pg_sys::Datum> {
736 match self {
737 #into_datum
738 }
739 }
740
741 fn type_oid() -> ::pgrx::pg_sys::Oid {
742 ::pgrx::wrappers::regtypein(#enum_name)
743 }
744
745 }
746
747 unsafe impl ::pgrx::callconv::BoxRet for #enum_ident {
748 unsafe fn box_into<'fcx>(self, fcinfo: &mut ::pgrx::callconv::FcInfo<'fcx>) -> ::pgrx::datum::Datum<'fcx> {
749 match ::pgrx::datum::IntoDatum::into_datum(self) {
750 None => fcinfo.return_null(),
751 Some(datum) => unsafe { fcinfo.return_raw_datum(datum) },
752 }
753 }
754 }
755 });
756
757 let sql_graph_entity_item = PostgresEnum::from_derive_input(sql_graph_entity_ast)?;
758 sql_graph_entity_item.to_tokens(&mut stream);
759
760 Ok(stream)
761}
762
763#[proc_macro_derive(
791 PostgresType,
792 attributes(
793 inoutfuncs,
794 pgvarlena_inoutfuncs,
795 pg_binary_protocol,
796 bikeshed_postgres_type_manually_impl_from_into_datum,
797 requires,
798 pgrx
799 )
800)]
801pub fn postgres_type(input: TokenStream) -> TokenStream {
802 let ast = parse_macro_input!(input as syn::DeriveInput);
803
804 impl_postgres_type(ast).unwrap_or_else(|e| e.into_compile_error()).into()
805}
806
807fn impl_postgres_type(ast: DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
808 let name = &ast.ident;
809 let generics = &ast.generics.clone();
810 let has_lifetimes = generics.lifetimes().next();
811 let funcname_in = Ident::new(&format!("{name}_in").to_lowercase(), name.span());
812 let funcname_out = Ident::new(&format!("{name}_out").to_lowercase(), name.span());
813 let funcname_recv = Ident::new(&format!("{name}_recv").to_lowercase(), name.span());
814 let funcname_send = Ident::new(&format!("{name}_send").to_lowercase(), name.span());
815
816 let mut args = parse_postgres_type_args(&ast.attrs);
817 let mut stream = proc_macro2::TokenStream::new();
818
819 match ast.data {
821 Data::Struct(_) => { }
822 Data::Enum(_) => {
823 }
827 _ => {
828 return Err(syn::Error::new(
829 ast.span(),
830 "#[derive(PostgresType)] can only be applied to structs or enums",
831 ))
832 }
833 }
834
835 if !args.contains(&PostgresTypeAttribute::InOutFuncs)
836 && !args.contains(&PostgresTypeAttribute::PgVarlenaInOutFuncs)
837 {
838 args.insert(PostgresTypeAttribute::Default);
840 }
841
842 let lifetime = match has_lifetimes {
843 Some(lifetime) => quote! {#lifetime},
844 None => quote! {'_},
845 };
846
847 let fcx_lt = syn::Lifetime::new("'fcx", proc_macro2::Span::mixed_site());
849 let mut generics_with_fcx = generics.clone();
850 generics_with_fcx.make_where_clause().predicates.push(syn::WherePredicate::Type(
852 syn::PredicateType {
853 lifetimes: None,
854 bounded_ty: syn::parse_quote! { Self },
855 colon_token: syn::Token),
856 bounds: syn::parse_quote! { #fcx_lt },
857 },
858 ));
859 let (impl_gens, ty_gens, where_clause) = generics_with_fcx.split_for_impl();
860 let mut impl_gens: syn::Generics = syn::parse_quote! { #impl_gens };
861 impl_gens
862 .params
863 .insert(0, syn::GenericParam::Lifetime(syn::LifetimeParam::new(fcx_lt.clone())));
864
865 stream.extend(quote! {
868 impl #generics ::pgrx::datum::PostgresType for #name #generics { }
869 });
870
871 if !args.contains(&PostgresTypeAttribute::ManualFromIntoDatum) {
872 stream.extend(
873 quote! {
874 impl #generics ::pgrx::datum::IntoDatum for #name #generics {
875 fn into_datum(self) -> Option<::pgrx::pg_sys::Datum> {
876 #[allow(deprecated)]
877 Some(unsafe { ::pgrx::datum::cbor_encode(&self) }.into())
878 }
879
880 fn type_oid() -> ::pgrx::pg_sys::Oid {
881 ::pgrx::wrappers::rust_regtypein::<Self>()
882 }
883 }
884
885 unsafe impl #generics ::pgrx::callconv::BoxRet for #name #generics {
886 unsafe fn box_into<'fcx>(self, fcinfo: &mut ::pgrx::callconv::FcInfo<'fcx>) -> ::pgrx::datum::Datum<'fcx> {
887 match ::pgrx::datum::IntoDatum::into_datum(self) {
888 None => fcinfo.return_null(),
889 Some(datum) => unsafe { fcinfo.return_raw_datum(datum) },
890 }
891 }
892 }
893
894 impl #generics ::pgrx::datum::FromDatum for #name #generics {
895 unsafe fn from_polymorphic_datum(
896 datum: ::pgrx::pg_sys::Datum,
897 is_null: bool,
898 _typoid: ::pgrx::pg_sys::Oid,
899 ) -> Option<Self> {
900 if is_null {
901 None
902 } else {
903 #[allow(deprecated)]
904 ::pgrx::datum::cbor_decode(datum.cast_mut_ptr())
905 }
906 }
907
908 unsafe fn from_datum_in_memory_context(
909 mut memory_context: ::pgrx::memcxt::PgMemoryContexts,
910 datum: ::pgrx::pg_sys::Datum,
911 is_null: bool,
912 _typoid: ::pgrx::pg_sys::Oid,
913 ) -> Option<Self> {
914 if is_null {
915 None
916 } else {
917 memory_context.switch_to(|_| {
918 let varlena = ::pgrx::pg_sys::pg_detoast_datum_copy(datum.cast_mut_ptr());
920 <Self as ::pgrx::datum::FromDatum>::from_datum(varlena.into(), is_null)
921 })
922 }
923 }
924 }
925
926 unsafe impl #generics ::pgrx::datum::UnboxDatum for #name #generics {
927 type As<'dat> = Self where Self: 'dat;
928 unsafe fn unbox<'dat>(datum: ::pgrx::datum::Datum<'dat>) -> Self::As<'dat> where Self: 'dat {
929 <Self as ::pgrx::datum::FromDatum>::from_datum(::core::mem::transmute(datum), false).unwrap()
930 }
931 }
932
933 unsafe impl #impl_gens ::pgrx::callconv::ArgAbi<#fcx_lt> for #name #ty_gens #where_clause
934 {
935 unsafe fn unbox_arg_unchecked(arg: ::pgrx::callconv::Arg<'_, #fcx_lt>) -> Self {
936 let index = arg.index();
937 unsafe { arg.unbox_arg_using_from_datum().unwrap_or_else(|| panic!("argument {index} must not be null")) }
938 }
939 }
940 }
941 )
942 }
943
944 if args.contains(&PostgresTypeAttribute::Default) {
947 stream.extend(quote! {
948 #[doc(hidden)]
949 #[::pgrx::pgrx_macros::pg_extern(immutable, parallel_safe)]
950 pub fn #funcname_in #generics(input: Option<&#lifetime ::core::ffi::CStr>) -> Option<#name #generics> {
951 use ::pgrx::inoutfuncs::json_from_slice;
952 input.map(|cstr| json_from_slice(cstr.to_bytes()).ok()).flatten()
953 }
954
955 #[doc(hidden)]
956 #[::pgrx::pgrx_macros::pg_extern (immutable, parallel_safe)]
957 pub fn #funcname_out #generics(input: #name #generics) -> ::pgrx::ffi::CString {
958 use ::pgrx::inoutfuncs::json_to_vec;
959 let mut bytes = json_to_vec(&input).unwrap();
960 bytes.push(0); ::pgrx::ffi::CString::from_vec_with_nul(bytes).unwrap()
962 }
963 });
964 } else if args.contains(&PostgresTypeAttribute::InOutFuncs) {
965 stream.extend(quote! {
967 #[doc(hidden)]
968 #[::pgrx::pgrx_macros::pg_extern(immutable,parallel_safe)]
969 pub fn #funcname_in #generics(input: Option<&::core::ffi::CStr>) -> Option<#name #generics> {
970 input.map_or_else(|| {
971 if let Some(m) = <#name as ::pgrx::inoutfuncs::InOutFuncs>::NULL_ERROR_MESSAGE {
972 ::pgrx::pg_sys::error!("{m}");
973 }
974 None
975 }, |i| Some(<#name as ::pgrx::inoutfuncs::InOutFuncs>::input(i)))
976 }
977
978 #[doc(hidden)]
979 #[::pgrx::pgrx_macros::pg_extern(immutable,parallel_safe)]
980 pub fn #funcname_out #generics(input: #name #generics) -> ::pgrx::ffi::CString {
981 let mut buffer = ::pgrx::stringinfo::StringInfo::new();
982 ::pgrx::inoutfuncs::InOutFuncs::output(&input, &mut buffer);
983 unsafe { buffer.leak_cstr().to_owned() }
985 }
986 });
987 } else if args.contains(&PostgresTypeAttribute::PgVarlenaInOutFuncs) {
988 stream.extend(quote! {
990 #[doc(hidden)]
991 #[::pgrx::pgrx_macros::pg_extern(immutable,parallel_safe)]
992 pub fn #funcname_in #generics(input: Option<&::core::ffi::CStr>) -> Option<::pgrx::datum::PgVarlena<#name #generics>> {
993 input.map_or_else(|| {
994 if let Some(m) = <#name as ::pgrx::inoutfuncs::PgVarlenaInOutFuncs>::NULL_ERROR_MESSAGE {
995 ::pgrx::pg_sys::error!("{m}");
996 }
997 None
998 }, |i| Some(<#name as ::pgrx::inoutfuncs::PgVarlenaInOutFuncs>::input(i)))
999 }
1000
1001 #[doc(hidden)]
1002 #[::pgrx::pgrx_macros::pg_extern(immutable,parallel_safe)]
1003 pub fn #funcname_out #generics(input: ::pgrx::datum::PgVarlena<#name #generics>) -> ::pgrx::ffi::CString {
1004 let mut buffer = ::pgrx::stringinfo::StringInfo::new();
1005 ::pgrx::inoutfuncs::PgVarlenaInOutFuncs::output(&*input, &mut buffer);
1006 unsafe { buffer.leak_cstr().to_owned() }
1008 }
1009 });
1010 }
1011
1012 if args.contains(&PostgresTypeAttribute::PgBinaryProtocol) {
1013 stream.extend(quote! {
1016 #[doc(hidden)]
1017 #[::pgrx::pgrx_macros::pg_extern(immutable, strict, parallel_safe)]
1018 pub fn #funcname_recv #generics(
1019 mut internal: ::pgrx::datum::Internal,
1020 ) -> #name #generics {
1021 let buf = unsafe { internal.get_mut::<::pgrx::pg_sys::StringInfoData>().unwrap() };
1022
1023 let mut serialized = ::pgrx::StringInfo::new();
1024
1025 serialized.push_bytes(&[0u8; ::pgrx::pg_sys::VARHDRSZ]); serialized.push_bytes(unsafe {
1027 core::slice::from_raw_parts(
1028 buf.data as *const u8,
1029 buf.len as usize
1030 )
1031 });
1032
1033 let size = serialized.len();
1034 let varlena = serialized.into_char_ptr();
1035
1036 unsafe{
1037 ::pgrx::set_varsize_4b(varlena as *mut ::pgrx::pg_sys::varlena, size as i32);
1038 buf.cursor = buf.len;
1039 ::pgrx::datum::cbor_decode(varlena as *mut ::pgrx::pg_sys::varlena)
1040 }
1041 }
1042 #[doc(hidden)]
1043 #[::pgrx::pgrx_macros::pg_extern(immutable, strict, parallel_safe)]
1044 pub fn #funcname_send #generics(input: #name #generics) -> Vec<u8> {
1045 use ::pgrx::datum::{FromDatum, IntoDatum};
1046 let Some(datum): Option<::pgrx::pg_sys::Datum> = input.into_datum() else {
1047 ::pgrx::error!("Datum of type `{}` is unexpectedly NULL.", stringify!(#name));
1048 };
1049 unsafe {
1050 let Some(serialized): Option<Vec<u8>> = FromDatum::from_datum(datum, false) else {
1051 ::pgrx::error!("Failed to CBOR-serialize Datum to type `{}`.", stringify!(#name));
1052 };
1053 serialized
1054 }
1055 }
1056 });
1057 }
1058
1059 let sql_graph_entity_item = sql_gen::PostgresTypeDerive::from_derive_input(
1060 ast,
1061 args.contains(&PostgresTypeAttribute::PgBinaryProtocol),
1062 )?;
1063 sql_graph_entity_item.to_tokens(&mut stream);
1064
1065 Ok(stream)
1066}
1067
1068#[proc_macro_derive(PostgresGucEnum, attributes(name, hidden))]
1070pub fn postgres_guc_enum(input: TokenStream) -> TokenStream {
1071 let ast = parse_macro_input!(input as syn::DeriveInput);
1072
1073 impl_guc_enum(ast).unwrap_or_else(|e| e.into_compile_error()).into()
1074}
1075
1076fn impl_guc_enum(ast: DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
1077 use std::str::FromStr;
1078 use syn::parse::Parse;
1079
1080 enum GucEnumAttribute {
1081 Name(CString),
1082 Hidden(bool),
1083 }
1084
1085 impl GucEnumAttribute {
1086 fn is_guc_enum_attribute(attribute: &str) -> bool {
1087 matches!(attribute, "name" | "hidden")
1088 }
1089 }
1090
1091 impl Parse for GucEnumAttribute {
1092 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
1093 let ident: Ident = input.parse()?;
1094 let _: syn::token::Eq = input.parse()?;
1095 match ident.to_string().as_str() {
1096 "name" => input.parse::<syn::LitCStr>().map(|val| Self::Name(val.value())),
1097 "hidden" => input.parse::<syn::LitBool>().map(|val| Self::Hidden(val.value())),
1098 x => Err(syn::Error::new(input.span(), format!("unknown attribute {x}"))),
1099 }
1100 }
1101 }
1102
1103 let Data::Enum(data) = ast.data.clone() else {
1105 return Err(syn::Error::new(
1106 ast.span(),
1107 "#[derive(PostgresGucEnum)] can only be applied to enums",
1108 ));
1109 };
1110 let ident = ast.ident.clone();
1111 let mut config = Vec::new();
1112 for (index, variant) in data.variants.iter().enumerate() {
1113 let default_name = CString::from_str(&variant.ident.to_string())
1114 .expect("the identifier contains a null character.");
1115 let default_val = index as i32;
1116 let default_hidden = false;
1117 let mut name = None;
1118 let mut hidden = None;
1119 for attr in variant.attrs.iter() {
1120 if let Some(ident) = attr.path().get_ident() {
1121 if GucEnumAttribute::is_guc_enum_attribute(&ident.to_string()) {
1122 let pair: GucEnumAttribute = syn::parse2(attr.meta.to_token_stream())?;
1123 match pair {
1124 GucEnumAttribute::Name(value) => {
1125 if name.replace(value).is_some() {
1126 return Err(syn::Error::new(
1127 ast.span(),
1128 "too many #[name] attributes",
1129 ));
1130 }
1131 }
1132 GucEnumAttribute::Hidden(value) => {
1133 if hidden.replace(value).is_some() {
1134 return Err(syn::Error::new(
1135 ast.span(),
1136 "too many #[hidden] attributes",
1137 ));
1138 }
1139 }
1140 }
1141 }
1142 }
1143 }
1144 let ident = variant.ident.clone();
1145 let name = name.unwrap_or(default_name);
1146 let val = default_val;
1147 let hidden = hidden.unwrap_or(default_hidden);
1148 config.push((ident, name, val, hidden));
1149 }
1150 let config_idents = config.iter().map(|x| &x.0).collect::<Vec<_>>();
1151 let config_names = config.iter().map(|x| &x.1).collect::<Vec<_>>();
1152 let config_vals = config.iter().map(|x| &x.2).collect::<Vec<_>>();
1153 let config_hiddens = config.iter().map(|x| &x.3).collect::<Vec<_>>();
1154
1155 Ok(quote! {
1156 unsafe impl ::pgrx::guc::GucEnum for #ident {
1157 fn from_ordinal(ordinal: i32) -> Self {
1158 match ordinal {
1159 #(#config_vals => Self::#config_idents,)*
1160 _ => panic!("Unrecognized ordinal"),
1161 }
1162 }
1163
1164 fn to_ordinal(&self) -> i32 {
1165 match self {
1166 #(Self::#config_idents => #config_vals,)*
1167 }
1168 }
1169
1170 const CONFIG_ENUM_ENTRY: *const ::pgrx::pg_sys::config_enum_entry = [
1171 #(
1172 ::pgrx::pg_sys::config_enum_entry {
1173 name: #config_names.as_ptr(),
1174 val: #config_vals,
1175 hidden: #config_hiddens,
1176 },
1177 )*
1178 ::pgrx::pg_sys::config_enum_entry {
1179 name: core::ptr::null(),
1180 val: 0,
1181 hidden: false,
1182 },
1183 ].as_ptr();
1184 }
1185 })
1186}
1187
1188#[derive(Debug, Hash, Ord, PartialOrd, Eq, PartialEq)]
1189enum PostgresTypeAttribute {
1190 InOutFuncs,
1191 PgBinaryProtocol,
1192 PgVarlenaInOutFuncs,
1193 Default,
1194 ManualFromIntoDatum,
1195}
1196
1197fn parse_postgres_type_args(attributes: &[Attribute]) -> HashSet<PostgresTypeAttribute> {
1198 let mut categorized_attributes = HashSet::new();
1199
1200 for a in attributes {
1201 let path = &a.path();
1202 let path = quote! {#path}.to_string();
1203 match path.as_str() {
1204 "inoutfuncs" => {
1205 categorized_attributes.insert(PostgresTypeAttribute::InOutFuncs);
1206 }
1207 "pg_binary_protocol" => {
1208 categorized_attributes.insert(PostgresTypeAttribute::PgBinaryProtocol);
1209 }
1210 "pgvarlena_inoutfuncs" => {
1211 categorized_attributes.insert(PostgresTypeAttribute::PgVarlenaInOutFuncs);
1212 }
1213 "bikeshed_postgres_type_manually_impl_from_into_datum" => {
1214 categorized_attributes.insert(PostgresTypeAttribute::ManualFromIntoDatum);
1215 }
1216 _ => {
1217 }
1219 };
1220 }
1221
1222 categorized_attributes
1223}
1224
1225#[proc_macro_derive(PostgresEq, attributes(pgrx))]
1247pub fn derive_postgres_eq(input: TokenStream) -> TokenStream {
1248 let ast = parse_macro_input!(input as syn::DeriveInput);
1249 deriving_postgres_eq(ast).unwrap_or_else(syn::Error::into_compile_error).into()
1250}
1251
1252#[proc_macro_derive(PostgresOrd, attributes(pgrx))]
1277pub fn derive_postgres_ord(input: TokenStream) -> TokenStream {
1278 let ast = parse_macro_input!(input as syn::DeriveInput);
1279 deriving_postgres_ord(ast).unwrap_or_else(syn::Error::into_compile_error).into()
1280}
1281
1282#[proc_macro_derive(PostgresHash, attributes(pgrx))]
1304pub fn derive_postgres_hash(input: TokenStream) -> TokenStream {
1305 let ast = parse_macro_input!(input as syn::DeriveInput);
1306 deriving_postgres_hash(ast).unwrap_or_else(syn::Error::into_compile_error).into()
1307}
1308
1309#[proc_macro_derive(AggregateName, attributes(aggregate_name))]
1311pub fn derive_aggregate_name(input: TokenStream) -> TokenStream {
1312 let ast = parse_macro_input!(input as syn::DeriveInput);
1313
1314 impl_aggregate_name(ast).unwrap_or_else(|e| e.into_compile_error()).into()
1315}
1316
1317fn impl_aggregate_name(ast: DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
1318 let name = &ast.ident;
1319
1320 let mut custom_name_value: Option<String> = None;
1321
1322 for attr in &ast.attrs {
1323 if attr.path().is_ident("aggregate_name") {
1324 let meta = &attr.meta;
1325 match meta {
1326 syn::Meta::NameValue(syn::MetaNameValue {
1327 value: syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Str(s), .. }),
1328 ..
1329 }) => {
1330 custom_name_value = Some(s.value());
1331 break;
1332 }
1333 _ => {
1334 return Err(syn::Error::new_spanned(
1335 attr,
1336 "#[aggregate_name] must be in the form `#[aggregate_name = \"string_literal\"]`",
1337 ));
1338 }
1339 }
1340 }
1341 }
1342
1343 let name_str = custom_name_value.unwrap_or(name.to_string());
1344
1345 let expanded = quote! {
1346 impl ::pgrx::aggregate::ToAggregateName for #name {
1347 const NAME: &'static str = #name_str;
1348 }
1349 };
1350
1351 Ok(expanded)
1352}
1353
1354#[proc_macro_attribute]
1360pub fn pg_aggregate(_attr: TokenStream, item: TokenStream) -> TokenStream {
1361 fn wrapped(item_impl: ItemImpl) -> Result<TokenStream, syn::Error> {
1363 let sql_graph_entity_item = PgAggregate::new(item_impl)?;
1364
1365 Ok(sql_graph_entity_item.to_token_stream().into())
1366 }
1367
1368 let parsed_base = parse_macro_input!(item as syn::ItemImpl);
1369 wrapped(parsed_base).unwrap_or_else(|e| e.into_compile_error().into())
1370}
1371
1372#[proc_macro_attribute]
1393pub fn pgrx(_attr: TokenStream, item: TokenStream) -> TokenStream {
1394 item
1395}
1396
1397#[proc_macro_attribute]
1404pub fn pg_trigger(attrs: TokenStream, input: TokenStream) -> TokenStream {
1405 fn wrapped(attrs: TokenStream, input: TokenStream) -> Result<TokenStream, syn::Error> {
1406 use pgrx_sql_entity_graph::{PgTrigger, PgTriggerAttribute};
1407 use syn::parse::Parser;
1408 use syn::punctuated::Punctuated;
1409 use syn::Token;
1410
1411 let attributes =
1412 Punctuated::<PgTriggerAttribute, Token![,]>::parse_terminated.parse(attrs)?;
1413 let item_fn: syn::ItemFn = syn::parse(input)?;
1414 let trigger_item = PgTrigger::new(item_fn, attributes)?;
1415 let trigger_tokens = trigger_item.to_token_stream();
1416
1417 Ok(trigger_tokens.into())
1418 }
1419
1420 wrapped(attrs, input).unwrap_or_else(|e| e.into_compile_error().into())
1421}