pgrx/datum/
with_typeid.rs

1use crate::datum::{Array, FromDatum, PgVarlena, VariadicArray};
2use crate::PgBox;
3use core::any::TypeId;
4use once_cell::sync::Lazy;
5use pgrx_sql_entity_graph::RustSqlMapping;
6
7/// Obtain a TypeId for T without `T: 'static`
8#[inline]
9#[doc(hidden)]
10pub fn nonstatic_typeid<T: ?Sized>() -> core::any::TypeId {
11    trait NonStaticAny {
12        fn type_id(&self) -> core::any::TypeId
13        where
14            Self: 'static;
15    }
16    impl<T: ?Sized> NonStaticAny for core::marker::PhantomData<T> {
17        #[inline]
18        fn type_id(&self) -> core::any::TypeId
19        where
20            Self: 'static,
21        {
22            core::any::TypeId::of::<T>()
23        }
24    }
25    let it = core::marker::PhantomData::<T>;
26    // There is no excuse for the crimes we have done here, but what jury would convict us?
27    unsafe { core::mem::transmute::<&dyn NonStaticAny, &'static dyn NonStaticAny>(&it).type_id() }
28}
29
30/// A type which can have its [`core::any::TypeId`]s registered for Rust to SQL mapping.
31///
32/// An example use of this trait:
33///
34/// ```rust
35/// use pgrx::prelude::*;
36/// use serde::{Serialize, Deserialize};
37///
38/// #[derive(Debug, Clone, Copy, Serialize, Deserialize, PostgresType)]
39/// struct Treat<'a> { best_part: &'a str, };
40///
41/// let mut mappings = Default::default();
42/// let treat_string = stringify!(Treat).to_string();
43/// <Treat<'_> as pgrx::datum::WithTypeIds>::register_with_refs(&mut mappings, treat_string.clone());
44///
45/// # assert!(mappings.iter().any(|x| x.id == ::pgrx::datum::nonstatic_typeid::<Treat<'static>>()));
46/// ```
47///
48/// This trait uses the fact that inherent implementations are a higher priority than trait
49/// implementations.
50pub trait WithTypeIds {
51    const ITEM_ID: Lazy<TypeId>;
52    const OPTION_ID: Lazy<Option<TypeId>>;
53    const VEC_ID: Lazy<Option<TypeId>>;
54    const VEC_OPTION_ID: Lazy<Option<TypeId>>;
55    const OPTION_VEC_ID: Lazy<Option<TypeId>>;
56    const OPTION_VEC_OPTION_ID: Lazy<Option<TypeId>>;
57    const ARRAY_ID: Lazy<Option<TypeId>>;
58    const OPTION_ARRAY_ID: Lazy<Option<TypeId>>;
59    const VARIADICARRAY_ID: Lazy<Option<TypeId>>;
60    const OPTION_VARIADICARRAY_ID: Lazy<Option<TypeId>>;
61    const VARLENA_ID: Lazy<Option<TypeId>>;
62    const OPTION_VARLENA_ID: Lazy<Option<TypeId>>;
63
64    fn register_with_refs(map: &mut std::collections::HashSet<RustSqlMapping>, single_sql: String) {
65        Self::register(map, single_sql.clone());
66        <&Self as WithTypeIds>::register(map, single_sql.clone());
67        <&mut Self as WithTypeIds>::register(map, single_sql);
68    }
69
70    fn register_sized_with_refs(
71        _map: &mut std::collections::HashSet<RustSqlMapping>,
72        _single_sql: String,
73    ) {
74        ()
75    }
76
77    fn register_sized(_map: &mut std::collections::HashSet<RustSqlMapping>, _single_sql: String) {
78        ()
79    }
80
81    fn register_varlena_with_refs(
82        _map: &mut std::collections::HashSet<RustSqlMapping>,
83        _single_sql: String,
84    ) {
85        ()
86    }
87
88    fn register_varlena(_map: &mut std::collections::HashSet<RustSqlMapping>, _single_sql: String) {
89        ()
90    }
91
92    fn register_array_with_refs(
93        _map: &mut std::collections::HashSet<RustSqlMapping>,
94        _single_sql: String,
95    ) {
96        ()
97    }
98
99    fn register_array(_map: &mut std::collections::HashSet<RustSqlMapping>, _single_sql: String) {
100        ()
101    }
102
103    fn register(set: &mut std::collections::HashSet<RustSqlMapping>, single_sql: String) {
104        let rust = core::any::type_name::<Self>();
105        assert!(
106            set.insert(RustSqlMapping {
107                sql: single_sql.clone(),
108                rust: rust.to_string(),
109                id: *Self::ITEM_ID,
110            }),
111            "Cannot set mapping of `{rust}` twice, was already `{single_sql}`.",
112        );
113    }
114}
115
116impl<T: ?Sized> WithTypeIds for T {
117    const ITEM_ID: Lazy<TypeId> = Lazy::new(|| nonstatic_typeid::<T>());
118    const OPTION_ID: Lazy<Option<TypeId>> = Lazy::new(|| None);
119    const VEC_ID: Lazy<Option<TypeId>> = Lazy::new(|| None);
120    const VEC_OPTION_ID: Lazy<Option<TypeId>> = Lazy::new(|| None);
121    const OPTION_VEC_ID: Lazy<Option<TypeId>> = Lazy::new(|| None);
122    const OPTION_VEC_OPTION_ID: Lazy<Option<TypeId>> = Lazy::new(|| None);
123    const ARRAY_ID: Lazy<Option<TypeId>> = Lazy::new(|| None);
124    const OPTION_ARRAY_ID: Lazy<Option<TypeId>> = Lazy::new(|| None);
125    const VARIADICARRAY_ID: Lazy<Option<TypeId>> = Lazy::new(|| None);
126    const OPTION_VARIADICARRAY_ID: Lazy<Option<TypeId>> = Lazy::new(|| None);
127    const VARLENA_ID: Lazy<Option<TypeId>> = Lazy::new(|| None);
128    const OPTION_VARLENA_ID: Lazy<Option<TypeId>> = Lazy::new(|| None);
129}
130
131/// A type which can have its [`core::any::TypeId`]s registered for Rust to SQL mapping.
132///
133/// An example use of this trait:
134///
135/// ```rust
136/// use pgrx::prelude::*;
137/// use serde::{Serialize, Deserialize};
138///
139/// #[derive(Debug, Clone, Copy, Serialize, Deserialize, PostgresType)]
140/// pub struct Treat<'a> { best_part: &'a str, };
141///
142/// let mut mappings = Default::default();
143/// let treat_string = stringify!(Treat).to_string();
144///
145/// pgrx::datum::WithSizedTypeIds::<Treat<'static>>::register_sized_with_refs(
146///     &mut mappings,
147///     treat_string.clone()
148/// );
149///
150/// assert!(mappings.iter().any(|x| x.id == core::any::TypeId::of::<Option<Treat<'static>>>()));
151/// ```
152///
153/// This trait uses the fact that inherent implementations are a higher priority than trait
154/// implementations.
155pub struct WithSizedTypeIds<T>(pub core::marker::PhantomData<T>);
156
157impl<T> WithSizedTypeIds<T> {
158    pub const PG_BOX_ID: Lazy<Option<TypeId>> = Lazy::new(|| Some(nonstatic_typeid::<PgBox<T>>()));
159    pub const PG_BOX_OPTION_ID: Lazy<Option<TypeId>> =
160        Lazy::new(|| Some(nonstatic_typeid::<PgBox<Option<T>>>()));
161    pub const PG_BOX_VEC_ID: Lazy<Option<TypeId>> =
162        Lazy::new(|| Some(nonstatic_typeid::<PgBox<Vec<T>>>()));
163    pub const OPTION_ID: Lazy<Option<TypeId>> = Lazy::new(|| Some(nonstatic_typeid::<Option<T>>()));
164    pub const VEC_ID: Lazy<Option<TypeId>> = Lazy::new(|| Some(nonstatic_typeid::<Vec<T>>()));
165    pub const VEC_OPTION_ID: Lazy<Option<TypeId>> =
166        Lazy::new(|| Some(nonstatic_typeid::<Vec<Option<T>>>()));
167    pub const OPTION_VEC_ID: Lazy<Option<TypeId>> =
168        Lazy::new(|| Some(nonstatic_typeid::<Option<Vec<T>>>()));
169    pub const OPTION_VEC_OPTION_ID: Lazy<Option<TypeId>> =
170        Lazy::new(|| Some(nonstatic_typeid::<Option<Vec<Option<T>>>>()));
171
172    pub fn register_sized_with_refs(
173        map: &mut std::collections::HashSet<RustSqlMapping>,
174        single_sql: String,
175    ) where
176        Self: 'static,
177    {
178        WithSizedTypeIds::<T>::register_sized(map, single_sql.clone());
179        WithSizedTypeIds::<&T>::register_sized(map, single_sql.clone());
180        WithSizedTypeIds::<&mut T>::register_sized(map, single_sql);
181    }
182
183    pub fn register_sized(map: &mut std::collections::HashSet<RustSqlMapping>, single_sql: String) {
184        let set_sql = format!("{single_sql}[]");
185
186        if let Some(id) = *WithSizedTypeIds::<T>::PG_BOX_ID {
187            let rust = core::any::type_name::<crate::PgBox<T>>().to_string();
188            assert!(
189                map.insert(RustSqlMapping { sql: single_sql.clone(), rust: rust.to_string(), id }),
190                "Cannot map `{rust}` twice.",
191            );
192        }
193
194        if let Some(id) = *WithSizedTypeIds::<T>::PG_BOX_OPTION_ID {
195            let rust = core::any::type_name::<crate::PgBox<Option<T>>>().to_string();
196            assert!(
197                map.insert(RustSqlMapping { sql: single_sql.clone(), rust: rust.to_string(), id }),
198                "Cannot map `{rust}` twice.",
199            );
200        }
201
202        if let Some(id) = *WithSizedTypeIds::<T>::PG_BOX_VEC_ID {
203            let rust = core::any::type_name::<crate::PgBox<Vec<T>>>().to_string();
204            assert!(
205                map.insert(RustSqlMapping { sql: set_sql.clone(), rust: rust.to_string(), id }),
206                "Cannot map `{rust}` twice.",
207            );
208        }
209
210        if let Some(id) = *WithSizedTypeIds::<T>::OPTION_ID {
211            let rust = core::any::type_name::<Option<T>>().to_string();
212            assert!(
213                map.insert(RustSqlMapping { sql: single_sql.clone(), rust: rust.to_string(), id }),
214                "Cannot map `{rust}` twice.",
215            );
216        }
217
218        if let Some(id) = *WithSizedTypeIds::<T>::VEC_ID {
219            let rust = core::any::type_name::<T>().to_string();
220            assert!(
221                map.insert(RustSqlMapping { sql: set_sql.clone(), rust: rust.to_string(), id }),
222                "Cannot map `{rust}` twice.",
223            );
224        }
225        if let Some(id) = *WithSizedTypeIds::<T>::VEC_OPTION_ID {
226            let rust = core::any::type_name::<Vec<Option<T>>>();
227            assert!(
228                map.insert(RustSqlMapping { sql: set_sql.clone(), rust: rust.to_string(), id }),
229                "Cannot map `{rust}` twice.",
230            );
231        }
232        if let Some(id) = *WithSizedTypeIds::<T>::OPTION_VEC_ID {
233            let rust = core::any::type_name::<Option<Vec<T>>>();
234            assert!(
235                map.insert(RustSqlMapping { sql: set_sql.clone(), rust: rust.to_string(), id }),
236                "Cannot map `{rust}` twice.",
237            );
238        }
239        if let Some(id) = *WithSizedTypeIds::<T>::OPTION_VEC_OPTION_ID {
240            let rust = core::any::type_name::<Option<Vec<Option<T>>>>();
241            assert!(
242                map.insert(RustSqlMapping { sql: set_sql.clone(), rust: rust.to_string(), id }),
243                "Cannot map `{rust}` twice.",
244            );
245        }
246    }
247}
248
249/// An [`Array`] compatible type which can have its [`core::any::TypeId`]s registered for Rust to SQL mapping.
250///
251/// An example use of this trait:
252///
253/// ```rust
254/// use pgrx::prelude::*;
255/// use serde::{Serialize, Deserialize};
256///
257/// #[derive(Debug, Clone, Serialize, Deserialize, PostgresType)]
258/// pub struct Treat { best_part: String, };
259///
260/// let mut mappings = Default::default();
261/// let treat_string = stringify!(Treat).to_string();
262///
263/// pgrx::datum::WithArrayTypeIds::<Treat>::register_array_with_refs(
264///     &mut mappings,
265///     treat_string.clone()
266/// );
267///
268/// # assert!(mappings.iter().any(|x| x.id == ::pgrx::datum::nonstatic_typeid::<Array<Treat>>()));
269/// ```
270///
271/// This trait uses the fact that inherent implementations are a higher priority than trait
272/// implementations.
273pub struct WithArrayTypeIds<T>(pub core::marker::PhantomData<T>);
274
275impl<T: FromDatum + 'static> WithArrayTypeIds<T> {
276    pub const ARRAY_ID: Lazy<Option<TypeId>> = Lazy::new(|| Some(nonstatic_typeid::<Array<T>>()));
277    pub const OPTION_ARRAY_ID: Lazy<Option<TypeId>> =
278        Lazy::new(|| Some(nonstatic_typeid::<Option<Array<T>>>()));
279    pub const VARIADICARRAY_ID: Lazy<Option<TypeId>> =
280        Lazy::new(|| Some(nonstatic_typeid::<VariadicArray<T>>()));
281    pub const OPTION_VARIADICARRAY_ID: Lazy<Option<TypeId>> =
282        Lazy::new(|| Some(nonstatic_typeid::<Option<VariadicArray<T>>>()));
283
284    pub fn register_array_with_refs(
285        map: &mut std::collections::HashSet<RustSqlMapping>,
286        single_sql: String,
287    ) where
288        Self: 'static,
289    {
290        WithArrayTypeIds::<T>::register_array(map, single_sql.clone());
291        WithArrayTypeIds::<&T>::register_array(map, single_sql.clone());
292        WithArrayTypeIds::<&mut T>::register_array(map, single_sql);
293    }
294
295    pub fn register_array(map: &mut std::collections::HashSet<RustSqlMapping>, single_sql: String) {
296        let set_sql = format!("{single_sql}[]");
297
298        if let Some(id) = *WithArrayTypeIds::<T>::ARRAY_ID {
299            let rust = core::any::type_name::<Array<T>>().to_string();
300            assert!(
301                map.insert(RustSqlMapping { sql: set_sql.clone(), rust: rust.to_string(), id }),
302                "Cannot map `{rust}` twice.",
303            );
304        }
305        if let Some(id) = *WithArrayTypeIds::<T>::OPTION_ARRAY_ID {
306            let rust = core::any::type_name::<Option<Array<T>>>().to_string();
307            assert!(
308                map.insert(RustSqlMapping { sql: set_sql.clone(), rust: rust.to_string(), id }),
309                "Cannot map `{rust}` twice.",
310            );
311        }
312
313        if let Some(id) = *WithArrayTypeIds::<T>::VARIADICARRAY_ID {
314            let rust = core::any::type_name::<VariadicArray<T>>().to_string();
315            assert!(
316                map.insert(RustSqlMapping { sql: set_sql.clone(), rust: rust.to_string(), id }),
317                "Cannot map `{rust}` twice.",
318            );
319        }
320        if let Some(id) = *WithArrayTypeIds::<T>::OPTION_VARIADICARRAY_ID {
321            let rust = core::any::type_name::<Option<VariadicArray<T>>>().to_string();
322            assert!(
323                map.insert(RustSqlMapping { sql: set_sql.clone(), rust: rust.to_string(), id }),
324                "Cannot map `{rust}` twice.",
325            );
326        }
327    }
328}
329
330/// A [`PgVarlena`] compatible type which can have its [`core::any::TypeId`]s registered for Rust to SQL mapping.
331///
332/// An example use of this trait:
333///
334/// ```rust
335/// use pgrx::prelude::*;
336/// use serde::{Serialize, Deserialize};
337///
338/// #[derive(Debug, Clone, Copy, Serialize, Deserialize, PostgresType)]
339/// pub struct Treat<'a> { best_part: &'a str, };
340///
341/// let mut mappings = Default::default();
342/// let treat_string = stringify!(Treat).to_string();
343///
344/// pgrx::datum::WithVarlenaTypeIds::<Treat<'static>>::register_varlena_with_refs(
345///     &mut mappings,
346///     treat_string.clone()
347/// );
348///
349/// # assert!(mappings.iter().any(|x| x.id == ::pgrx::datum::nonstatic_typeid::<PgVarlena<Treat<'_>>>()));
350/// ```
351///
352/// This trait uses the fact that inherent implementations are a higher priority than trait
353/// implementations.
354pub struct WithVarlenaTypeIds<T>(pub core::marker::PhantomData<T>);
355
356impl<T: Copy + 'static> WithVarlenaTypeIds<T> {
357    pub const VARLENA_ID: Lazy<Option<TypeId>> =
358        Lazy::new(|| Some(nonstatic_typeid::<PgVarlena<T>>()));
359    pub const PG_BOX_VARLENA_ID: Lazy<Option<TypeId>> =
360        Lazy::new(|| Some(nonstatic_typeid::<PgBox<PgVarlena<T>>>()));
361    pub const OPTION_VARLENA_ID: Lazy<Option<TypeId>> =
362        Lazy::new(|| Some(nonstatic_typeid::<Option<PgVarlena<T>>>()));
363
364    pub fn register_varlena_with_refs(
365        map: &mut std::collections::HashSet<RustSqlMapping>,
366        single_sql: String,
367    ) where
368        Self: 'static,
369    {
370        WithVarlenaTypeIds::<T>::register_varlena(map, single_sql.clone());
371        WithVarlenaTypeIds::<&T>::register_varlena(map, single_sql.clone());
372        WithVarlenaTypeIds::<&mut T>::register_varlena(map, single_sql);
373    }
374
375    pub fn register_varlena(
376        map: &mut std::collections::HashSet<RustSqlMapping>,
377        single_sql: String,
378    ) {
379        if let Some(id) = *WithVarlenaTypeIds::<T>::VARLENA_ID {
380            let rust = core::any::type_name::<PgVarlena<T>>();
381            assert!(
382                map.insert(RustSqlMapping { sql: single_sql.clone(), rust: rust.to_string(), id }),
383                "Cannot map `{rust}` twice.",
384            );
385        }
386
387        if let Some(id) = *WithVarlenaTypeIds::<T>::PG_BOX_VARLENA_ID {
388            let rust = core::any::type_name::<PgBox<PgVarlena<T>>>().to_string();
389            assert!(
390                map.insert(RustSqlMapping { sql: single_sql.clone(), rust: rust.to_string(), id }),
391                "Cannot map `{rust}` twice.",
392            );
393        }
394        if let Some(id) = *WithVarlenaTypeIds::<T>::OPTION_VARLENA_ID {
395            let rust = core::any::type_name::<Option<PgVarlena<T>>>().to_string();
396            assert!(
397                map.insert(RustSqlMapping { sql: single_sql.clone(), rust: rust.to_string(), id }),
398                "Cannot map `{rust}` twice.",
399            );
400        }
401    }
402}