Skip to main content

diesel/pg/types/
numeric.rs

1#[cfg(feature = "numeric")]
2mod bigdecimal {
3    extern crate bigdecimal;
4    extern crate num_bigint;
5    extern crate num_integer;
6    extern crate num_traits;
7
8    use self::bigdecimal::BigDecimal;
9    use self::num_bigint::{BigInt, BigUint, Sign};
10    use self::num_integer::Integer;
11    use self::num_traits::{Signed, ToPrimitive, Zero};
12
13    use crate::deserialize::{self, Defaultable, FromSql};
14    use crate::pg::data_types::PgNumeric;
15    use crate::pg::{Pg, PgValue};
16    use crate::serialize::{self, Output, ToSql};
17    use crate::sql_types::Numeric;
18
19    use core::error::Error;
20
21    /// Iterator over the digits of a big uint in base 10k.
22    /// The digits will be returned in little endian order.
23    struct ToBase10000(Option<BigUint>);
24
25    impl Iterator for ToBase10000 {
26        type Item = i16;
27
28        fn next(&mut self) -> Option<Self::Item> {
29            self.0.take().map(|v| {
30                let (div, rem) = v.div_rem(&BigUint::from(10_000u16));
31                if !div.is_zero() {
32                    self.0 = Some(div);
33                }
34                rem.to_i16().expect("10000 always fits in an i16")
35            })
36        }
37    }
38
39    #[cfg(all(feature = "postgres_backend", feature = "numeric"))]
40    impl<'a> TryFrom<&'a PgNumeric> for BigDecimal {
41        type Error = Box<dyn Error + Send + Sync>;
42
43        fn try_from(numeric: &'a PgNumeric) -> deserialize::Result<Self> {
44            let (sign, weight, scale, digits) = match *numeric {
45                PgNumeric::Positive {
46                    weight,
47                    scale,
48                    ref digits,
49                } => (Sign::Plus, weight, scale, digits),
50                PgNumeric::Negative {
51                    weight,
52                    scale,
53                    ref digits,
54                } => (Sign::Minus, weight, scale, digits),
55                PgNumeric::NaN => {
56                    return Err(Box::from("NaN is not (yet) supported in BigDecimal"));
57                }
58            };
59
60            let mut result = BigUint::default();
61            let count = i64::try_from(digits.len())?;
62            for digit in digits {
63                result *= BigUint::from(10_000u64);
64                result += BigUint::from(u64::try_from(*digit)?);
65            }
66            // First digit got factor 10_000^(digits.len() - 1), but should get 10_000^weight
67            let correction_exp = 4 * (i64::from(weight) - count + 1);
68            let result = BigDecimal::new(BigInt::from_biguint(sign, result), -correction_exp)
69                .with_scale(i64::from(scale));
70            Ok(result)
71        }
72    }
73
74    #[cfg(all(feature = "postgres_backend", feature = "numeric"))]
75    impl TryFrom<PgNumeric> for BigDecimal {
76        type Error = Box<dyn Error + Send + Sync>;
77
78        fn try_from(numeric: PgNumeric) -> deserialize::Result<Self> {
79            (&numeric).try_into()
80        }
81    }
82
83    // that should likely be a `TryFrom` impl
84    // TODO: diesel 3.0
85    // This now mostly exists for backward compatibility
86    // Our own `ToSql` impls don't call it anymore in favour of calling
87    // the failable inner function instead
88    #[cfg(all(feature = "postgres_backend", feature = "numeric"))]
89    impl<'a> From<&'a BigDecimal> for PgNumeric {
90        fn from(decimal: &'a BigDecimal) -> Self {
91            try_convert_decimal_to_pg_numeric(decimal)
92                .expect("Failed to convert BigDecimal to PgNumeric")
93        }
94    }
95
96    // NOTE(clippy): No `std::ops::MulAssign` impl for `BigInt`
97    // NOTE(clippy): Clippy suggests to replace the `.take_while(|i| i.is_zero())`
98    // with `.take_while(Zero::is_zero)`, but that's a false positive.
99    // The closure gets an `&&i16` due to autoderef `<i16 as Zero>::is_zero(&self) -> bool`
100    // is called. There is no impl for `&i16` that would work with this closure.
101    #[allow(clippy::assign_op_pattern, clippy::redundant_closure)]
102    #[cfg(all(feature = "postgres_backend", feature = "numeric"))]
103    fn try_convert_decimal_to_pg_numeric(
104        decimal: &BigDecimal,
105    ) -> Result<PgNumeric, Box<dyn core::error::Error + Send + Sync>> {
106        let (mut integer, scale) = decimal.as_bigint_and_exponent();
107
108        // Handling of negative scale
109        let scale = if scale < 0 {
110            for _ in 0..(-scale) {
111                integer = integer * 10;
112            }
113            0
114        } else {
115            scale
116                .try_into()
117                .map_err(|_| "Scale is expected to be 16bit large")?
118        };
119
120        integer = integer.abs();
121
122        // Ensure that the decimal will always lie on a digit boundary
123        for _ in 0..(4 - scale % 4) {
124            integer = integer * 10;
125        }
126        let integer = integer.to_biguint().ok_or("integer is always positive")?;
127
128        let mut digits = ToBase10000(Some(integer)).collect::<Vec<_>>();
129        digits.reverse();
130        let digits_after_decimal = scale / 4 + 1;
131        let weight = i16::try_from(digits.len())
132            .map_err(|_| "Max digit number is expected to fit into 16 bit")?
133            - i16::try_from(digits_after_decimal)
134                .map_err(|_| "Max digit number is expected to fit into 16 bit")?
135            - 1;
136
137        let unnecessary_zeroes = digits.iter().rev().take_while(|i| i.is_zero()).count();
138
139        let relevant_digits = digits.len() - unnecessary_zeroes;
140        digits.truncate(relevant_digits);
141
142        let result = match decimal.sign() {
143            Sign::Plus => PgNumeric::Positive {
144                digits,
145                scale,
146                weight,
147            },
148            Sign::Minus => PgNumeric::Negative {
149                digits,
150                scale,
151                weight,
152            },
153            Sign::NoSign => PgNumeric::Positive {
154                digits: ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [0]))vec![0],
155                scale: 0,
156                weight: 0,
157            },
158        };
159
160        Ok(result)
161    }
162
163    #[cfg(all(feature = "postgres_backend", feature = "numeric"))]
164    impl From<BigDecimal> for PgNumeric {
165        fn from(bigdecimal: BigDecimal) -> Self {
166            (&bigdecimal).into()
167        }
168    }
169
170    #[cfg(all(feature = "postgres_backend", feature = "numeric"))]
171    impl ToSql<Numeric, Pg> for BigDecimal {
172        fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Pg>) -> serialize::Result {
173            let numeric = try_convert_decimal_to_pg_numeric(self)?;
174            ToSql::<Numeric, Pg>::to_sql(&numeric, &mut out.reborrow())
175        }
176    }
177
178    #[cfg(all(feature = "postgres_backend", feature = "numeric"))]
179    impl FromSql<Numeric, Pg> for BigDecimal {
180        fn from_sql(numeric: PgValue<'_>) -> deserialize::Result<Self> {
181            PgNumeric::from_sql(numeric)?.try_into()
182        }
183    }
184
185    #[cfg(all(feature = "postgres_backend", feature = "numeric"))]
186    impl Defaultable for BigDecimal {
187        fn default_value() -> Self {
188            Self::default()
189        }
190    }
191
192    #[cfg(test)]
193    mod tests {
194        use crate::query_builder::ByteWrapper;
195
196        use super::*;
197        use std::str::FromStr;
198
199        #[diesel_test_helper::test]
200        fn bigdecimal_to_pgnumeric_converts_digits_to_base_10000() {
201            let decimal = BigDecimal::from_str("1").unwrap();
202            let expected = PgNumeric::Positive {
203                weight: 0,
204                scale: 0,
205                digits: vec![1],
206            };
207            assert_eq!(expected, decimal.into());
208
209            let decimal = BigDecimal::from_str("10").unwrap();
210            let expected = PgNumeric::Positive {
211                weight: 0,
212                scale: 0,
213                digits: vec![10],
214            };
215            assert_eq!(expected, decimal.into());
216
217            let decimal = BigDecimal::from_str("10000").unwrap();
218            let expected = PgNumeric::Positive {
219                weight: 1,
220                scale: 0,
221                digits: vec![1],
222            };
223            assert_eq!(expected, decimal.into());
224
225            let decimal = BigDecimal::from_str("10001").unwrap();
226            let expected = PgNumeric::Positive {
227                weight: 1,
228                scale: 0,
229                digits: vec![1, 1],
230            };
231            assert_eq!(expected, decimal.into());
232
233            let decimal = BigDecimal::from_str("100000000").unwrap();
234            let expected = PgNumeric::Positive {
235                weight: 2,
236                scale: 0,
237                digits: vec![1],
238            };
239            assert_eq!(expected, decimal.into());
240        }
241
242        #[diesel_test_helper::test]
243        fn bigdecimal_to_pg_numeric_properly_adjusts_scale() {
244            let decimal = BigDecimal::from_str("1").unwrap();
245            let expected = PgNumeric::Positive {
246                weight: 0,
247                scale: 0,
248                digits: vec![1],
249            };
250            assert_eq!(expected, decimal.into());
251
252            let decimal = BigDecimal::from_str("1.0").unwrap();
253            let expected = PgNumeric::Positive {
254                weight: 0,
255                scale: 1,
256                digits: vec![1],
257            };
258            assert_eq!(expected, decimal.into());
259
260            let decimal = BigDecimal::from_str("1.1").unwrap();
261            let expected = PgNumeric::Positive {
262                weight: 0,
263                scale: 1,
264                digits: vec![1, 1000],
265            };
266            assert_eq!(expected, decimal.into());
267
268            let decimal = BigDecimal::from_str("1.10").unwrap();
269            let expected = PgNumeric::Positive {
270                weight: 0,
271                scale: 2,
272                digits: vec![1, 1000],
273            };
274            assert_eq!(expected, decimal.into());
275
276            let decimal = BigDecimal::from_str("100000000.0001").unwrap();
277            let expected = PgNumeric::Positive {
278                weight: 2,
279                scale: 4,
280                digits: vec![1, 0, 0, 1],
281            };
282            assert_eq!(expected, decimal.into());
283
284            let decimal = BigDecimal::from_str("0.1").unwrap();
285            let expected = PgNumeric::Positive {
286                weight: -1,
287                scale: 1,
288                digits: vec![1000],
289            };
290            assert_eq!(expected, decimal.into());
291        }
292
293        #[diesel_test_helper::test]
294        fn bigdecimal_to_pg_numeric_retains_sign() {
295            let decimal = BigDecimal::from_str("123.456").unwrap();
296            let expected = PgNumeric::Positive {
297                weight: 0,
298                scale: 3,
299                digits: vec![123, 4560],
300            };
301            assert_eq!(expected, decimal.into());
302
303            let decimal = BigDecimal::from_str("-123.456").unwrap();
304            let expected = PgNumeric::Negative {
305                weight: 0,
306                scale: 3,
307                digits: vec![123, 4560],
308            };
309            assert_eq!(expected, decimal.into());
310        }
311
312        #[diesel_test_helper::test]
313        fn bigdecimal_with_negative_scale_to_pg_numeric_works() {
314            let decimal = BigDecimal::new(50.into(), -2);
315            let expected = PgNumeric::Positive {
316                weight: 0,
317                scale: 0,
318                digits: vec![5000],
319            };
320            assert_eq!(expected, decimal.into());
321
322            let decimal = BigDecimal::new(1.into(), -4);
323            let expected = PgNumeric::Positive {
324                weight: 1,
325                scale: 0,
326                digits: vec![1],
327            };
328            assert_eq!(expected, decimal.into());
329        }
330
331        #[diesel_test_helper::test]
332        fn bigdecimal_with_negative_weight_to_pg_numeric_works() {
333            let decimal = BigDecimal::from_str("0.1000000000000000").unwrap();
334            let expected = PgNumeric::Positive {
335                weight: -1,
336                scale: 16,
337                digits: vec![1000],
338            };
339            assert_eq!(expected, decimal.into());
340
341            let decimal = BigDecimal::from_str("0.00315937").unwrap();
342            let expected = PgNumeric::Positive {
343                weight: -1,
344                scale: 8,
345                digits: vec![31, 5937],
346            };
347            assert_eq!(expected, decimal.into());
348
349            let decimal = BigDecimal::from_str("0.003159370000000000").unwrap();
350            let expected = PgNumeric::Positive {
351                weight: -1,
352                scale: 18,
353                digits: vec![31, 5937],
354            };
355            assert_eq!(expected, decimal.into());
356        }
357
358        #[diesel_test_helper::test]
359        fn pg_numeric_to_bigdecimal_works() {
360            let expected = BigDecimal::from_str("123.456").unwrap();
361            let pg_numeric = PgNumeric::Positive {
362                weight: 0,
363                scale: 3,
364                digits: vec![123, 4560],
365            };
366            let res: BigDecimal = pg_numeric.try_into().unwrap();
367            assert_eq!(res, expected);
368
369            let expected = BigDecimal::from_str("-56.78").unwrap();
370            let pg_numeric = PgNumeric::Negative {
371                weight: 0,
372                scale: 2,
373                digits: vec![56, 7800],
374            };
375            let res: BigDecimal = pg_numeric.try_into().unwrap();
376            assert_eq!(res, expected);
377        }
378
379        #[diesel_test_helper::test]
380        fn bigdecimal_with_huge_scale_errors() {
381            let huge_scale = "0.".to_string() + &"0".repeat(70_000) + "1";
382            let decimal = BigDecimal::from_str(&huge_scale).unwrap();
383            let mut v = Vec::new();
384            let output = ByteWrapper(&mut v);
385
386            let mut output = Output::test(output);
387            let r = <BigDecimal as ToSql<Numeric, Pg>>::to_sql(&decimal, &mut output);
388            assert!(r.is_err());
389        }
390    }
391}