sqlx_postgres/types/
money.rs1use crate::{
2    decode::Decode,
3    encode::{Encode, IsNull},
4    error::BoxDynError,
5    types::Type,
6    {PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres},
7};
8use byteorder::{BigEndian, ByteOrder};
9use std::{
10    io,
11    ops::{Add, AddAssign, Sub, SubAssign},
12};
13
14#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)]
52pub struct PgMoney(
53    pub i64,
64);
65
66impl PgMoney {
67    #[cfg(feature = "bigdecimal")]
73    pub fn to_bigdecimal(self, locale_frac_digits: i64) -> bigdecimal::BigDecimal {
74        let digits = num_bigint::BigInt::from(self.0);
75
76        bigdecimal::BigDecimal::new(digits, locale_frac_digits)
77    }
78
79    #[cfg(feature = "rust_decimal")]
85    pub fn to_decimal(self, locale_frac_digits: u32) -> rust_decimal::Decimal {
86        rust_decimal::Decimal::new(self.0, locale_frac_digits)
87    }
88
89    #[cfg(feature = "rust_decimal")]
98    pub fn from_decimal(mut decimal: rust_decimal::Decimal, locale_frac_digits: u32) -> Self {
99        decimal.rescale(locale_frac_digits);
101
102        const SIGN_MASK: i64 = i64::MAX;
104
105        let is_negative = decimal.is_sign_negative();
106        let serialized = decimal.serialize();
107
108        let value = i64::from_le_bytes(
111            *<&[u8; 8]>::try_from(&serialized[4..12])
112                .expect("BUG: slice of serialized should be 8 bytes"),
113        ) & SIGN_MASK; Self(if is_negative { -value } else { value })
117    }
118
119    #[cfg(feature = "bigdecimal")]
122    pub fn from_bigdecimal(
123        decimal: bigdecimal::BigDecimal,
124        locale_frac_digits: u32,
125    ) -> Result<Self, BoxDynError> {
126        use bigdecimal::ToPrimitive;
127
128        let multiplier = bigdecimal::BigDecimal::new(
129            num_bigint::BigInt::from(10i128.pow(locale_frac_digits)),
130            0,
131        );
132
133        let cents = decimal * multiplier;
134
135        let money = cents.to_i64().ok_or_else(|| {
136            io::Error::new(
137                io::ErrorKind::InvalidData,
138                "Provided BigDecimal could not convert to i64: overflow.",
139            )
140        })?;
141
142        Ok(Self(money))
143    }
144}
145
146impl Type<Postgres> for PgMoney {
147    fn type_info() -> PgTypeInfo {
148        PgTypeInfo::MONEY
149    }
150}
151
152impl PgHasArrayType for PgMoney {
153    fn array_type_info() -> PgTypeInfo {
154        PgTypeInfo::MONEY_ARRAY
155    }
156}
157
158impl<T> From<T> for PgMoney
159where
160    T: Into<i64>,
161{
162    fn from(num: T) -> Self {
163        Self(num.into())
164    }
165}
166
167impl Encode<'_, Postgres> for PgMoney {
168    fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
169        buf.extend(&self.0.to_be_bytes());
170
171        Ok(IsNull::No)
172    }
173}
174
175impl Decode<'_, Postgres> for PgMoney {
176    fn decode(value: PgValueRef<'_>) -> Result<Self, BoxDynError> {
177        match value.format() {
178            PgValueFormat::Binary => {
179                let cents = BigEndian::read_i64(value.as_bytes()?);
180
181                Ok(PgMoney(cents))
182            }
183            PgValueFormat::Text => {
184                let error = io::Error::new(
185                    io::ErrorKind::InvalidData,
186                    "Reading a `MONEY` value in text format is not supported.",
187                );
188
189                Err(Box::new(error))
190            }
191        }
192    }
193}
194
195impl Add<PgMoney> for PgMoney {
196    type Output = PgMoney;
197
198    fn add(self, rhs: PgMoney) -> Self::Output {
203        self.0
204            .checked_add(rhs.0)
205            .map(PgMoney)
206            .expect("overflow adding money amounts")
207    }
208}
209
210impl AddAssign<PgMoney> for PgMoney {
211    fn add_assign(&mut self, rhs: PgMoney) {
216        self.0 = self
217            .0
218            .checked_add(rhs.0)
219            .expect("overflow adding money amounts")
220    }
221}
222
223impl Sub<PgMoney> for PgMoney {
224    type Output = PgMoney;
225
226    fn sub(self, rhs: PgMoney) -> Self::Output {
231        self.0
232            .checked_sub(rhs.0)
233            .map(PgMoney)
234            .expect("overflow subtracting money amounts")
235    }
236}
237
238impl SubAssign<PgMoney> for PgMoney {
239    fn sub_assign(&mut self, rhs: PgMoney) {
244        self.0 = self
245            .0
246            .checked_sub(rhs.0)
247            .expect("overflow subtracting money amounts")
248    }
249}
250
251#[cfg(test)]
252mod tests {
253    use super::PgMoney;
254
255    #[test]
256    fn adding_works() {
257        assert_eq!(PgMoney(3), PgMoney(1) + PgMoney(2))
258    }
259
260    #[test]
261    fn add_assign_works() {
262        let mut money = PgMoney(1);
263        money += PgMoney(2);
264
265        assert_eq!(PgMoney(3), money);
266    }
267
268    #[test]
269    fn subtracting_works() {
270        assert_eq!(PgMoney(4), PgMoney(5) - PgMoney(1))
271    }
272
273    #[test]
274    fn sub_assign_works() {
275        let mut money = PgMoney(1);
276        money -= PgMoney(2);
277
278        assert_eq!(PgMoney(-1), money);
279    }
280
281    #[test]
282    fn default_value() {
283        let money = PgMoney::default();
284
285        assert_eq!(money, PgMoney(0));
286    }
287
288    #[test]
289    #[should_panic]
290    fn add_overflow_panics() {
291        let _ = PgMoney(i64::MAX) + PgMoney(1);
292    }
293
294    #[test]
295    #[should_panic]
296    fn add_assign_overflow_panics() {
297        let mut money = PgMoney(i64::MAX);
298        money += PgMoney(1);
299    }
300
301    #[test]
302    #[should_panic]
303    fn sub_overflow_panics() {
304        let _ = PgMoney(i64::MIN) - PgMoney(1);
305    }
306
307    #[test]
308    #[should_panic]
309    fn sub_assign_overflow_panics() {
310        let mut money = PgMoney(i64::MIN);
311        money -= PgMoney(1);
312    }
313
314    #[test]
315    #[cfg(feature = "bigdecimal")]
316    fn conversion_to_bigdecimal_works() {
317        let money = PgMoney(12345);
318
319        assert_eq!(
320            bigdecimal::BigDecimal::new(num_bigint::BigInt::from(12345), 2),
321            money.to_bigdecimal(2)
322        );
323    }
324
325    #[test]
326    #[cfg(feature = "rust_decimal")]
327    fn conversion_to_decimal_works() {
328        assert_eq!(
329            rust_decimal::Decimal::new(12345, 2),
330            PgMoney(12345).to_decimal(2)
331        );
332    }
333
334    #[test]
335    #[cfg(feature = "rust_decimal")]
336    fn conversion_from_decimal_works() {
337        assert_eq!(
338            PgMoney(12345),
339            PgMoney::from_decimal(rust_decimal::Decimal::new(12345, 2), 2)
340        );
341
342        assert_eq!(
343            PgMoney(12345),
344            PgMoney::from_decimal(rust_decimal::Decimal::new(123450, 3), 2)
345        );
346
347        assert_eq!(
348            PgMoney(-12345),
349            PgMoney::from_decimal(rust_decimal::Decimal::new(-123450, 3), 2)
350        );
351
352        assert_eq!(
353            PgMoney(-12300),
354            PgMoney::from_decimal(rust_decimal::Decimal::new(-123, 0), 2)
355        );
356    }
357
358    #[test]
359    #[cfg(feature = "bigdecimal")]
360    fn conversion_from_bigdecimal_works() {
361        let dec = bigdecimal::BigDecimal::new(num_bigint::BigInt::from(12345), 2);
362
363        assert_eq!(PgMoney(12345), PgMoney::from_bigdecimal(dec, 2).unwrap());
364    }
365}