mirror of
https://github.com/launchbadge/sqlx.git
synced 2025-12-25 09:39:58 +00:00
fix: correct decoding of rust_decimal::Decimal for high-precision values (#2820)
also fixes handling of feature flags
This commit is contained in:
parent
540baf7df5
commit
58cb18a47a
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -3075,7 +3075,6 @@ dependencies = [
|
||||
"env_logger",
|
||||
"futures",
|
||||
"hex",
|
||||
"libsqlite3-sys",
|
||||
"paste",
|
||||
"rand 0.8.5",
|
||||
"rand_xoshiro",
|
||||
|
||||
@ -16,7 +16,7 @@ migrate = ["sqlx-core/migrate"]
|
||||
offline = ["sqlx-core/offline"]
|
||||
|
||||
# Type integration features which require additional dependencies
|
||||
rust_decimal = ["dep:rust_decimal", "dep:num-bigint"]
|
||||
rust_decimal = ["dep:rust_decimal", "rust_decimal/maths"]
|
||||
bigdecimal = ["dep:bigdecimal", "dep:num-bigint"]
|
||||
|
||||
[dependencies]
|
||||
|
||||
@ -1,8 +1,4 @@
|
||||
use num_bigint::{BigInt, Sign};
|
||||
use rust_decimal::{
|
||||
prelude::{ToPrimitive, Zero},
|
||||
Decimal,
|
||||
};
|
||||
use rust_decimal::{prelude::Zero, Decimal};
|
||||
|
||||
use crate::decode::Decode;
|
||||
use crate::encode::{Encode, IsNull};
|
||||
@ -11,6 +7,8 @@ use crate::types::numeric::{PgNumeric, PgNumericSign};
|
||||
use crate::types::Type;
|
||||
use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres};
|
||||
|
||||
use rust_decimal::MathematicalOps;
|
||||
|
||||
impl Type<Postgres> for Decimal {
|
||||
fn type_info() -> PgTypeInfo {
|
||||
PgTypeInfo::NUMERIC
|
||||
@ -27,7 +25,7 @@ impl TryFrom<PgNumeric> for Decimal {
|
||||
type Error = BoxDynError;
|
||||
|
||||
fn try_from(numeric: PgNumeric) -> Result<Self, BoxDynError> {
|
||||
let (digits, sign, weight) = match numeric {
|
||||
let (digits, sign, mut weight) = match numeric {
|
||||
PgNumeric::Number {
|
||||
digits,
|
||||
sign,
|
||||
@ -41,39 +39,33 @@ impl TryFrom<PgNumeric> for Decimal {
|
||||
};
|
||||
|
||||
if digits.is_empty() {
|
||||
// Postgres returns an empty digit array for 0 but BigInt expects at least one zero
|
||||
// Postgres returns an empty digit array for 0
|
||||
return Ok(0u64.into());
|
||||
}
|
||||
|
||||
let sign = match sign {
|
||||
PgNumericSign::Positive => Sign::Plus,
|
||||
PgNumericSign::Negative => Sign::Minus,
|
||||
};
|
||||
let mut value = Decimal::ZERO;
|
||||
|
||||
// weight is 0 if the decimal point falls after the first base-10000 digit
|
||||
let scale = (digits.len() as i64 - weight as i64 - 1) * 4;
|
||||
// Sum over `digits`, multiply each by its weight and add it to `value`.
|
||||
for digit in digits {
|
||||
let mul = Decimal::from(10_000i16)
|
||||
.checked_powi(weight as i64)
|
||||
.ok_or("value not representable as rust_decimal::Decimal")?;
|
||||
|
||||
// no optimized algorithm for base-10 so use base-100 for faster processing
|
||||
let mut cents = Vec::with_capacity(digits.len() * 2);
|
||||
for digit in &digits {
|
||||
cents.push((digit / 100) as u8);
|
||||
cents.push((digit % 100) as u8);
|
||||
let part = Decimal::from(digit) * mul;
|
||||
|
||||
value = value
|
||||
.checked_add(part)
|
||||
.ok_or("value not representable as rust_decimal::Decimal")?;
|
||||
|
||||
weight = weight.checked_sub(1).ok_or("weight underflowed")?;
|
||||
}
|
||||
|
||||
let bigint = BigInt::from_radix_be(sign, ¢s, 100)
|
||||
.ok_or("PgNumeric contained an out-of-range digit")?;
|
||||
|
||||
match (bigint.to_i128(), scale) {
|
||||
// A negative scale, meaning we have nothing on the right and must
|
||||
// add zeroes to the left.
|
||||
(Some(num), scale) if scale < 0 => Ok(Decimal::from_i128_with_scale(
|
||||
num * 10i128.pow(scale.abs() as u32),
|
||||
0,
|
||||
)),
|
||||
// A positive scale, so we have decimals on the right.
|
||||
(Some(num), _) => Ok(Decimal::from_i128_with_scale(num, scale as u32)),
|
||||
(None, _) => Err("Decimal's integer part out of range.".into()),
|
||||
match sign {
|
||||
PgNumericSign::Positive => value.set_sign_positive(true),
|
||||
PgNumericSign::Negative => value.set_sign_negative(true),
|
||||
}
|
||||
|
||||
Ok(value)
|
||||
}
|
||||
}
|
||||
|
||||
@ -403,4 +395,7 @@ mod decimal_to_pgnumeric {
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn issue_666_trailing_zeroes_at_max_precision() {}
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
extern crate time_ as time;
|
||||
|
||||
#[cfg(feature = "decimal")]
|
||||
#[cfg(feature = "rust_decimal")]
|
||||
use std::str::FromStr;
|
||||
|
||||
use sqlx::mysql::MySql;
|
||||
@ -223,7 +223,7 @@ test_type!(bigdecimal<sqlx::types::BigDecimal>(
|
||||
"CAST(12345.6789 AS DECIMAL(9, 4))" == "12345.6789".parse::<sqlx::types::BigDecimal>().unwrap(),
|
||||
));
|
||||
|
||||
#[cfg(feature = "decimal")]
|
||||
#[cfg(feature = "rust_decimal")]
|
||||
test_type!(decimal<sqlx::types::Decimal>(MySql,
|
||||
"CAST(0 as DECIMAL(0, 0))" == sqlx::types::Decimal::from_str("0").unwrap(),
|
||||
"CAST(1 AS DECIMAL(1, 0))" == sqlx::types::Decimal::from_str("1").unwrap(),
|
||||
|
||||
@ -6,7 +6,6 @@ use sqlx::postgres::types::{Oid, PgCiText, PgInterval, PgMoney, PgRange};
|
||||
use sqlx::postgres::Postgres;
|
||||
use sqlx_test::{test_decode_type, test_prepared_type, test_type};
|
||||
|
||||
#[cfg(any(postgres_14, postgres_15))]
|
||||
use std::str::FromStr;
|
||||
|
||||
test_type!(null<Option<i16>>(Postgres,
|
||||
@ -475,7 +474,7 @@ test_type!(numrange_bigdecimal<PgRange<sqlx::types::BigDecimal>>(Postgres,
|
||||
Bound::Excluded("2.4".parse::<sqlx::types::BigDecimal>().unwrap())))
|
||||
));
|
||||
|
||||
#[cfg(feature = "decimal")]
|
||||
#[cfg(feature = "rust_decimal")]
|
||||
test_type!(decimal<sqlx::types::Decimal>(Postgres,
|
||||
"0::numeric" == sqlx::types::Decimal::from_str("0").unwrap(),
|
||||
"1::numeric" == sqlx::types::Decimal::from_str("1").unwrap(),
|
||||
@ -484,9 +483,12 @@ test_type!(decimal<sqlx::types::Decimal>(Postgres,
|
||||
"0.01234::numeric" == sqlx::types::Decimal::from_str("0.01234").unwrap(),
|
||||
"12.34::numeric" == sqlx::types::Decimal::from_str("12.34").unwrap(),
|
||||
"12345.6789::numeric" == sqlx::types::Decimal::from_str("12345.6789").unwrap(),
|
||||
// https://github.com/launchbadge/sqlx/issues/666#issuecomment-683872154
|
||||
"17.905625985174584660842500258::numeric" == sqlx::types::Decimal::from_str("17.905625985174584660842500258").unwrap(),
|
||||
"-17.905625985174584660842500258::numeric" == sqlx::types::Decimal::from_str("-17.905625985174584660842500258").unwrap(),
|
||||
));
|
||||
|
||||
#[cfg(feature = "decimal")]
|
||||
#[cfg(feature = "rust_decimal")]
|
||||
test_type!(numrange_decimal<PgRange<sqlx::types::Decimal>>(Postgres,
|
||||
"'(1.3,2.4)'::numrange" == PgRange::from(
|
||||
(Bound::Excluded(sqlx::types::Decimal::from_str("1.3").unwrap()),
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user