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 std::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    #[cfg(all(feature = "postgres_backend", feature = "numeric"))]
86    impl<'a> From<&'a BigDecimal> for PgNumeric {
87        // NOTE(clippy): No `std::ops::MulAssign` impl for `BigInt`
88        // NOTE(clippy): Clippy suggests to replace the `.take_while(|i| i.is_zero())`
89        // with `.take_while(Zero::is_zero)`, but that's a false positive.
90        // The closure gets an `&&i16` due to autoderef `<i16 as Zero>::is_zero(&self) -> bool`
91        // is called. There is no impl for `&i16` that would work with this closure.
92        #[allow(clippy::assign_op_pattern, clippy::redundant_closure)]
93        fn from(decimal: &'a BigDecimal) -> Self {
94            let (mut integer, scale) = decimal.as_bigint_and_exponent();
95
96            // Handling of negative scale
97            let scale = if scale < 0 {
98                for _ in 0..(-scale) {
99                    integer = integer * 10;
100                }
101                0
102            } else {
103                scale
104                    .try_into()
105                    .expect("Scale is expected to be 16bit large")
106            };
107
108            integer = integer.abs();
109
110            // Ensure that the decimal will always lie on a digit boundary
111            for _ in 0..(4 - scale % 4) {
112                integer = integer * 10;
113            }
114            let integer = integer.to_biguint().expect("integer is always positive");
115
116            let mut digits = ToBase10000(Some(integer)).collect::<Vec<_>>();
117            digits.reverse();
118            let digits_after_decimal = scale / 4 + 1;
119            let weight = i16::try_from(digits.len())
120                .expect("Max digit number is expected to fit into 16 bit")
121                - i16::try_from(digits_after_decimal)
122                    .expect("Max digit number is expected to fit into 16 bit")
123                - 1;
124
125            let unnecessary_zeroes = digits.iter().rev().take_while(|i| i.is_zero()).count();
126
127            let relevant_digits = digits.len() - unnecessary_zeroes;
128            digits.truncate(relevant_digits);
129
130            match decimal.sign() {
131                Sign::Plus => PgNumeric::Positive {
132                    digits,
133                    scale,
134                    weight,
135                },
136                Sign::Minus => PgNumeric::Negative {
137                    digits,
138                    scale,
139                    weight,
140                },
141                Sign::NoSign => PgNumeric::Positive {
142                    digits: vec![0],
143                    scale: 0,
144                    weight: 0,
145                },
146            }
147        }
148    }
149
150    #[cfg(all(feature = "postgres_backend", feature = "numeric"))]
151    impl From<BigDecimal> for PgNumeric {
152        fn from(bigdecimal: BigDecimal) -> Self {
153            (&bigdecimal).into()
154        }
155    }
156
157    #[cfg(all(feature = "postgres_backend", feature = "numeric"))]
158    impl ToSql<Numeric, Pg> for BigDecimal {
159        fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Pg>) -> serialize::Result {
160            let numeric = PgNumeric::from(self);
161            ToSql::<Numeric, Pg>::to_sql(&numeric, &mut out.reborrow())
162        }
163    }
164
165    #[cfg(all(feature = "postgres_backend", feature = "numeric"))]
166    impl FromSql<Numeric, Pg> for BigDecimal {
167        fn from_sql(numeric: PgValue<'_>) -> deserialize::Result<Self> {
168            PgNumeric::from_sql(numeric)?.try_into()
169        }
170    }
171
172    #[cfg(all(feature = "postgres_backend", feature = "numeric"))]
173    impl Defaultable for BigDecimal {
174        fn default_value() -> Self {
175            Self::default()
176        }
177    }
178
179    #[cfg(test)]
180    mod tests {
181        use super::*;
182        use std::str::FromStr;
183
184        #[diesel_test_helper::test]
185        fn bigdecimal_to_pgnumeric_converts_digits_to_base_10000() {
186            let decimal = BigDecimal::from_str("1").unwrap();
187            let expected = PgNumeric::Positive {
188                weight: 0,
189                scale: 0,
190                digits: vec![1],
191            };
192            assert_eq!(expected, decimal.into());
193
194            let decimal = BigDecimal::from_str("10").unwrap();
195            let expected = PgNumeric::Positive {
196                weight: 0,
197                scale: 0,
198                digits: vec![10],
199            };
200            assert_eq!(expected, decimal.into());
201
202            let decimal = BigDecimal::from_str("10000").unwrap();
203            let expected = PgNumeric::Positive {
204                weight: 1,
205                scale: 0,
206                digits: vec![1],
207            };
208            assert_eq!(expected, decimal.into());
209
210            let decimal = BigDecimal::from_str("10001").unwrap();
211            let expected = PgNumeric::Positive {
212                weight: 1,
213                scale: 0,
214                digits: vec![1, 1],
215            };
216            assert_eq!(expected, decimal.into());
217
218            let decimal = BigDecimal::from_str("100000000").unwrap();
219            let expected = PgNumeric::Positive {
220                weight: 2,
221                scale: 0,
222                digits: vec![1],
223            };
224            assert_eq!(expected, decimal.into());
225        }
226
227        #[diesel_test_helper::test]
228        fn bigdecimal_to_pg_numeric_properly_adjusts_scale() {
229            let decimal = BigDecimal::from_str("1").unwrap();
230            let expected = PgNumeric::Positive {
231                weight: 0,
232                scale: 0,
233                digits: vec![1],
234            };
235            assert_eq!(expected, decimal.into());
236
237            let decimal = BigDecimal::from_str("1.0").unwrap();
238            let expected = PgNumeric::Positive {
239                weight: 0,
240                scale: 1,
241                digits: vec![1],
242            };
243            assert_eq!(expected, decimal.into());
244
245            let decimal = BigDecimal::from_str("1.1").unwrap();
246            let expected = PgNumeric::Positive {
247                weight: 0,
248                scale: 1,
249                digits: vec![1, 1000],
250            };
251            assert_eq!(expected, decimal.into());
252
253            let decimal = BigDecimal::from_str("1.10").unwrap();
254            let expected = PgNumeric::Positive {
255                weight: 0,
256                scale: 2,
257                digits: vec![1, 1000],
258            };
259            assert_eq!(expected, decimal.into());
260
261            let decimal = BigDecimal::from_str("100000000.0001").unwrap();
262            let expected = PgNumeric::Positive {
263                weight: 2,
264                scale: 4,
265                digits: vec![1, 0, 0, 1],
266            };
267            assert_eq!(expected, decimal.into());
268
269            let decimal = BigDecimal::from_str("0.1").unwrap();
270            let expected = PgNumeric::Positive {
271                weight: -1,
272                scale: 1,
273                digits: vec![1000],
274            };
275            assert_eq!(expected, decimal.into());
276        }
277
278        #[diesel_test_helper::test]
279        fn bigdecimal_to_pg_numeric_retains_sign() {
280            let decimal = BigDecimal::from_str("123.456").unwrap();
281            let expected = PgNumeric::Positive {
282                weight: 0,
283                scale: 3,
284                digits: vec![123, 4560],
285            };
286            assert_eq!(expected, decimal.into());
287
288            let decimal = BigDecimal::from_str("-123.456").unwrap();
289            let expected = PgNumeric::Negative {
290                weight: 0,
291                scale: 3,
292                digits: vec![123, 4560],
293            };
294            assert_eq!(expected, decimal.into());
295        }
296
297        #[diesel_test_helper::test]
298        fn bigdecimal_with_negative_scale_to_pg_numeric_works() {
299            let decimal = BigDecimal::new(50.into(), -2);
300            let expected = PgNumeric::Positive {
301                weight: 0,
302                scale: 0,
303                digits: vec![5000],
304            };
305            assert_eq!(expected, decimal.into());
306
307            let decimal = BigDecimal::new(1.into(), -4);
308            let expected = PgNumeric::Positive {
309                weight: 1,
310                scale: 0,
311                digits: vec![1],
312            };
313            assert_eq!(expected, decimal.into());
314        }
315
316        #[diesel_test_helper::test]
317        fn bigdecimal_with_negative_weight_to_pg_numeric_works() {
318            let decimal = BigDecimal::from_str("0.1000000000000000").unwrap();
319            let expected = PgNumeric::Positive {
320                weight: -1,
321                scale: 16,
322                digits: vec![1000],
323            };
324            assert_eq!(expected, decimal.into());
325
326            let decimal = BigDecimal::from_str("0.00315937").unwrap();
327            let expected = PgNumeric::Positive {
328                weight: -1,
329                scale: 8,
330                digits: vec![31, 5937],
331            };
332            assert_eq!(expected, decimal.into());
333
334            let decimal = BigDecimal::from_str("0.003159370000000000").unwrap();
335            let expected = PgNumeric::Positive {
336                weight: -1,
337                scale: 18,
338                digits: vec![31, 5937],
339            };
340            assert_eq!(expected, decimal.into());
341        }
342
343        #[diesel_test_helper::test]
344        fn pg_numeric_to_bigdecimal_works() {
345            let expected = BigDecimal::from_str("123.456").unwrap();
346            let pg_numeric = PgNumeric::Positive {
347                weight: 0,
348                scale: 3,
349                digits: vec![123, 4560],
350            };
351            let res: BigDecimal = pg_numeric.try_into().unwrap();
352            assert_eq!(res, expected);
353
354            let expected = BigDecimal::from_str("-56.78").unwrap();
355            let pg_numeric = PgNumeric::Negative {
356                weight: 0,
357                scale: 2,
358                digits: vec![56, 7800],
359            };
360            let res: BigDecimal = pg_numeric.try_into().unwrap();
361            assert_eq!(res, expected);
362        }
363    }
364}