pgx_utils/sql_entity_graph/metadata/
sql_translatable.rs

1/*!
2
3A trait denoting a type can possibly be mapped to an SQL type
4
5> Like all of the [`sql_entity_graph`][crate::sql_entity_graph] APIs, this is considered **internal**
6to the `pgx` framework and very subject to change between versions. While you may use this, please do it with caution.
7
8*/
9use std::error::Error;
10
11use super::return_variant::ReturnsError;
12use super::{FunctionMetadataTypeEntity, Returns};
13
14#[derive(Clone, Copy, Debug, Hash, Ord, PartialOrd, PartialEq, Eq)]
15pub enum ArgumentError {
16    SetOf,
17    Table,
18    BareU8,
19    SkipInArray,
20    Datum,
21}
22
23impl std::fmt::Display for ArgumentError {
24    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25        match self {
26            ArgumentError::SetOf => {
27                write!(f, "Cannot use SetOfIterator as an argument")
28            }
29            ArgumentError::Table => {
30                write!(f, "Cannot use TableIterator as an argument")
31            }
32            ArgumentError::BareU8 => {
33                write!(f, "Cannot use bare u8")
34            }
35            ArgumentError::SkipInArray => {
36                write!(f, "SqlMapping::Skip inside Array is not valid")
37            }
38            ArgumentError::Datum => {
39                write!(f, "A Datum as an argument means that `sql = \"...\"` must be set in the declaration")
40            }
41        }
42    }
43}
44
45/// Describes ways that Rust types are mapped into SQL
46#[derive(Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)]
47pub enum SqlMapping {
48    /// Explicit mappings provided by PGX
49    As(String),
50    Composite {
51        array_brackets: bool,
52    },
53    /// Some types are still directly from source
54    Source {
55        array_brackets: bool,
56    },
57    /// Placeholder for some types with no simple translation
58    Skip,
59}
60
61impl SqlMapping {
62    pub fn literal(s: &'static str) -> SqlMapping {
63        SqlMapping::As(String::from(s))
64    }
65}
66
67impl Error for ArgumentError {}
68
69/**
70A value which can be represented in SQL
71
72# Safety
73
74By implementing this, you assert you are not lying to either Postgres or Rust in doing so.
75This trait asserts a safe translation exists between values of this type from Rust to SQL,
76or from SQL into Rust. If you are mistaken about how this works, either the Postgres C API
77or the Rust handling in PGX may emit undefined behavior.
78
79It cannot be made private or sealed due to details of the structure of the PGX framework.
80Nonetheless, if you are not confident the translation is valid: do not implement this trait.
81*/
82pub unsafe trait SqlTranslatable {
83    fn type_name() -> &'static str {
84        core::any::type_name::<Self>()
85    }
86    fn argument_sql() -> Result<SqlMapping, ArgumentError>;
87    fn return_sql() -> Result<Returns, ReturnsError>;
88    fn variadic() -> bool {
89        false
90    }
91    fn optional() -> bool {
92        false
93    }
94    fn entity() -> FunctionMetadataTypeEntity {
95        FunctionMetadataTypeEntity {
96            type_name: Self::type_name(),
97            argument_sql: Self::argument_sql(),
98            return_sql: Self::return_sql(),
99            variadic: Self::variadic(),
100            optional: Self::optional(),
101        }
102    }
103}
104
105unsafe impl<T> SqlTranslatable for Option<T>
106where
107    T: SqlTranslatable,
108{
109    fn argument_sql() -> Result<SqlMapping, ArgumentError> {
110        T::argument_sql()
111    }
112    fn return_sql() -> Result<Returns, ReturnsError> {
113        T::return_sql()
114    }
115    fn optional() -> bool {
116        true
117    }
118}
119
120unsafe impl<T> SqlTranslatable for *mut T
121where
122    T: SqlTranslatable,
123{
124    fn argument_sql() -> Result<SqlMapping, ArgumentError> {
125        T::argument_sql()
126    }
127    fn return_sql() -> Result<Returns, ReturnsError> {
128        T::return_sql()
129    }
130    fn optional() -> bool {
131        T::optional()
132    }
133}
134
135unsafe impl<T, E> SqlTranslatable for Result<T, E>
136where
137    T: SqlTranslatable,
138    E: std::error::Error + 'static,
139{
140    fn argument_sql() -> Result<SqlMapping, ArgumentError> {
141        T::argument_sql()
142    }
143    fn return_sql() -> Result<Returns, ReturnsError> {
144        T::return_sql()
145    }
146    fn optional() -> bool {
147        true
148    }
149}
150
151unsafe impl<T> SqlTranslatable for Vec<T>
152where
153    T: SqlTranslatable,
154{
155    fn argument_sql() -> Result<SqlMapping, ArgumentError> {
156        match T::type_name() {
157            id if id == u8::type_name() => Ok(SqlMapping::As(format!("bytea"))),
158            _ => match T::argument_sql() {
159                Ok(SqlMapping::As(val)) => Ok(SqlMapping::As(format!("{val}[]"))),
160                Ok(SqlMapping::Composite { array_brackets: _ }) => {
161                    Ok(SqlMapping::Composite { array_brackets: true })
162                }
163                Ok(SqlMapping::Source { array_brackets: _ }) => {
164                    Ok(SqlMapping::Source { array_brackets: true })
165                }
166                Ok(SqlMapping::Skip) => Ok(SqlMapping::Skip),
167                err @ Err(_) => err,
168            },
169        }
170    }
171    fn return_sql() -> Result<Returns, ReturnsError> {
172        match T::type_name() {
173            id if id == u8::type_name() => Ok(Returns::One(SqlMapping::As(format!("bytea")))),
174            _ => match T::return_sql() {
175                Ok(Returns::One(SqlMapping::As(val))) => {
176                    Ok(Returns::One(SqlMapping::As(format!("{val}[]"))))
177                }
178                Ok(Returns::One(SqlMapping::Composite { array_brackets: _ })) => {
179                    Ok(Returns::One(SqlMapping::Composite { array_brackets: true }))
180                }
181                Ok(Returns::One(SqlMapping::Source { array_brackets: _ })) => {
182                    Ok(Returns::One(SqlMapping::Source { array_brackets: true }))
183                }
184                Ok(Returns::One(SqlMapping::Skip)) => Ok(Returns::One(SqlMapping::Skip)),
185                Ok(Returns::SetOf(_)) => Err(ReturnsError::SetOfInArray),
186                Ok(Returns::Table(_)) => Err(ReturnsError::TableInArray),
187                err @ Err(_) => err,
188            },
189        }
190    }
191    fn optional() -> bool {
192        T::optional()
193    }
194}
195
196unsafe impl SqlTranslatable for u8 {
197    fn argument_sql() -> Result<SqlMapping, ArgumentError> {
198        Err(ArgumentError::BareU8)
199    }
200    fn return_sql() -> Result<Returns, ReturnsError> {
201        Err(ReturnsError::BareU8)
202    }
203}
204
205unsafe impl SqlTranslatable for i32 {
206    fn argument_sql() -> Result<SqlMapping, ArgumentError> {
207        Ok(SqlMapping::literal("INT"))
208    }
209    fn return_sql() -> Result<Returns, ReturnsError> {
210        Ok(Returns::One(SqlMapping::literal("INT")))
211    }
212}
213
214unsafe impl SqlTranslatable for u32 {
215    fn argument_sql() -> Result<SqlMapping, ArgumentError> {
216        Ok(SqlMapping::Source { array_brackets: false })
217    }
218    fn return_sql() -> Result<Returns, ReturnsError> {
219        Ok(Returns::One(SqlMapping::Source { array_brackets: false }))
220    }
221}
222
223unsafe impl SqlTranslatable for String {
224    fn argument_sql() -> Result<SqlMapping, ArgumentError> {
225        Ok(SqlMapping::literal("TEXT"))
226    }
227    fn return_sql() -> Result<Returns, ReturnsError> {
228        Ok(Returns::One(SqlMapping::literal("TEXT")))
229    }
230}
231
232unsafe impl<T> SqlTranslatable for &T
233where
234    T: SqlTranslatable,
235{
236    fn argument_sql() -> Result<SqlMapping, ArgumentError> {
237        T::argument_sql()
238    }
239    fn return_sql() -> Result<Returns, ReturnsError> {
240        T::return_sql()
241    }
242}
243
244unsafe impl<'a> SqlTranslatable for &'a str {
245    fn argument_sql() -> Result<SqlMapping, ArgumentError> {
246        Ok(SqlMapping::literal("TEXT"))
247    }
248    fn return_sql() -> Result<Returns, ReturnsError> {
249        Ok(Returns::One(SqlMapping::literal("TEXT")))
250    }
251}
252
253unsafe impl<'a> SqlTranslatable for &'a [u8] {
254    fn argument_sql() -> Result<SqlMapping, ArgumentError> {
255        Ok(SqlMapping::literal("bytea"))
256    }
257    fn return_sql() -> Result<Returns, ReturnsError> {
258        Ok(Returns::One(SqlMapping::literal("bytea")))
259    }
260}
261
262unsafe impl SqlTranslatable for i8 {
263    fn argument_sql() -> Result<SqlMapping, ArgumentError> {
264        Ok(SqlMapping::As(String::from("\"char\"")))
265    }
266    fn return_sql() -> Result<Returns, ReturnsError> {
267        Ok(Returns::One(SqlMapping::As(String::from("\"char\""))))
268    }
269}
270
271unsafe impl SqlTranslatable for i16 {
272    fn argument_sql() -> Result<SqlMapping, ArgumentError> {
273        Ok(SqlMapping::literal("smallint"))
274    }
275    fn return_sql() -> Result<Returns, ReturnsError> {
276        Ok(Returns::One(SqlMapping::literal("smallint")))
277    }
278}
279
280unsafe impl SqlTranslatable for i64 {
281    fn argument_sql() -> Result<SqlMapping, ArgumentError> {
282        Ok(SqlMapping::literal("bigint"))
283    }
284    fn return_sql() -> Result<Returns, ReturnsError> {
285        Ok(Returns::One(SqlMapping::literal("bigint")))
286    }
287}
288
289unsafe impl SqlTranslatable for bool {
290    fn argument_sql() -> Result<SqlMapping, ArgumentError> {
291        Ok(SqlMapping::literal("bool"))
292    }
293    fn return_sql() -> Result<Returns, ReturnsError> {
294        Ok(Returns::One(SqlMapping::literal("bool")))
295    }
296}
297
298unsafe impl SqlTranslatable for char {
299    fn argument_sql() -> Result<SqlMapping, ArgumentError> {
300        Ok(SqlMapping::literal("varchar"))
301    }
302    fn return_sql() -> Result<Returns, ReturnsError> {
303        Ok(Returns::One(SqlMapping::literal("varchar")))
304    }
305}
306
307unsafe impl SqlTranslatable for f32 {
308    fn argument_sql() -> Result<SqlMapping, ArgumentError> {
309        Ok(SqlMapping::literal("real"))
310    }
311    fn return_sql() -> Result<Returns, ReturnsError> {
312        Ok(Returns::One(SqlMapping::literal("real")))
313    }
314}
315
316unsafe impl SqlTranslatable for f64 {
317    fn argument_sql() -> Result<SqlMapping, ArgumentError> {
318        Ok(SqlMapping::literal("double precision"))
319    }
320    fn return_sql() -> Result<Returns, ReturnsError> {
321        Ok(Returns::One(SqlMapping::literal("double precision")))
322    }
323}
324
325unsafe impl SqlTranslatable for std::ffi::CStr {
326    fn argument_sql() -> Result<SqlMapping, ArgumentError> {
327        Ok(SqlMapping::literal("cstring"))
328    }
329    fn return_sql() -> Result<Returns, ReturnsError> {
330        Ok(Returns::One(SqlMapping::literal("cstring")))
331    }
332}
333
334unsafe impl SqlTranslatable for &'static std::ffi::CStr {
335    fn argument_sql() -> Result<SqlMapping, ArgumentError> {
336        Ok(SqlMapping::literal("cstring"))
337    }
338    fn return_sql() -> Result<Returns, ReturnsError> {
339        Ok(Returns::One(SqlMapping::literal("cstring")))
340    }
341}
342
343unsafe impl SqlTranslatable for &'static cstr_core::CStr {
344    fn argument_sql() -> Result<SqlMapping, ArgumentError> {
345        Ok(SqlMapping::literal("cstring"))
346    }
347    fn return_sql() -> Result<Returns, ReturnsError> {
348        Ok(Returns::One(SqlMapping::literal("cstring")))
349    }
350}
351
352unsafe impl SqlTranslatable for cstr_core::CStr {
353    fn argument_sql() -> Result<SqlMapping, ArgumentError> {
354        Ok(SqlMapping::literal("cstring"))
355    }
356    fn return_sql() -> Result<Returns, ReturnsError> {
357        Ok(Returns::One(SqlMapping::literal("cstring")))
358    }
359}