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 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 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 #[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 #[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 let scale = if scale < -131064 {
110 return Err("Max digit number is expect to fit into 16 bit".into());
114 } else if scale < 0 {
115 integer = BigDecimal::new(integer, scale)
116 .with_scale(0)
117 .as_bigint_and_exponent()
118 .0;
119 0
120 } else {
121 scale
122 .try_into()
123 .map_err(|_| "Scale is expected to be 16bit large")?
124 };
125
126 integer = integer.abs();
127
128 for _ in 0..(4 - scale % 4) {
130 integer = integer * 10;
131 }
132 let integer = integer.to_biguint().ok_or("integer is always positive")?;
133
134 let mut digits = ToBase10000(Some(integer)).collect::<Vec<_>>();
135 digits.reverse();
136 let digits_after_decimal = scale / 4 + 1;
137 let weight = i16::try_from(digits.len())
138 .map_err(|_| "Max digit number is expected to fit into 16 bit")?
139 - i16::try_from(digits_after_decimal)
140 .map_err(|_| "Max digit number is expected to fit into 16 bit")?
141 - 1;
142
143 let unnecessary_zeroes = digits.iter().rev().take_while(|i| i.is_zero()).count();
144
145 let relevant_digits = digits.len() - unnecessary_zeroes;
146 digits.truncate(relevant_digits);
147
148 let result = match decimal.sign() {
149 Sign::Plus => PgNumeric::Positive {
150 digits,
151 scale,
152 weight,
153 },
154 Sign::Minus => PgNumeric::Negative {
155 digits,
156 scale,
157 weight,
158 },
159 Sign::NoSign => PgNumeric::Positive {
160 digits: ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
[0]))vec![0],
161 scale: 0,
162 weight: 0,
163 },
164 };
165
166 Ok(result)
167 }
168
169 #[cfg(all(feature = "postgres_backend", feature = "numeric"))]
170 impl From<BigDecimal> for PgNumeric {
171 fn from(bigdecimal: BigDecimal) -> Self {
172 (&bigdecimal).into()
173 }
174 }
175
176 #[cfg(all(feature = "postgres_backend", feature = "numeric"))]
177 impl ToSql<Numeric, Pg> for BigDecimal {
178 fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Pg>) -> serialize::Result {
179 let numeric = try_convert_decimal_to_pg_numeric(self)?;
180 ToSql::<Numeric, Pg>::to_sql(&numeric, &mut out.reborrow())
181 }
182 }
183
184 #[cfg(all(feature = "postgres_backend", feature = "numeric"))]
185 impl FromSql<Numeric, Pg> for BigDecimal {
186 fn from_sql(numeric: PgValue<'_>) -> deserialize::Result<Self> {
187 PgNumeric::from_sql(numeric)?.try_into()
188 }
189 }
190
191 #[cfg(all(feature = "postgres_backend", feature = "numeric"))]
192 impl Defaultable for BigDecimal {
193 fn default_value() -> Self {
194 Self::default()
195 }
196 }
197
198 #[cfg(test)]
199 mod tests {
200 use crate::query_builder::ByteWrapper;
201
202 use super::*;
203 use std::str::FromStr;
204
205 #[diesel_test_helper::test]
206 fn bigdecimal_to_pgnumeric_converts_digits_to_base_10000() {
207 let decimal = BigDecimal::from_str("1").unwrap();
208 let expected = PgNumeric::Positive {
209 weight: 0,
210 scale: 0,
211 digits: vec![1],
212 };
213 assert_eq!(expected, decimal.into());
214
215 let decimal = BigDecimal::from_str("10").unwrap();
216 let expected = PgNumeric::Positive {
217 weight: 0,
218 scale: 0,
219 digits: vec![10],
220 };
221 assert_eq!(expected, decimal.into());
222
223 let decimal = BigDecimal::from_str("10000").unwrap();
224 let expected = PgNumeric::Positive {
225 weight: 1,
226 scale: 0,
227 digits: vec![1],
228 };
229 assert_eq!(expected, decimal.into());
230
231 let decimal = BigDecimal::from_str("10001").unwrap();
232 let expected = PgNumeric::Positive {
233 weight: 1,
234 scale: 0,
235 digits: vec![1, 1],
236 };
237 assert_eq!(expected, decimal.into());
238
239 let decimal = BigDecimal::from_str("100000000").unwrap();
240 let expected = PgNumeric::Positive {
241 weight: 2,
242 scale: 0,
243 digits: vec![1],
244 };
245 assert_eq!(expected, decimal.into());
246 }
247
248 #[diesel_test_helper::test]
249 fn bigdecimal_to_pg_numeric_properly_adjusts_scale() {
250 let decimal = BigDecimal::from_str("1").unwrap();
251 let expected = PgNumeric::Positive {
252 weight: 0,
253 scale: 0,
254 digits: vec![1],
255 };
256 assert_eq!(expected, decimal.into());
257
258 let decimal = BigDecimal::from_str("1.0").unwrap();
259 let expected = PgNumeric::Positive {
260 weight: 0,
261 scale: 1,
262 digits: vec![1],
263 };
264 assert_eq!(expected, decimal.into());
265
266 let decimal = BigDecimal::from_str("1.1").unwrap();
267 let expected = PgNumeric::Positive {
268 weight: 0,
269 scale: 1,
270 digits: vec![1, 1000],
271 };
272 assert_eq!(expected, decimal.into());
273
274 let decimal = BigDecimal::from_str("1.10").unwrap();
275 let expected = PgNumeric::Positive {
276 weight: 0,
277 scale: 2,
278 digits: vec![1, 1000],
279 };
280 assert_eq!(expected, decimal.into());
281
282 let decimal = BigDecimal::from_str("100000000.0001").unwrap();
283 let expected = PgNumeric::Positive {
284 weight: 2,
285 scale: 4,
286 digits: vec![1, 0, 0, 1],
287 };
288 assert_eq!(expected, decimal.into());
289
290 let decimal = BigDecimal::from_str("0.1").unwrap();
291 let expected = PgNumeric::Positive {
292 weight: -1,
293 scale: 1,
294 digits: vec![1000],
295 };
296 assert_eq!(expected, decimal.into());
297 }
298
299 #[diesel_test_helper::test]
300 fn bigdecimal_to_pg_numeric_retains_sign() {
301 let decimal = BigDecimal::from_str("123.456").unwrap();
302 let expected = PgNumeric::Positive {
303 weight: 0,
304 scale: 3,
305 digits: vec![123, 4560],
306 };
307 assert_eq!(expected, decimal.into());
308
309 let decimal = BigDecimal::from_str("-123.456").unwrap();
310 let expected = PgNumeric::Negative {
311 weight: 0,
312 scale: 3,
313 digits: vec![123, 4560],
314 };
315 assert_eq!(expected, decimal.into());
316 }
317
318 #[diesel_test_helper::test]
319 fn bigdecimal_with_negative_scale_to_pg_numeric_works() {
320 let decimal = BigDecimal::new(50.into(), -2);
321 let expected = PgNumeric::Positive {
322 weight: 0,
323 scale: 0,
324 digits: vec![5000],
325 };
326 assert_eq!(expected, decimal.into());
327
328 let decimal = BigDecimal::new(1.into(), -4);
329 let expected = PgNumeric::Positive {
330 weight: 1,
331 scale: 0,
332 digits: vec![1],
333 };
334 assert_eq!(expected, decimal.into());
335 }
336
337 #[diesel_test_helper::test]
338 fn bigdecimal_with_negative_weight_to_pg_numeric_works() {
339 let decimal = BigDecimal::from_str("0.1000000000000000").unwrap();
340 let expected = PgNumeric::Positive {
341 weight: -1,
342 scale: 16,
343 digits: vec![1000],
344 };
345 assert_eq!(expected, decimal.into());
346
347 let decimal = BigDecimal::from_str("0.00315937").unwrap();
348 let expected = PgNumeric::Positive {
349 weight: -1,
350 scale: 8,
351 digits: vec![31, 5937],
352 };
353 assert_eq!(expected, decimal.into());
354
355 let decimal = BigDecimal::from_str("0.003159370000000000").unwrap();
356 let expected = PgNumeric::Positive {
357 weight: -1,
358 scale: 18,
359 digits: vec![31, 5937],
360 };
361 assert_eq!(expected, decimal.into());
362 }
363
364 #[diesel_test_helper::test]
365 fn pg_numeric_to_bigdecimal_works() {
366 let expected = BigDecimal::from_str("123.456").unwrap();
367 let pg_numeric = PgNumeric::Positive {
368 weight: 0,
369 scale: 3,
370 digits: vec![123, 4560],
371 };
372 let res: BigDecimal = pg_numeric.try_into().unwrap();
373 assert_eq!(res, expected);
374
375 let expected = BigDecimal::from_str("-56.78").unwrap();
376 let pg_numeric = PgNumeric::Negative {
377 weight: 0,
378 scale: 2,
379 digits: vec![56, 7800],
380 };
381 let res: BigDecimal = pg_numeric.try_into().unwrap();
382 assert_eq!(res, expected);
383 }
384
385 #[diesel_test_helper::test]
386 fn bigdecimal_with_huge_scale_errors() {
387 let huge_scale = "0.".to_string() + &"0".repeat(70_000) + "1";
388 let decimal = BigDecimal::from_str(&huge_scale).unwrap();
389 let mut v = Vec::new();
390 let output = ByteWrapper(&mut v);
391
392 let mut output = Output::test(output);
393 let r = <BigDecimal as ToSql<Numeric, Pg>>::to_sql(&decimal, &mut output);
394 assert!(r.is_err());
395 }
396
397 #[diesel_test_helper::test]
398 fn bigdecimal_negative_scale_doesnt_burn_cpu_time() {
399 let d = BigDecimal::from_str("1E+130000").expect("parse bigdecimal");
400 let n: PgNumeric = d.into();
401 assert_eq!(
402 n,
403 PgNumeric::Positive {
404 weight: 32500,
405 scale: 0,
406 digits: vec![1]
407 }
408 );
409 }
410
411 #[diesel_test_helper::test]
412 #[should_panic]
413 fn bigdecimal_negative_scale_doesnt_burn_cpu_time_fast_error() {
414 let d = BigDecimal::from_str("1E+131064").expect("parse bigdecimal");
415 let _n: PgNumeric = d.into();
416 }
417 }
418}