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}