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]
623#[track_caller]
624pub fn pg_extern(attr: TokenStream, item: TokenStream) -> TokenStream {
625 fn wrapped(attr: TokenStream, item: TokenStream) -> Result<TokenStream, syn::Error> {
626 let pg_extern_item = PgExtern::new(attr.into(), item.into())?;
627 Ok(pg_extern_item.to_token_stream().into())
628 }
629
630 wrapped(attr, item).unwrap_or_else(|e: syn::Error| e.into_compile_error().into())
631}
632
633#[proc_macro_derive(PostgresEnum, attributes(requires, pgrx))]
649pub fn postgres_enum(input: TokenStream) -> TokenStream {
650 let ast = parse_macro_input!(input as syn::DeriveInput);
651
652 impl_postgres_enum(ast).unwrap_or_else(|e| e.into_compile_error()).into()
653}
654
655fn impl_postgres_enum(ast: DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
656 let mut stream = proc_macro2::TokenStream::new();
657 let sql_graph_entity_ast = ast.clone();
658 let generics = &ast.generics.clone();
659 let enum_ident = &ast.ident;
660 let enum_name = enum_ident.to_string();
661
662 let Data::Enum(enum_data) = ast.data else {
664 return Err(syn::Error::new(
665 ast.span(),
666 "#[derive(PostgresEnum)] can only be applied to enums",
667 ));
668 };
669
670 let mut from_datum = proc_macro2::TokenStream::new();
671 let mut into_datum = proc_macro2::TokenStream::new();
672
673 for d in enum_data.variants.clone() {
674 let label_ident = &d.ident;
675 let label_string = label_ident.to_string();
676
677 from_datum.extend(quote! { #label_string => Some(#enum_ident::#label_ident), });
678 into_datum.extend(quote! { #enum_ident::#label_ident => Some(::pgrx::enum_helper::lookup_enum_by_label(#enum_name, #label_string)), });
679 }
680
681 let fcx_lt = syn::Lifetime::new("'fcx", proc_macro2::Span::mixed_site());
683 let mut generics_with_fcx = generics.clone();
684 generics_with_fcx.make_where_clause().predicates.push(syn::WherePredicate::Type(
686 syn::PredicateType {
687 lifetimes: None,
688 bounded_ty: syn::parse_quote! { Self },
689 colon_token: syn::Token),
690 bounds: syn::parse_quote! { #fcx_lt },
691 },
692 ));
693 let (impl_gens, ty_gens, where_clause) = generics_with_fcx.split_for_impl();
694 let mut impl_gens: syn::Generics = syn::parse_quote! { #impl_gens };
695 impl_gens
696 .params
697 .insert(0, syn::GenericParam::Lifetime(syn::LifetimeParam::new(fcx_lt.clone())));
698
699 stream.extend(quote! {
700 impl ::pgrx::datum::FromDatum for #enum_ident {
701 #[inline]
702 unsafe fn from_polymorphic_datum(datum: ::pgrx::pg_sys::Datum, is_null: bool, _typeoid: ::pgrx::pg_sys::Oid) -> Option<#enum_ident> {
703 if is_null {
704 None
705 } else {
706 let (name, _, _) = ::pgrx::enum_helper::lookup_enum_by_oid(unsafe { ::pgrx::pg_sys::Oid::from_datum(datum, is_null)? } );
708 match name.as_str() {
709 #from_datum
710 _ => panic!("invalid enum value: {name}")
711 }
712 }
713 }
714 }
715
716 unsafe impl #impl_gens ::pgrx::callconv::ArgAbi<#fcx_lt> for #enum_ident #ty_gens #where_clause {
717 unsafe fn unbox_arg_unchecked(arg: ::pgrx::callconv::Arg<'_, #fcx_lt>) -> Self {
718 let index = arg.index();
719 unsafe { arg.unbox_arg_using_from_datum().unwrap_or_else(|| panic!("argument {index} must not be null")) }
720 }
721
722 }
723
724 unsafe impl #generics ::pgrx::datum::UnboxDatum for #enum_ident #generics {
725 type As<'dat> = #enum_ident #generics where Self: 'dat;
726 #[inline]
727 unsafe fn unbox<'dat>(d: ::pgrx::datum::Datum<'dat>) -> Self::As<'dat> where Self: 'dat {
728 <Self as ::pgrx::datum::FromDatum>::from_datum(::core::mem::transmute(d), false).unwrap()
729 }
730 }
731
732 impl ::pgrx::datum::IntoDatum for #enum_ident {
733 #[inline]
734 fn into_datum(self) -> Option<::pgrx::pg_sys::Datum> {
735 match self {
736 #into_datum
737 }
738 }
739
740 fn type_oid() -> ::pgrx::pg_sys::Oid {
741 ::pgrx::wrappers::regtypein(#enum_name)
742 }
743
744 }
745
746 unsafe impl ::pgrx::callconv::BoxRet for #enum_ident {
747 unsafe fn box_into<'fcx>(self, fcinfo: &mut ::pgrx::callconv::FcInfo<'fcx>) -> ::pgrx::datum::Datum<'fcx> {
748 match ::pgrx::datum::IntoDatum::into_datum(self) {
749 None => fcinfo.return_null(),
750 Some(datum) => unsafe { fcinfo.return_raw_datum(datum) },
751 }
752 }
753 }
754 });
755
756 let sql_graph_entity_item = PostgresEnum::from_derive_input(sql_graph_entity_ast)?;
757 sql_graph_entity_item.to_tokens(&mut stream);
758
759 Ok(stream)
760}
761
762#[proc_macro_derive(
790 PostgresType,
791 attributes(
792 inoutfuncs,
793 pgvarlena_inoutfuncs,
794 pg_binary_protocol,
795 bikeshed_postgres_type_manually_impl_from_into_datum,
796 requires,
797 pgrx
798 )
799)]
800pub fn postgres_type(input: TokenStream) -> TokenStream {
801 let ast = parse_macro_input!(input as syn::DeriveInput);
802
803 impl_postgres_type(ast).unwrap_or_else(|e| e.into_compile_error()).into()
804}
805
806fn impl_postgres_type(ast: DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
807 let name = &ast.ident;
808 let generics = &ast.generics.clone();
809 let has_lifetimes = generics.lifetimes().next();
810 let funcname_in = Ident::new(&format!("{name}_in").to_lowercase(), name.span());
811 let funcname_out = Ident::new(&format!("{name}_out").to_lowercase(), name.span());
812 let funcname_recv = Ident::new(&format!("{name}_recv").to_lowercase(), name.span());
813 let funcname_send = Ident::new(&format!("{name}_send").to_lowercase(), name.span());
814
815 let mut args = parse_postgres_type_args(&ast.attrs);
816 let mut stream = proc_macro2::TokenStream::new();
817
818 match ast.data {
820 Data::Struct(_) => { }
821 Data::Enum(_) => {
822 }
826 _ => {
827 return Err(syn::Error::new(
828 ast.span(),
829 "#[derive(PostgresType)] can only be applied to structs or enums",
830 ))
831 }
832 }
833
834 if !args.contains(&PostgresTypeAttribute::InOutFuncs)
835 && !args.contains(&PostgresTypeAttribute::PgVarlenaInOutFuncs)
836 {
837 args.insert(PostgresTypeAttribute::Default);
839 }
840
841 let lifetime = match has_lifetimes {
842 Some(lifetime) => quote! {#lifetime},
843 None => quote! {'_},
844 };
845
846 let fcx_lt = syn::Lifetime::new("'fcx", proc_macro2::Span::mixed_site());
848 let mut generics_with_fcx = generics.clone();
849 generics_with_fcx.make_where_clause().predicates.push(syn::WherePredicate::Type(
851 syn::PredicateType {
852 lifetimes: None,
853 bounded_ty: syn::parse_quote! { Self },
854 colon_token: syn::Token),
855 bounds: syn::parse_quote! { #fcx_lt },
856 },
857 ));
858 let (impl_gens, ty_gens, where_clause) = generics_with_fcx.split_for_impl();
859 let mut impl_gens: syn::Generics = syn::parse_quote! { #impl_gens };
860 impl_gens
861 .params
862 .insert(0, syn::GenericParam::Lifetime(syn::LifetimeParam::new(fcx_lt.clone())));
863
864 stream.extend(quote! {
867 impl #generics ::pgrx::datum::PostgresType for #name #generics { }
868 });
869
870 if !args.contains(&PostgresTypeAttribute::ManualFromIntoDatum) {
871 stream.extend(
872 quote! {
873 impl #generics ::pgrx::datum::IntoDatum for #name #generics {
874 fn into_datum(self) -> Option<::pgrx::pg_sys::Datum> {
875 #[allow(deprecated)]
876 Some(unsafe { ::pgrx::datum::cbor_encode(&self) }.into())
877 }
878
879 fn type_oid() -> ::pgrx::pg_sys::Oid {
880 ::pgrx::wrappers::rust_regtypein::<Self>()
881 }
882 }
883
884 unsafe impl #generics ::pgrx::callconv::BoxRet for #name #generics {
885 unsafe fn box_into<'fcx>(self, fcinfo: &mut ::pgrx::callconv::FcInfo<'fcx>) -> ::pgrx::datum::Datum<'fcx> {
886 match ::pgrx::datum::IntoDatum::into_datum(self) {
887 None => fcinfo.return_null(),
888 Some(datum) => unsafe { fcinfo.return_raw_datum(datum) },
889 }
890 }
891 }
892
893 impl #generics ::pgrx::datum::FromDatum for #name #generics {
894 unsafe fn from_polymorphic_datum(
895 datum: ::pgrx::pg_sys::Datum,
896 is_null: bool,
897 _typoid: ::pgrx::pg_sys::Oid,
898 ) -> Option<Self> {
899 if is_null {
900 None
901 } else {
902 #[allow(deprecated)]
903 ::pgrx::datum::cbor_decode(datum.cast_mut_ptr())
904 }
905 }
906
907 unsafe fn from_datum_in_memory_context(
908 mut memory_context: ::pgrx::memcxt::PgMemoryContexts,
909 datum: ::pgrx::pg_sys::Datum,
910 is_null: bool,
911 _typoid: ::pgrx::pg_sys::Oid,
912 ) -> Option<Self> {
913 if is_null {
914 None
915 } else {
916 memory_context.switch_to(|_| {
917 let varlena = ::pgrx::pg_sys::pg_detoast_datum_copy(datum.cast_mut_ptr());
919 <Self as ::pgrx::datum::FromDatum>::from_datum(varlena.into(), is_null)
920 })
921 }
922 }
923 }
924
925 unsafe impl #generics ::pgrx::datum::UnboxDatum for #name #generics {
926 type As<'dat> = Self where Self: 'dat;
927 unsafe fn unbox<'dat>(datum: ::pgrx::datum::Datum<'dat>) -> Self::As<'dat> where Self: 'dat {
928 <Self as ::pgrx::datum::FromDatum>::from_datum(::core::mem::transmute(datum), false).unwrap()
929 }
930 }
931
932 unsafe impl #impl_gens ::pgrx::callconv::ArgAbi<#fcx_lt> for #name #ty_gens #where_clause
933 {
934 unsafe fn unbox_arg_unchecked(arg: ::pgrx::callconv::Arg<'_, #fcx_lt>) -> Self {
935 let index = arg.index();
936 unsafe { arg.unbox_arg_using_from_datum().unwrap_or_else(|| panic!("argument {index} must not be null")) }
937 }
938 }
939 }
940 )
941 }
942
943 if args.contains(&PostgresTypeAttribute::Default) {
946 stream.extend(quote! {
947 #[doc(hidden)]
948 #[::pgrx::pgrx_macros::pg_extern(immutable, parallel_safe)]
949 pub fn #funcname_in #generics(input: Option<&#lifetime ::core::ffi::CStr>) -> Option<#name #generics> {
950 use ::pgrx::inoutfuncs::json_from_slice;
951 input.map(|cstr| json_from_slice(cstr.to_bytes()).ok()).flatten()
952 }
953
954 #[doc(hidden)]
955 #[::pgrx::pgrx_macros::pg_extern (immutable, parallel_safe)]
956 pub fn #funcname_out #generics(input: #name #generics) -> ::pgrx::ffi::CString {
957 use ::pgrx::inoutfuncs::json_to_vec;
958 let mut bytes = json_to_vec(&input).unwrap();
959 bytes.push(0); ::pgrx::ffi::CString::from_vec_with_nul(bytes).unwrap()
961 }
962 });
963 } else if args.contains(&PostgresTypeAttribute::InOutFuncs) {
964 stream.extend(quote! {
966 #[doc(hidden)]
967 #[::pgrx::pgrx_macros::pg_extern(immutable,parallel_safe)]
968 pub fn #funcname_in #generics(input: Option<&::core::ffi::CStr>) -> Option<#name #generics> {
969 input.map_or_else(|| {
970 if let Some(m) = <#name as ::pgrx::inoutfuncs::InOutFuncs>::NULL_ERROR_MESSAGE {
971 ::pgrx::pg_sys::error!("{m}");
972 }
973 None
974 }, |i| Some(<#name as ::pgrx::inoutfuncs::InOutFuncs>::input(i)))
975 }
976
977 #[doc(hidden)]
978 #[::pgrx::pgrx_macros::pg_extern(immutable,parallel_safe)]
979 pub fn #funcname_out #generics(input: #name #generics) -> ::pgrx::ffi::CString {
980 let mut buffer = ::pgrx::stringinfo::StringInfo::new();
981 ::pgrx::inoutfuncs::InOutFuncs::output(&input, &mut buffer);
982 unsafe { buffer.leak_cstr().to_owned() }
984 }
985 });
986 } else if args.contains(&PostgresTypeAttribute::PgVarlenaInOutFuncs) {
987 stream.extend(quote! {
989 #[doc(hidden)]
990 #[::pgrx::pgrx_macros::pg_extern(immutable,parallel_safe)]
991 pub fn #funcname_in #generics(input: Option<&::core::ffi::CStr>) -> Option<::pgrx::datum::PgVarlena<#name #generics>> {
992 input.map_or_else(|| {
993 if let Some(m) = <#name as ::pgrx::inoutfuncs::PgVarlenaInOutFuncs>::NULL_ERROR_MESSAGE {
994 ::pgrx::pg_sys::error!("{m}");
995 }
996 None
997 }, |i| Some(<#name as ::pgrx::inoutfuncs::PgVarlenaInOutFuncs>::input(i)))
998 }
999
1000 #[doc(hidden)]
1001 #[::pgrx::pgrx_macros::pg_extern(immutable,parallel_safe)]
1002 pub fn #funcname_out #generics(input: ::pgrx::datum::PgVarlena<#name #generics>) -> ::pgrx::ffi::CString {
1003 let mut buffer = ::pgrx::stringinfo::StringInfo::new();
1004 ::pgrx::inoutfuncs::PgVarlenaInOutFuncs::output(&*input, &mut buffer);
1005 unsafe { buffer.leak_cstr().to_owned() }
1007 }
1008 });
1009 }
1010
1011 if args.contains(&PostgresTypeAttribute::PgBinaryProtocol) {
1012 stream.extend(quote! {
1015 #[doc(hidden)]
1016 #[::pgrx::pgrx_macros::pg_extern(immutable, strict, parallel_safe)]
1017 pub fn #funcname_recv #generics(
1018 mut internal: ::pgrx::datum::Internal,
1019 ) -> #name #generics {
1020 let buf = unsafe { internal.get_mut::<::pgrx::pg_sys::StringInfoData>().unwrap() };
1021
1022 let mut serialized = ::pgrx::StringInfo::new();
1023
1024 serialized.push_bytes(&[0u8; ::pgrx::pg_sys::VARHDRSZ]); serialized.push_bytes(unsafe {
1026 core::slice::from_raw_parts(
1027 buf.data as *const u8,
1028 buf.len as usize
1029 )
1030 });
1031
1032 let size = serialized.len();
1033 let varlena = serialized.into_char_ptr();
1034
1035 unsafe{
1036 ::pgrx::set_varsize_4b(varlena as *mut ::pgrx::pg_sys::varlena, size as i32);
1037 buf.cursor = buf.len;
1038 ::pgrx::datum::cbor_decode(varlena as *mut ::pgrx::pg_sys::varlena)
1039 }
1040 }
1041 #[doc(hidden)]
1042 #[::pgrx::pgrx_macros::pg_extern(immutable, strict, parallel_safe)]
1043 pub fn #funcname_send #generics(input: #name #generics) -> Vec<u8> {
1044 use ::pgrx::datum::{FromDatum, IntoDatum};
1045 let Some(datum): Option<::pgrx::pg_sys::Datum> = input.into_datum() else {
1046 ::pgrx::error!("Datum of type `{}` is unexpectedly NULL.", stringify!(#name));
1047 };
1048 unsafe {
1049 let Some(serialized): Option<Vec<u8>> = FromDatum::from_datum(datum, false) else {
1050 ::pgrx::error!("Failed to CBOR-serialize Datum to type `{}`.", stringify!(#name));
1051 };
1052 serialized
1053 }
1054 }
1055 });
1056 }
1057
1058 let sql_graph_entity_item = sql_gen::PostgresTypeDerive::from_derive_input(
1059 ast,
1060 args.contains(&PostgresTypeAttribute::PgBinaryProtocol),
1061 )?;
1062 sql_graph_entity_item.to_tokens(&mut stream);
1063
1064 Ok(stream)
1065}
1066
1067#[proc_macro_derive(PostgresGucEnum, attributes(name, hidden))]
1069pub fn postgres_guc_enum(input: TokenStream) -> TokenStream {
1070 let ast = parse_macro_input!(input as syn::DeriveInput);
1071
1072 impl_guc_enum(ast).unwrap_or_else(|e| e.into_compile_error()).into()
1073}
1074
1075fn impl_guc_enum(ast: DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
1076 use std::str::FromStr;
1077 use syn::parse::Parse;
1078
1079 enum GucEnumAttribute {
1080 Name(CString),
1081 Hidden(bool),
1082 }
1083
1084 impl Parse for GucEnumAttribute {
1085 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
1086 let ident: Ident = input.parse()?;
1087 let _: syn::token::Eq = input.parse()?;
1088 match ident.to_string().as_str() {
1089 "name" => input.parse::<syn::LitCStr>().map(|val| Self::Name(val.value())),
1090 "hidden" => input.parse::<syn::LitBool>().map(|val| Self::Hidden(val.value())),
1091 x => Err(syn::Error::new(input.span(), format!("unknown attribute {x}"))),
1092 }
1093 }
1094 }
1095
1096 let Data::Enum(data) = ast.data.clone() else {
1098 return Err(syn::Error::new(
1099 ast.span(),
1100 "#[derive(PostgresGucEnum)] can only be applied to enums",
1101 ));
1102 };
1103 let ident = ast.ident.clone();
1104 let mut config = Vec::new();
1105 for (index, variant) in data.variants.iter().enumerate() {
1106 let default_name = CString::from_str(&variant.ident.to_string())
1107 .expect("the identifier contains a null character.");
1108 let default_val = index as i32;
1109 let default_hidden = false;
1110 let mut name = None;
1111 let mut hidden = None;
1112 for attr in variant.attrs.iter() {
1113 let tokens = attr.meta.require_name_value()?.to_token_stream();
1114 let pair: GucEnumAttribute = syn::parse2(tokens)?;
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(ast.span(), "too many #[hidden] attributes"));
1124 }
1125 }
1126 }
1127 }
1128 let ident = variant.ident.clone();
1129 let name = name.unwrap_or(default_name);
1130 let val = default_val;
1131 let hidden = hidden.unwrap_or(default_hidden);
1132 config.push((ident, name, val, hidden));
1133 }
1134 let config_idents = config.iter().map(|x| &x.0).collect::<Vec<_>>();
1135 let config_names = config.iter().map(|x| &x.1).collect::<Vec<_>>();
1136 let config_vals = config.iter().map(|x| &x.2).collect::<Vec<_>>();
1137 let config_hiddens = config.iter().map(|x| &x.3).collect::<Vec<_>>();
1138
1139 Ok(quote! {
1140 unsafe impl ::pgrx::guc::GucEnum for #ident {
1141 fn from_ordinal(ordinal: i32) -> Self {
1142 match ordinal {
1143 #(#config_vals => Self::#config_idents,)*
1144 _ => panic!("Unrecognized ordinal"),
1145 }
1146 }
1147
1148 fn to_ordinal(&self) -> i32 {
1149 match self {
1150 #(Self::#config_idents => #config_vals,)*
1151 }
1152 }
1153
1154 const CONFIG_ENUM_ENTRY: *const ::pgrx::pg_sys::config_enum_entry = [
1155 #(
1156 ::pgrx::pg_sys::config_enum_entry {
1157 name: #config_names.as_ptr(),
1158 val: #config_vals,
1159 hidden: #config_hiddens,
1160 },
1161 )*
1162 ::pgrx::pg_sys::config_enum_entry {
1163 name: core::ptr::null(),
1164 val: 0,
1165 hidden: false,
1166 },
1167 ].as_ptr();
1168 }
1169 })
1170}
1171
1172#[derive(Debug, Hash, Ord, PartialOrd, Eq, PartialEq)]
1173enum PostgresTypeAttribute {
1174 InOutFuncs,
1175 PgBinaryProtocol,
1176 PgVarlenaInOutFuncs,
1177 Default,
1178 ManualFromIntoDatum,
1179}
1180
1181fn parse_postgres_type_args(attributes: &[Attribute]) -> HashSet<PostgresTypeAttribute> {
1182 let mut categorized_attributes = HashSet::new();
1183
1184 for a in attributes {
1185 let path = &a.path();
1186 let path = quote! {#path}.to_string();
1187 match path.as_str() {
1188 "inoutfuncs" => {
1189 categorized_attributes.insert(PostgresTypeAttribute::InOutFuncs);
1190 }
1191 "pg_binary_protocol" => {
1192 categorized_attributes.insert(PostgresTypeAttribute::PgBinaryProtocol);
1193 }
1194 "pgvarlena_inoutfuncs" => {
1195 categorized_attributes.insert(PostgresTypeAttribute::PgVarlenaInOutFuncs);
1196 }
1197 "bikeshed_postgres_type_manually_impl_from_into_datum" => {
1198 categorized_attributes.insert(PostgresTypeAttribute::ManualFromIntoDatum);
1199 }
1200 _ => {
1201 }
1203 };
1204 }
1205
1206 categorized_attributes
1207}
1208
1209#[proc_macro_derive(PostgresEq, attributes(pgrx))]
1231pub fn derive_postgres_eq(input: TokenStream) -> TokenStream {
1232 let ast = parse_macro_input!(input as syn::DeriveInput);
1233 deriving_postgres_eq(ast).unwrap_or_else(syn::Error::into_compile_error).into()
1234}
1235
1236#[proc_macro_derive(PostgresOrd, attributes(pgrx))]
1261pub fn derive_postgres_ord(input: TokenStream) -> TokenStream {
1262 let ast = parse_macro_input!(input as syn::DeriveInput);
1263 deriving_postgres_ord(ast).unwrap_or_else(syn::Error::into_compile_error).into()
1264}
1265
1266#[proc_macro_derive(PostgresHash, attributes(pgrx))]
1288pub fn derive_postgres_hash(input: TokenStream) -> TokenStream {
1289 let ast = parse_macro_input!(input as syn::DeriveInput);
1290 deriving_postgres_hash(ast).unwrap_or_else(syn::Error::into_compile_error).into()
1291}
1292
1293#[proc_macro_derive(AggregateName, attributes(aggregate_name))]
1295pub fn derive_aggregate_name(input: TokenStream) -> TokenStream {
1296 let ast = parse_macro_input!(input as syn::DeriveInput);
1297
1298 impl_aggregate_name(ast).unwrap_or_else(|e| e.into_compile_error()).into()
1299}
1300
1301fn impl_aggregate_name(ast: DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
1302 let name = &ast.ident;
1303
1304 let mut custom_name_value: Option<String> = None;
1305
1306 for attr in &ast.attrs {
1307 if attr.path().is_ident("aggregate_name") {
1308 let meta = &attr.meta;
1309 match meta {
1310 syn::Meta::NameValue(syn::MetaNameValue {
1311 value: syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Str(s), .. }),
1312 ..
1313 }) => {
1314 custom_name_value = Some(s.value());
1315 break;
1316 }
1317 _ => {
1318 return Err(syn::Error::new_spanned(
1319 attr,
1320 "#[aggregate_name] must be in the form `#[aggregate_name = \"string_literal\"]`",
1321 ));
1322 }
1323 }
1324 }
1325 }
1326
1327 let name_str = custom_name_value.unwrap_or(name.to_string());
1328
1329 let expanded = quote! {
1330 impl ::pgrx::aggregate::ToAggregateName for #name {
1331 const NAME: &'static str = #name_str;
1332 }
1333 };
1334
1335 Ok(expanded)
1336}
1337
1338#[proc_macro_attribute]
1344pub fn pg_aggregate(_attr: TokenStream, item: TokenStream) -> TokenStream {
1345 fn wrapped(item_impl: ItemImpl) -> Result<TokenStream, syn::Error> {
1347 let sql_graph_entity_item = PgAggregate::new(item_impl)?;
1348
1349 Ok(sql_graph_entity_item.to_token_stream().into())
1350 }
1351
1352 let parsed_base = parse_macro_input!(item as syn::ItemImpl);
1353 wrapped(parsed_base).unwrap_or_else(|e| e.into_compile_error().into())
1354}
1355
1356#[proc_macro_attribute]
1377pub fn pgrx(_attr: TokenStream, item: TokenStream) -> TokenStream {
1378 item
1379}
1380
1381#[proc_macro_attribute]
1388pub fn pg_trigger(attrs: TokenStream, input: TokenStream) -> TokenStream {
1389 fn wrapped(attrs: TokenStream, input: TokenStream) -> Result<TokenStream, syn::Error> {
1390 use pgrx_sql_entity_graph::{PgTrigger, PgTriggerAttribute};
1391 use syn::parse::Parser;
1392 use syn::punctuated::Punctuated;
1393 use syn::Token;
1394
1395 let attributes =
1396 Punctuated::<PgTriggerAttribute, Token![,]>::parse_terminated.parse(attrs)?;
1397 let item_fn: syn::ItemFn = syn::parse(input)?;
1398 let trigger_item = PgTrigger::new(item_fn, attributes)?;
1399 let trigger_tokens = trigger_item.to_token_stream();
1400
1401 Ok(trigger_tokens.into())
1402 }
1403
1404 wrapped(attrs, input).unwrap_or_else(|e| e.into_compile_error().into())
1405}