Skip to main content

pgrx/datum/
numeric.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 core::ffi::CStr;
11use core::fmt::{Debug, Display, Formatter};
12use std::cmp::Ordering;
13use std::fmt;
14use std::iter::Sum;
15
16use crate::datum::numeric_support::convert::{FromPrimitiveFunc, from_primitive_helper};
17pub use crate::datum::numeric_support::error::Error;
18use crate::{direct_function_call, pg_sys};
19
20/// A wrapper around the Postgres SQL `NUMERIC(P, S)` type.  Its `Precision` and `Scale` values
21/// are known at compile-time to assist with scale conversions and general type safety.
22///
23/// PostgreSQL permits the scale in a numeric type declaration to be any value in the range
24/// -1000 to 1000. However, the SQL standard requires the scale to be in the range 0 to precision.
25/// Using scales outside that range may not be portable to other database systems.
26///
27/// `pgrx`, by using a `u32` for the scale, restricts it to be in line with the SQL standard.  While
28/// it is possible to specify value larger than 1000, ultimately Postgres will reject such values.
29///
30/// All the various Rust arithmetic traits are implemented for [`AnyNumeric`], but using them, even
31/// if the (P, S) is the same on both sides of the operator converts the result to an [`AnyNumeric`].
32/// It is [`AnyNumeric`] that also supports the various "Assign" (ie, [`AddAssign`][core::ops::AddAssign]) traits.
33///
34/// [`Numeric`] is well-suited for a `#[pg_extern]` return type, more so than a function argument.
35/// Use [`AnyNumeric`] for those.
36#[derive(Debug, Clone)]
37#[repr(transparent)]
38pub struct Numeric<const P: u32, const S: u32>(pub(crate) AnyNumeric);
39
40/// A plain Postgres SQL `NUMERIC` with default precision and scale values.  This is a sufficient type
41/// to represent any Rust primitive value from `i128::MIN` to `u128::MAX` and anything in between.
42///
43/// Generally, this is the type you'll want to use as function arguments.
44///
45#[derive(Debug, Clone)]
46pub struct AnyNumeric {
47    // we represent a NUMERIC as opaque bytes -- we never inspect these ourselves, only a pointer
48    // to them (cast as a [`Datum`]) are passed to Postgres
49    pub(super) inner: Box<[u8]>,
50}
51
52impl Display for AnyNumeric {
53    fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result {
54        let numeric_out = unsafe {
55            direct_function_call::<&CStr>(pg_sys::numeric_out, &[self.as_datum()]).unwrap()
56        };
57        let s = numeric_out.to_str().expect("numeric_out is not a valid UTF8 string");
58        fmt.pad(s)
59    }
60}
61
62impl<const P: u32, const S: u32> Display for Numeric<P, S> {
63    fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), fmt::Error> {
64        write!(fmt, "{}", self.0)
65    }
66}
67
68impl Default for AnyNumeric {
69    fn default() -> Self {
70        0.into()
71    }
72}
73
74impl Sum for AnyNumeric {
75    fn sum<I: Iterator<Item = Self>>(mut iter: I) -> Self {
76        let mut sum = iter.next().unwrap_or_default();
77        for n in iter {
78            sum += n;
79        }
80        sum
81    }
82}
83
84#[inline(always)]
85pub(crate) const fn make_typmod(precision: u32, scale: u32) -> i32 {
86    let (precision, scale) = (precision as i32, scale as i32);
87    match (precision, scale) {
88        (0, 0) => -1,
89        (p, s) => ((p << 16) | s) + pg_sys::VARHDRSZ as i32,
90    }
91}
92
93/// Which way does a [`AnyNumeric`] sign face?
94#[derive(Debug, PartialEq, Eq, Hash)]
95pub enum Sign {
96    Negative,
97    Positive,
98    Zero,
99    NaN,
100}
101
102impl AnyNumeric {
103    /// Returns the sign of this [`AnyNumeric`]
104    pub fn sign(&self) -> Sign {
105        if self.is_nan() {
106            Sign::NaN
107        } else {
108            match self.cmp(&0.into()) {
109                Ordering::Less => Sign::Negative,
110                Ordering::Greater => Sign::Positive,
111                Ordering::Equal => Sign::Zero,
112            }
113        }
114    }
115
116    /// Is this [`AnyNumeric`] not-a-number?
117    pub fn is_nan(&self) -> bool {
118        unsafe { pg_sys::numeric_is_nan(self.as_ptr() as *mut _) }
119    }
120
121    /// The absolute value of this [`AnyNumeric`]
122    pub fn abs(&self) -> Self {
123        unsafe { direct_function_call(pg_sys::numeric_abs, &[self.as_datum()]).unwrap() }
124    }
125
126    /// Compute the logarithm of this [`AnyNumeric`] in the given base
127    pub fn log(&self, base: AnyNumeric) -> Self {
128        unsafe {
129            direct_function_call(pg_sys::numeric_log, &[self.as_datum(), base.as_datum()]).unwrap()
130        }
131    }
132
133    /// Raise `e` to the power of `x`
134    pub fn exp(x: &AnyNumeric) -> Self {
135        unsafe { direct_function_call(pg_sys::numeric_exp, &[x.as_datum()]).unwrap() }
136    }
137
138    /// Compute the square root of this [`AnyNumeric`]
139    pub fn sqrt(&self) -> Self {
140        unsafe { direct_function_call(pg_sys::numeric_sqrt, &[self.as_datum()]).unwrap() }
141    }
142
143    /// Return the smallest integer greater than or equal to this [`AnyNumeric`]
144    pub fn ceil(&self) -> Self {
145        unsafe { direct_function_call(pg_sys::numeric_ceil, &[self.as_datum()]).unwrap() }
146    }
147
148    /// Return the largest integer equal to or less than this [`AnyNumeric`]
149    pub fn floor(&self) -> Self {
150        unsafe { direct_function_call(pg_sys::numeric_floor, &[self.as_datum()]).unwrap() }
151    }
152
153    /// Calculate the greatest common divisor of this an another [`AnyNumeric`]
154    pub fn gcd(&self, n: &AnyNumeric) -> AnyNumeric {
155        unsafe {
156            direct_function_call(pg_sys::numeric_gcd, &[self.as_datum(), n.as_datum()]).unwrap()
157        }
158    }
159
160    /// Output function for numeric data type, suppressing insignificant trailing
161    /// zeroes and then any trailing decimal point.  The intent of this is to
162    /// produce strings that are equal if and only if the input numeric values
163    /// compare equal.
164    pub fn normalize(&self) -> &str {
165        unsafe {
166            let s = pg_sys::numeric_normalize(self.as_ptr() as *mut _);
167            let cstr = CStr::from_ptr(s);
168            let normalized = cstr.to_str().unwrap();
169            normalized
170        }
171    }
172
173    /// Consume this [`AnyNumeric`] and apply a `Precision` and `Scale`.
174    ///
175    /// Returns an [`Error`] if this [`AnyNumeric`] doesn't fit within the new constraints.
176    ///
177    /// ## Examples
178    ///
179    /// ```rust,no_run
180    /// use pgrx::{AnyNumeric, Numeric};
181    /// let start = AnyNumeric::try_from(42.42).unwrap();
182    /// let rescaled:f32 = start.rescale::<3, 1>().unwrap().try_into().unwrap();
183    /// assert_eq!(rescaled, 42.4);
184    /// ```
185    #[inline]
186    pub fn rescale<const P: u32, const S: u32>(self) -> Result<Numeric<P, S>, Error> {
187        Numeric::try_from(self)
188    }
189
190    #[inline]
191    pub(crate) fn as_datum(&self) -> Option<pg_sys::Datum> {
192        Some(pg_sys::Datum::from(self.inner.as_ptr()))
193    }
194
195    #[inline]
196    fn as_ptr(&self) -> *const pg_sys::NumericData {
197        self.inner.as_ptr().cast()
198    }
199}
200
201impl<const P: u32, const S: u32> Numeric<P, S> {
202    /// Borrow this [`AnyNumeric`] as the more generic [`AnyNumeric`]
203    #[inline]
204    pub fn as_anynumeric(&self) -> &AnyNumeric {
205        &self.0
206    }
207
208    /// Consume this [`AnyNumeric`] and a new `Precision` and `Scale`.
209    ///
210    /// Returns an [`Error`] if this [`AnyNumeric`] doesn't fit within the new constraints.
211    ///
212    /// ## Examples
213    ///
214    /// ```rust,no_run
215    /// use pgrx::{AnyNumeric, Numeric};
216    /// use pgrx_pg_sys::float4;
217    /// let start = AnyNumeric::try_from(42.42).unwrap();
218    /// let rescaled:float4 = start.rescale::<3, 1>().unwrap().try_into().unwrap();
219    /// assert_eq!(rescaled, 42.4);
220    /// ```
221    #[inline]
222    pub fn rescale<const NEW_P: u32, const NEW_S: u32>(
223        self,
224    ) -> Result<Numeric<NEW_P, NEW_S>, Error> {
225        from_primitive_helper::<_, NEW_P, NEW_S>(self, FromPrimitiveFunc::Numeric)
226    }
227}