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, 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(test)]
173    mod tests {
174        use super::*;
175        use std::str::FromStr;
176
177        #[test]
178        fn bigdecimal_to_pgnumeric_converts_digits_to_base_10000() {
179            let decimal = BigDecimal::from_str("1").unwrap();
180            let expected = PgNumeric::Positive {
181                weight: 0,
182                scale: 0,
183                digits: vec![1],
184            };
185            assert_eq!(expected, decimal.into());
186
187            let decimal = BigDecimal::from_str("10").unwrap();
188            let expected = PgNumeric::Positive {
189                weight: 0,
190                scale: 0,
191                digits: vec![10],
192            };
193            assert_eq!(expected, decimal.into());
194
195            let decimal = BigDecimal::from_str("10000").unwrap();
196            let expected = PgNumeric::Positive {
197                weight: 1,
198                scale: 0,
199                digits: vec![1],
200            };
201            assert_eq!(expected, decimal.into());
202
203            let decimal = BigDecimal::from_str("10001").unwrap();
204            let expected = PgNumeric::Positive {
205                weight: 1,
206                scale: 0,
207                digits: vec![1, 1],
208            };
209            assert_eq!(expected, decimal.into());
210
211            let decimal = BigDecimal::from_str("100000000").unwrap();
212            let expected = PgNumeric::Positive {
213                weight: 2,
214                scale: 0,
215                digits: vec![1],
216            };
217            assert_eq!(expected, decimal.into());
218        }
219
220        #[test]
221        fn bigdecimal_to_pg_numeric_properly_adjusts_scale() {
222            let decimal = BigDecimal::from_str("1").unwrap();
223            let expected = PgNumeric::Positive {
224                weight: 0,
225                scale: 0,
226                digits: vec![1],
227            };
228            assert_eq!(expected, decimal.into());
229
230            let decimal = BigDecimal::from_str("1.0").unwrap();
231            let expected = PgNumeric::Positive {
232                weight: 0,
233                scale: 1,
234                digits: vec![1],
235            };
236            assert_eq!(expected, decimal.into());
237
238            let decimal = BigDecimal::from_str("1.1").unwrap();
239            let expected = PgNumeric::Positive {
240                weight: 0,
241                scale: 1,
242                digits: vec![1, 1000],
243            };
244            assert_eq!(expected, decimal.into());
245
246            let decimal = BigDecimal::from_str("1.10").unwrap();
247            let expected = PgNumeric::Positive {
248                weight: 0,
249                scale: 2,
250                digits: vec![1, 1000],
251            };
252            assert_eq!(expected, decimal.into());
253
254            let decimal = BigDecimal::from_str("100000000.0001").unwrap();
255            let expected = PgNumeric::Positive {
256                weight: 2,
257                scale: 4,
258                digits: vec![1, 0, 0, 1],
259            };
260            assert_eq!(expected, decimal.into());
261
262            let decimal = BigDecimal::from_str("0.1").unwrap();
263            let expected = PgNumeric::Positive {
264                weight: -1,
265                scale: 1,
266                digits: vec![1000],
267            };
268            assert_eq!(expected, decimal.into());
269        }
270
271        #[test]
272        fn bigdecimal_to_pg_numeric_retains_sign() {
273            let decimal = BigDecimal::from_str("123.456").unwrap();
274            let expected = PgNumeric::Positive {
275                weight: 0,
276                scale: 3,
277                digits: vec![123, 4560],
278            };
279            assert_eq!(expected, decimal.into());
280
281            let decimal = BigDecimal::from_str("-123.456").unwrap();
282            let expected = PgNumeric::Negative {
283                weight: 0,
284                scale: 3,
285                digits: vec![123, 4560],
286            };
287            assert_eq!(expected, decimal.into());
288        }
289
290        #[test]
291        fn bigdecimal_with_negative_scale_to_pg_numeric_works() {
292            let decimal = BigDecimal::new(50.into(), -2);
293            let expected = PgNumeric::Positive {
294                weight: 0,
295                scale: 0,
296                digits: vec![5000],
297            };
298            assert_eq!(expected, decimal.into());
299
300            let decimal = BigDecimal::new(1.into(), -4);
301            let expected = PgNumeric::Positive {
302                weight: 1,
303                scale: 0,
304                digits: vec![1],
305            };
306            assert_eq!(expected, decimal.into());
307        }
308
309        #[test]
310        fn bigdecimal_with_negative_weight_to_pg_numeric_works() {
311            let decimal = BigDecimal::from_str("0.1000000000000000").unwrap();
312            let expected = PgNumeric::Positive {
313                weight: -1,
314                scale: 16,
315                digits: vec![1000],
316            };
317            assert_eq!(expected, decimal.into());
318
319            let decimal = BigDecimal::from_str("0.00315937").unwrap();
320            let expected = PgNumeric::Positive {
321                weight: -1,
322                scale: 8,
323                digits: vec![31, 5937],
324            };
325            assert_eq!(expected, decimal.into());
326
327            let decimal = BigDecimal::from_str("0.003159370000000000").unwrap();
328            let expected = PgNumeric::Positive {
329                weight: -1,
330                scale: 18,
331                digits: vec![31, 5937],
332            };
333            assert_eq!(expected, decimal.into());
334        }
335
336        #[test]
337        fn pg_numeric_to_bigdecimal_works() {
338            let expected = BigDecimal::from_str("123.456").unwrap();
339            let pg_numeric = PgNumeric::Positive {
340                weight: 0,
341                scale: 3,
342                digits: vec![123, 4560],
343            };
344            let res: BigDecimal = pg_numeric.try_into().unwrap();
345            assert_eq!(res, expected);
346
347            let expected = BigDecimal::from_str("-56.78").unwrap();
348            let pg_numeric = PgNumeric::Negative {
349                weight: 0,
350                scale: 2,
351                digits: vec![56, 7800],
352            };
353            let res: BigDecimal = pg_numeric.try_into().unwrap();
354            assert_eq!(res, expected);
355        }
356    }
357}