|
1 | 1 | use crate::frame::frame_errors::ParseError; |
2 | 2 | use crate::frame::types; |
3 | | -use bigdecimal::BigDecimal; |
4 | 3 | use bytes::BufMut; |
5 | 4 | use std::borrow::Cow; |
6 | 5 | use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; |
@@ -365,6 +364,91 @@ impl std::hash::Hash for CqlVarint { |
365 | 364 | } |
366 | 365 | } |
367 | 366 |
|
| 367 | +/// Native CQL `decimal` representation. |
| 368 | +/// |
| 369 | +/// Represented as a pair: |
| 370 | +/// - a [`CqlVarint`] value |
| 371 | +/// - 32-bit integer which determines the position of the decimal point |
| 372 | +/// |
| 373 | +/// The type is not very useful in most use cases. |
| 374 | +/// However, users can make use of more complex types |
| 375 | +/// such as `bigdecimal::BigDecimal` (v0.4). |
| 376 | +/// The library support (e.g. conversion from [`CqlValue`]) for the type is |
| 377 | +/// enabled via `bigdecimal-04` crate feature. |
| 378 | +/// |
| 379 | +/// # DB data format |
| 380 | +/// Notice that [constructors](CqlDecimal#impl-CqlDecimal) |
| 381 | +/// don't perform any normalization on the provided data. |
| 382 | +/// For more details, see [`CqlVarint`] documentation. |
| 383 | +#[derive(Clone, PartialEq, Eq, Debug)] |
| 384 | +pub struct CqlDecimal { |
| 385 | + int_val: CqlVarint, |
| 386 | + scale: i32, |
| 387 | +} |
| 388 | + |
| 389 | +/// Constructors |
| 390 | +impl CqlDecimal { |
| 391 | + /// Creates a [`CqlDecimal`] from an array of bytes |
| 392 | + /// representing [`CqlVarint`] and a 32-bit scale. |
| 393 | + /// |
| 394 | + /// See: disclaimer about [non-normalized values](CqlVarint#db-data-format). |
| 395 | + pub fn from_signed_be_bytes_and_exponent(bytes: Vec<u8>, scale: i32) -> Self { |
| 396 | + Self { |
| 397 | + int_val: CqlVarint::from_signed_bytes_be(bytes), |
| 398 | + scale, |
| 399 | + } |
| 400 | + } |
| 401 | + |
| 402 | + /// Creates a [`CqlDecimal`] from a slice of bytes |
| 403 | + /// representing [`CqlVarint`] and a 32-bit scale. |
| 404 | + /// |
| 405 | + /// See: disclaimer about [non-normalized values](CqlVarint#db-data-format). |
| 406 | + pub fn from_signed_be_bytes_slice_and_exponent(bytes: &[u8], scale: i32) -> Self { |
| 407 | + Self::from_signed_be_bytes_and_exponent(bytes.to_vec(), scale) |
| 408 | + } |
| 409 | +} |
| 410 | + |
| 411 | +/// Conversion to raw bytes |
| 412 | +impl CqlDecimal { |
| 413 | + /// Returns a slice of bytes in two's complement |
| 414 | + /// binary big-endian representation and a scale. |
| 415 | + pub fn as_signed_be_bytes_slice_and_exponent(&self) -> (&[u8], i32) { |
| 416 | + (self.int_val.as_signed_bytes_be_slice(), self.scale) |
| 417 | + } |
| 418 | + |
| 419 | + /// Converts [`CqlDecimal`] to an array of bytes in two's |
| 420 | + /// complement binary big-endian representation and a scale. |
| 421 | + pub fn into_signed_be_bytes_and_exponent(self) -> (Vec<u8>, i32) { |
| 422 | + (self.int_val.into_signed_bytes_be(), self.scale) |
| 423 | + } |
| 424 | +} |
| 425 | + |
| 426 | +#[cfg(feature = "bigdecimal-04")] |
| 427 | +impl From<CqlDecimal> for bigdecimal_04::BigDecimal { |
| 428 | + fn from(value: CqlDecimal) -> Self { |
| 429 | + Self::from(( |
| 430 | + bigdecimal_04::num_bigint::BigInt::from_signed_bytes_be( |
| 431 | + value.int_val.as_signed_bytes_be_slice(), |
| 432 | + ), |
| 433 | + value.scale as i64, |
| 434 | + )) |
| 435 | + } |
| 436 | +} |
| 437 | + |
| 438 | +#[cfg(feature = "bigdecimal-04")] |
| 439 | +impl TryFrom<bigdecimal_04::BigDecimal> for CqlDecimal { |
| 440 | + type Error = <i64 as TryInto<i32>>::Error; |
| 441 | + |
| 442 | + fn try_from(value: bigdecimal_04::BigDecimal) -> Result<Self, Self::Error> { |
| 443 | + let (bigint, scale) = value.into_bigint_and_exponent(); |
| 444 | + let bytes = bigint.to_signed_bytes_be(); |
| 445 | + Ok(Self::from_signed_be_bytes_and_exponent( |
| 446 | + bytes, |
| 447 | + scale.try_into()?, |
| 448 | + )) |
| 449 | + } |
| 450 | +} |
| 451 | + |
368 | 452 | /// Native CQL date representation that allows for a bigger range of dates (-262145-1-1 to 262143-12-31). |
369 | 453 | /// |
370 | 454 | /// Represented as number of days since -5877641-06-23 i.e. 2^31 days before unix epoch. |
@@ -881,14 +965,36 @@ impl Value for i64 { |
881 | 965 | } |
882 | 966 | } |
883 | 967 |
|
884 | | -impl Value for BigDecimal { |
| 968 | +impl Value for CqlDecimal { |
| 969 | + fn serialize(&self, buf: &mut Vec<u8>) -> Result<(), ValueTooBig> { |
| 970 | + let (bytes, scale) = self.as_signed_be_bytes_slice_and_exponent(); |
| 971 | + |
| 972 | + if bytes.len() > (i32::MAX - 4) as usize { |
| 973 | + return Err(ValueTooBig); |
| 974 | + } |
| 975 | + let serialized_len: i32 = bytes.len() as i32 + 4; |
| 976 | + |
| 977 | + buf.put_i32(serialized_len); |
| 978 | + buf.put_i32(scale); |
| 979 | + buf.extend_from_slice(bytes); |
| 980 | + |
| 981 | + Ok(()) |
| 982 | + } |
| 983 | +} |
| 984 | + |
| 985 | +#[cfg(feature = "bigdecimal-04")] |
| 986 | +impl Value for bigdecimal_04::BigDecimal { |
885 | 987 | fn serialize(&self, buf: &mut Vec<u8>) -> Result<(), ValueTooBig> { |
886 | 988 | let (value, scale) = self.as_bigint_and_exponent(); |
887 | 989 |
|
888 | 990 | let serialized = value.to_signed_bytes_be(); |
889 | | - let serialized_len: i32 = serialized.len().try_into().map_err(|_| ValueTooBig)?; |
890 | 991 |
|
891 | | - buf.put_i32(serialized_len + 4); |
| 992 | + if serialized.len() > (i32::MAX - 4) as usize { |
| 993 | + return Err(ValueTooBig); |
| 994 | + } |
| 995 | + let serialized_len: i32 = serialized.len() as i32 + 4; |
| 996 | + |
| 997 | + buf.put_i32(serialized_len); |
892 | 998 | buf.put_i32(scale.try_into().map_err(|_| ValueTooBig)?); |
893 | 999 | buf.extend_from_slice(&serialized); |
894 | 1000 |
|
|
0 commit comments