spg_sqlx/types/
decimal.rs1use spg_embedded::ValueOwned as EngineValue;
16
17#[cfg(feature = "bigdecimal")]
18use sqlx_core::decode::Decode;
19#[cfg(feature = "bigdecimal")]
20use sqlx_core::error::BoxDynError;
21#[cfg(feature = "bigdecimal")]
22use sqlx_core::types::Type;
23
24#[cfg(feature = "bigdecimal")]
25use crate::database::Spg;
26#[cfg(feature = "bigdecimal")]
27use crate::type_info::{Kind, SpgTypeInfo};
28#[cfg(feature = "bigdecimal")]
29use crate::value::SpgValueRef;
30
31pub(crate) fn numeric_to_text(scaled: i128, scale: u16) -> String {
38 if scale == 0 {
39 return format!("{scaled}");
40 }
41 let negative = scaled < 0;
42 let mag_str = scaled.unsigned_abs().to_string();
43 let mag_bytes = mag_str.as_bytes();
44 let scale_u = scale as usize;
45 let mut out = String::with_capacity(mag_str.len() + 3);
46 if negative {
47 out.push('-');
48 }
49 if mag_bytes.len() <= scale_u {
50 out.push('0');
51 out.push('.');
52 for _ in mag_bytes.len()..scale_u {
53 out.push('0');
54 }
55 out.push_str(&mag_str);
56 } else {
57 let split = mag_bytes.len() - scale_u;
58 out.push_str(&mag_str[..split]);
59 out.push('.');
60 out.push_str(&mag_str[split..]);
61 }
62 out
63}
64
65pub(crate) fn try_numeric_as_string(value: &EngineValue) -> Option<String> {
68 match value {
69 EngineValue::Numeric {
70 scaled,
71 scale,
72 kind: spg_storage::NumericKind::Finite,
73 } => Some(numeric_to_text(*scaled, *scale)),
74 _ => None,
75 }
76}
77
78#[cfg(feature = "bigdecimal")]
81mod bd {
82 use super::*;
83 use bigdecimal::BigDecimal;
84 use num_bigint::{BigInt, Sign};
85 use num_traits::ToPrimitive;
86 use sqlx_core::encode::{Encode, IsNull};
87
88 use crate::arguments::SpgArgumentValue;
89
90 impl Type<Spg> for BigDecimal {
91 fn type_info() -> SpgTypeInfo {
92 SpgTypeInfo::of(Kind::Numeric)
93 }
94
95 fn compatible(ty: &SpgTypeInfo) -> bool {
96 matches!(
100 ty.kind(),
101 Kind::Numeric | Kind::Int | Kind::BigInt | Kind::SmallInt
102 )
103 }
104 }
105
106 impl<'q> Encode<'q, Spg> for BigDecimal {
107 fn encode_by_ref(
108 &self,
109 buf: &mut Vec<SpgArgumentValue<'q>>,
110 ) -> Result<IsNull, BoxDynError> {
111 let (scaled, scale) = bigdecimal_to_scaled(self)?;
112 buf.push(SpgArgumentValue {
113 value: EngineValue::Numeric {
114 scaled,
115 scale,
116 kind: spg_storage::NumericKind::Finite,
117 },
118 type_info: Some(SpgTypeInfo::of(Kind::Numeric)),
119 _phantom: core::marker::PhantomData,
120 });
121 Ok(IsNull::No)
122 }
123 }
124
125 impl<'r> Decode<'r, Spg> for BigDecimal {
126 fn decode(value: SpgValueRef<'r>) -> Result<Self, BoxDynError> {
127 match value.engine() {
128 EngineValue::Numeric {
129 scaled,
130 scale,
131 kind: spg_storage::NumericKind::Finite,
132 } => Ok(scaled_to_bigdecimal(*scaled, *scale)),
133 EngineValue::Int(n) => Ok(BigDecimal::from(*n)),
137 EngineValue::BigInt(n) => Ok(BigDecimal::from(*n)),
138 EngineValue::SmallInt(n) => Ok(BigDecimal::from(i32::from(*n))),
139 other => Err(format!("cannot decode {other:?} as BigDecimal / NUMERIC").into()),
140 }
141 }
142 }
143
144 fn bigdecimal_to_scaled(d: &BigDecimal) -> Result<(i128, u16), BoxDynError> {
149 let (mantissa, exp) = d.as_bigint_and_exponent();
155 let (scaled, scale) = if exp < 0 {
156 let factor = BigInt::from(10u8).pow((-exp) as u32);
157 let folded = mantissa * factor;
158 (folded, 0u16)
159 } else if exp > i64::from(u16::MAX) {
160 return Err(format!(
161 "BigDecimal scale {exp} exceeds SPG NUMERIC ceiling ({})",
162 u16::MAX
163 )
164 .into());
165 } else {
166 #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
167 let s = exp as u16;
168 (mantissa, s)
169 };
170 let scaled_i128 = scaled.to_i128().ok_or_else(|| {
171 format!(
172 "BigDecimal mantissa {scaled} overflows i128 — SPG NUMERIC tops out at precision 38"
173 )
174 })?;
175 Ok((scaled_i128, scale))
176 }
177
178 fn scaled_to_bigdecimal(scaled: i128, scale: u16) -> BigDecimal {
179 let sign = if scaled < 0 { Sign::Minus } else { Sign::Plus };
180 let magnitude: u128 = scaled.unsigned_abs();
181 let mantissa = BigInt::from_biguint(sign, num_bigint::BigUint::from(magnitude));
182 BigDecimal::new(mantissa, i64::from(scale))
183 }
184}