pgrx_macros/
operators.rs

1//LICENSE Portions Copyright 2019-2021 ZomboDB, LLC.
2//LICENSE
3//LICENSE Portions Copyright 2021-2023 Technology Concepts & Design, Inc.
4//LICENSE
5//LICENSE Portions Copyright 2023-2023 PgCentral Foundation, Inc. <contact@pgcentral.org>
6//LICENSE
7//LICENSE All rights reserved.
8//LICENSE
9//LICENSE Use of this source code is governed by the MIT license that can be found in the LICENSE file.
10use pgrx_sql_entity_graph::{PostgresHash, PostgresOrd};
11
12use crate::{parse_postgres_type_args, PostgresTypeAttribute};
13use proc_macro2::Ident;
14use quote::{quote, ToTokens};
15use syn::DeriveInput;
16
17#[track_caller]
18fn ident_and_path(ast: &DeriveInput) -> (&Ident, proc_macro2::TokenStream) {
19    let ident = &ast.ident;
20    let args = parse_postgres_type_args(&ast.attrs);
21    let path = if args.contains(&PostgresTypeAttribute::PgVarlenaInOutFuncs) {
22        quote! { ::pgrx::datum::PgVarlena<#ident> }
23    } else {
24        quote! { #ident }
25    };
26    (ident, path)
27}
28
29#[track_caller]
30pub(crate) fn deriving_postgres_eq(ast: DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
31    let mut stream = proc_macro2::TokenStream::new();
32    let (ident, path) = ident_and_path(&ast);
33    stream.extend(derive_pg_eq(ident, &path));
34    stream.extend(derive_pg_ne(ident, &path));
35
36    Ok(stream)
37}
38
39#[track_caller]
40pub(crate) fn deriving_postgres_ord(ast: DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
41    let mut stream = proc_macro2::TokenStream::new();
42    let (ident, path) = ident_and_path(&ast);
43
44    stream.extend(derive_pg_lt(ident, &path));
45    stream.extend(derive_pg_gt(ident, &path));
46    stream.extend(derive_pg_le(ident, &path));
47    stream.extend(derive_pg_ge(ident, &path));
48    stream.extend(derive_pg_cmp(ident, &path));
49
50    let sql_graph_entity_item = PostgresOrd::from_derive_input(ast)?;
51    sql_graph_entity_item.to_tokens(&mut stream);
52
53    Ok(stream)
54}
55
56pub(crate) fn deriving_postgres_hash(ast: DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
57    let mut stream = proc_macro2::TokenStream::new();
58    let (ident, path) = ident_and_path(&ast);
59
60    stream.extend(derive_pg_hash(ident, &path));
61
62    let sql_graph_entity_item = PostgresHash::from_derive_input(ast)?;
63    sql_graph_entity_item.to_tokens(&mut stream);
64
65    Ok(stream)
66}
67
68/// Derive a Postgres `=` operator from Rust `==`
69///
70/// Note this expansion applies a number of assumptions that may not be true:
71/// - PartialEq::eq is referentially transparent (immutable and parallel-safe)
72/// - PartialEq::ne must reverse PartialEq::eq (negator)
73/// - PartialEq::eq is commutative
74///
75/// Postgres swears that these are just ["optimization hints"], and they can be
76/// defined to use regular SQL or PL/pgSQL functions with spurious results.
77///
78/// However, it is entirely plausible these assumptions actually are venomous.
79/// It is deeply unlikely that we can audit the millions of lines of C code in
80/// Postgres to confirm that it avoids using these assumptions in a way that
81/// would lead to UB or unacceptable behavior from PGRX if Eq is incorrectly
82/// implemented, and we have no realistic means of guaranteeing this.
83///
84/// Further, Postgres adds a disclaimer to these "optimization hints":
85///
86/// > But if you provide them, you must be sure that they are right!
87/// > Incorrect use of an optimization clause can result in
88/// > slow queries, subtly wrong output, or other Bad Things.
89///
90/// In practice, most Eq impls are in fact correct, referentially transparent,
91/// and commutative. So this note could be for nothing. This signpost is left
92/// in order to guide anyone unfortunate enough to be debugging an issue that
93/// finally leads them here.
94///
95/// ["optimization hints"]: https://www.postgresql.org/docs/current/xoper-optimization.html
96#[track_caller]
97pub fn derive_pg_eq(name: &Ident, path: &proc_macro2::TokenStream) -> proc_macro2::TokenStream {
98    let pg_name = Ident::new(&format!("{name}_eq").to_lowercase(), name.span());
99    quote! {
100        #[allow(non_snake_case)]
101        #[::pgrx::pgrx_macros::pg_operator(immutable, parallel_safe)]
102        #[::pgrx::pgrx_macros::opname(=)]
103        #[::pgrx::pgrx_macros::commutator(=)]
104        #[::pgrx::pgrx_macros::negator(<>)]
105        #[::pgrx::pgrx_macros::restrict(eqsel)]
106        #[::pgrx::pgrx_macros::join(eqjoinsel)]
107        #[::pgrx::pgrx_macros::merges]
108        #[::pgrx::pgrx_macros::hashes]
109        fn #pg_name(left: #path, right: #path) -> bool
110        where
111            #path: ::core::cmp::Eq,
112        {
113            left == right
114        }
115    }
116}
117
118/// Derive a Postgres `<>` operator from Rust `!=`
119///
120/// Note that this expansion applies a number of assumptions that aren't necessarily true:
121/// - PartialEq::ne is referentially transparent (immutable and parallel-safe)
122/// - PartialEq::eq must reverse PartialEq::ne (negator)
123/// - PartialEq::ne is commutative
124///
125/// See `derive_pg_eq` for the implications of this assumption.
126#[track_caller]
127pub fn derive_pg_ne(name: &Ident, path: &proc_macro2::TokenStream) -> proc_macro2::TokenStream {
128    let pg_name = Ident::new(&format!("{name}_ne").to_lowercase(), name.span());
129    quote! {
130        #[allow(non_snake_case)]
131        #[::pgrx::pgrx_macros::pg_operator(immutable, parallel_safe)]
132        #[::pgrx::pgrx_macros::opname(<>)]
133        #[::pgrx::pgrx_macros::commutator(<>)]
134        #[::pgrx::pgrx_macros::negator(=)]
135        #[::pgrx::pgrx_macros::restrict(neqsel)]
136        #[::pgrx::pgrx_macros::join(neqjoinsel)]
137        fn #pg_name(left: #path, right: #path) -> bool {
138            left != right
139        }
140    }
141}
142
143#[track_caller]
144pub fn derive_pg_lt(name: &Ident, path: &proc_macro2::TokenStream) -> proc_macro2::TokenStream {
145    let pg_name = Ident::new(&format!("{name}_lt").to_lowercase(), name.span());
146    quote! {
147        #[allow(non_snake_case)]
148        #[::pgrx::pgrx_macros::pg_operator(immutable, parallel_safe)]
149        #[::pgrx::pgrx_macros::opname(<)]
150        #[::pgrx::pgrx_macros::negator(>=)]
151        #[::pgrx::pgrx_macros::commutator(>)]
152        #[::pgrx::pgrx_macros::restrict(scalarltsel)]
153        #[::pgrx::pgrx_macros::join(scalarltjoinsel)]
154        fn #pg_name(left: #path, right: #path) -> bool {
155            left < right
156        }
157
158    }
159}
160
161#[track_caller]
162pub fn derive_pg_gt(name: &Ident, path: &proc_macro2::TokenStream) -> proc_macro2::TokenStream {
163    let pg_name = Ident::new(&format!("{name}_gt").to_lowercase(), name.span());
164    quote! {
165        #[allow(non_snake_case)]
166        #[::pgrx::pgrx_macros::pg_operator(immutable, parallel_safe)]
167        #[::pgrx::pgrx_macros::opname(>)]
168        #[::pgrx::pgrx_macros::negator(<=)]
169        #[::pgrx::pgrx_macros::commutator(<)]
170        #[::pgrx::pgrx_macros::restrict(scalargtsel)]
171        #[::pgrx::pgrx_macros::join(scalargtjoinsel)]
172        fn #pg_name(left: #path, right: #path) -> bool {
173            left > right
174        }
175    }
176}
177
178#[track_caller]
179pub fn derive_pg_le(name: &Ident, path: &proc_macro2::TokenStream) -> proc_macro2::TokenStream {
180    let pg_name = Ident::new(&format!("{name}_le").to_lowercase(), name.span());
181    quote! {
182        #[allow(non_snake_case)]
183        #[::pgrx::pgrx_macros::pg_operator(immutable, parallel_safe)]
184        #[::pgrx::pgrx_macros::opname(<=)]
185        #[::pgrx::pgrx_macros::negator(>)]
186        #[::pgrx::pgrx_macros::commutator(>=)]
187        #[::pgrx::pgrx_macros::restrict(scalarlesel)]
188        #[::pgrx::pgrx_macros::join(scalarlejoinsel)]
189        fn #pg_name(left: #path, right: #path) -> bool {
190            left <= right
191        }
192    }
193}
194
195#[track_caller]
196pub fn derive_pg_ge(name: &Ident, path: &proc_macro2::TokenStream) -> proc_macro2::TokenStream {
197    let pg_name = Ident::new(&format!("{name}_ge").to_lowercase(), name.span());
198    quote! {
199        #[allow(non_snake_case)]
200        #[::pgrx::pgrx_macros::pg_operator(immutable, parallel_safe)]
201        #[::pgrx::pgrx_macros::opname(>=)]
202        #[::pgrx::pgrx_macros::negator(<)]
203        #[::pgrx::pgrx_macros::commutator(<=)]
204        #[::pgrx::pgrx_macros::restrict(scalargesel)]
205        #[::pgrx::pgrx_macros::join(scalargejoinsel)]
206        fn #pg_name(left: #path, right: #path) -> bool {
207            left >= right
208        }
209    }
210}
211
212#[track_caller]
213pub fn derive_pg_cmp(name: &Ident, path: &proc_macro2::TokenStream) -> proc_macro2::TokenStream {
214    let pg_name = Ident::new(&format!("{name}_cmp").to_lowercase(), name.span());
215    quote! {
216        #[allow(non_snake_case)]
217        #[::pgrx::pgrx_macros::pg_extern(immutable, parallel_safe)]
218        fn #pg_name(left: #path, right: #path) -> i32 {
219            ::core::cmp::Ord::cmp(&left, &right) as i32
220        }
221    }
222}
223
224/// Derive a Postgres hash operator using a provided hash function
225///
226/// # HashEq?
227///
228/// To quote the std documentation:
229///
230/// "When implementing both Hash and Eq, it is important that the following property holds:
231/// ```text
232/// k1 == k2 -> hash(k1) == hash(k2)
233/// ```
234/// In other words, if two keys are equal, their hashes must also be equal. HashMap and HashSet both rely on this behavior."
235///
236/// Postgres is no different: this hashing is for the explicit purpose of equality checks,
237/// and it also needs to be able to reason from hash equality to actual equality.
238#[track_caller]
239pub fn derive_pg_hash(name: &Ident, path: &proc_macro2::TokenStream) -> proc_macro2::TokenStream {
240    let pg_name = Ident::new(&format!("{name}_hash").to_lowercase(), name.span());
241    quote! {
242        #[allow(non_snake_case)]
243        #[::pgrx::pgrx_macros::pg_extern(immutable, parallel_safe)]
244        fn #pg_name(value: #path) -> i32
245        where
246            #path: ::core::hash::Hash + ::core::cmp::Eq,
247        {
248            ::pgrx::misc::pgrx_seahash(&value) as i32
249        }
250    }
251}