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}