pgrx/datum/numeric_support/
convert_primitive.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.
10//! Conversion implementations for converting [`AnyNumeric`] or [`Numeric<P, S>`] into primitives
11
12use super::error::Error;
13use crate::datum::{AnyNumeric, FromDatum, Numeric};
14use crate::{direct_function_call, pg_sys};
15use core::str::FromStr;
16use pgrx_pg_sys::errcodes::PgSqlErrorCode;
17use pgrx_pg_sys::panic::CaughtError;
18use pgrx_pg_sys::PgTryBuilder;
19
20macro_rules! anynumeric_to_primitive {
21    ($ty:ty, $as_:ty, $pg_func:ident) => {
22        impl TryFrom<AnyNumeric> for $ty {
23            type Error = Error;
24
25            #[inline]
26            fn try_from(value: AnyNumeric) -> Result<Self, Self::Error> {
27                to_primitive_helper::<$as_>(&value, pg_sys::$pg_func).map(|v| v as $ty)
28            }
29        }
30    };
31}
32
33anynumeric_to_primitive!(isize, i64, numeric_int8);
34anynumeric_to_primitive!(i64, i64, numeric_int8);
35anynumeric_to_primitive!(i32, i32, numeric_int4);
36anynumeric_to_primitive!(i16, i16, numeric_int2);
37anynumeric_to_primitive!(i8, i16, numeric_int2);
38anynumeric_to_primitive!(f32, f32, numeric_float4);
39anynumeric_to_primitive!(f64, f64, numeric_float8);
40
41macro_rules! anynumeric_to_oversized_primitive {
42    ($ty:ty, $as_:ty, $pg_func:ident) => {
43        /// Try to convert using the signed equivalent.  If that fails, then try using slower String
44        /// conversion
45        impl TryFrom<AnyNumeric> for $ty {
46            type Error = Error;
47
48            fn try_from(value: AnyNumeric) -> Result<Self, Self::Error> {
49                match to_primitive_helper::<$as_>(&value, pg_sys::$pg_func) {
50                    Ok(value) => Ok(value as $ty),
51                    Err(Error::OutOfRange(_)) => <$ty>::from_str(value.to_string().as_str())
52                        .map_err(|e| Error::OutOfRange(format!("{e}"))),
53                    Err(e) => Err(e),
54                }
55            }
56        }
57    };
58}
59anynumeric_to_oversized_primitive!(i128, i64, numeric_int8);
60anynumeric_to_oversized_primitive!(usize, i64, numeric_int8);
61anynumeric_to_oversized_primitive!(u128, i64, numeric_int8);
62anynumeric_to_oversized_primitive!(u64, i64, numeric_int8);
63anynumeric_to_oversized_primitive!(u32, i32, numeric_int4);
64anynumeric_to_oversized_primitive!(u16, i16, numeric_int2);
65anynumeric_to_oversized_primitive!(u8, i16, numeric_int2);
66
67macro_rules! numeric_to_primitive {
68    ($ty:ty, $as_:ty, $pg_func:ident) => {
69        impl<const P: u32, const S: u32> TryFrom<Numeric<P, S>> for $ty {
70            type Error = Error;
71
72            #[inline]
73            fn try_from(value: Numeric<P, S>) -> Result<Self, Self::Error> {
74                to_primitive_helper::<$as_>(&value, pg_sys::$pg_func).map(|v| v as $ty)
75            }
76        }
77    };
78}
79
80numeric_to_primitive!(isize, i64, numeric_int8);
81numeric_to_primitive!(i64, i64, numeric_int8);
82numeric_to_primitive!(i32, i32, numeric_int4);
83numeric_to_primitive!(i16, i16, numeric_int2);
84numeric_to_primitive!(i8, i16, numeric_int2);
85numeric_to_primitive!(f32, f32, numeric_float4);
86numeric_to_primitive!(f64, f64, numeric_float8);
87
88macro_rules! numeric_to_oversized_primitive {
89    ($ty:ty, $as_:ty, $pg_func:ident) => {
90        /// Try to convert using the signed equivalent.  If that fails, then try using slower String
91        /// conversion
92        impl<const P: u32, const S: u32> TryFrom<Numeric<P, S>> for $ty {
93            type Error = Error;
94
95            fn try_from(value: Numeric<P, S>) -> Result<Self, Self::Error> {
96                match to_primitive_helper::<$as_>(&value, pg_sys::$pg_func) {
97                    Ok(value) => Ok(value as $ty),
98                    Err(Error::OutOfRange(_)) => <$ty>::from_str(value.to_string().as_str())
99                        .map_err(|e| Error::OutOfRange(format!("{e}"))),
100                    Err(e) => Err(e),
101                }
102            }
103        }
104    };
105}
106numeric_to_oversized_primitive!(i128, i64, numeric_int8);
107numeric_to_oversized_primitive!(usize, i64, numeric_int8);
108numeric_to_oversized_primitive!(u128, i64, numeric_int8);
109numeric_to_oversized_primitive!(u64, i64, numeric_int8);
110numeric_to_oversized_primitive!(u32, i32, numeric_int4);
111numeric_to_oversized_primitive!(u16, i16, numeric_int2);
112numeric_to_oversized_primitive!(u8, i16, numeric_int2);
113
114fn to_primitive_helper<T: FromDatum>(
115    value: &AnyNumeric,
116    func: unsafe fn(pg_sys::FunctionCallInfo) -> pg_sys::Datum,
117) -> Result<T, Error> {
118    let datum = value.as_datum();
119    PgTryBuilder::new(|| unsafe {
120        // SAFETY: if 'func' returns, it won't be NULL
121        Ok(direct_function_call::<T>(func, &[datum]).unwrap_unchecked())
122    })
123    .catch_when(PgSqlErrorCode::ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE, |e| {
124        if let CaughtError::PostgresError(ref ereport) = e {
125            Err(Error::OutOfRange(ereport.message().to_string()))
126        } else {
127            e.rethrow()
128        }
129    })
130    .catch_when(PgSqlErrorCode::ERRCODE_FEATURE_NOT_SUPPORTED, |e| {
131        if let CaughtError::PostgresError(ref ereport) = e {
132            Err(Error::ConversionNotSupported(ereport.message().to_string()))
133        } else {
134            e.rethrow()
135        }
136    })
137    .execute()
138}