diff --git a/docs/source/data-types/decimal.md b/docs/source/data-types/decimal.md index 3ad7f9302..084592500 100644 --- a/docs/source/data-types/decimal.md +++ b/docs/source/data-types/decimal.md @@ -3,7 +3,7 @@ ## value::CqlDecimal -Without any feature flags, the user can interact with `decimal` type by making use of `value::CqlDecimal` which is a very simple wrapper representing the value as signed binary number in big-endian order with a 32-bit scale. +Without any feature flags, the user can interact with `decimal` type by making use of `value::CqlDecimal` or `value::CqlDecimalBorrowed` which are very simple wrappers representing the value as signed binary number in big-endian order with a 32-bit scale. ```rust # extern crate scylla; diff --git a/scylla-cql/src/frame/value.rs b/scylla-cql/src/frame/value.rs index 0cac789b1..862b470f3 100644 --- a/scylla-cql/src/frame/value.rs +++ b/scylla-cql/src/frame/value.rs @@ -454,6 +454,9 @@ impl From> for num_bigint_04::BigInt { /// - a [`CqlVarint`] value /// - 32-bit integer which determines the position of the decimal point /// +/// This struct holds owned bytes. If you wish to borrow the bytes instead, +/// see [`CqlDecimalBorrowed`] documentation. +/// /// The type is not very useful in most use cases. /// However, users can make use of more complex types /// such as `bigdecimal::BigDecimal` (v0.4). @@ -470,6 +473,20 @@ pub struct CqlDecimal { scale: i32, } +/// Borrowed version of native CQL `decimal` representation. +/// +/// Represented as a pair: +/// - a [`CqlVarintBorrowed`] value +/// - 32-bit integer which determines the position of the decimal point +/// +/// Refer to the documentation of [`CqlDecimal`]. +/// Especially, see the disclaimer about [non-normalized values](CqlDecimal#db-data-format). +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct CqlDecimalBorrowed<'b> { + int_val: CqlVarintBorrowed<'b>, + scale: i32, +} + /// Constructors impl CqlDecimal { /// Creates a [`CqlDecimal`] from an array of bytes @@ -492,6 +509,20 @@ impl CqlDecimal { } } +/// Constructors +impl<'b> CqlDecimalBorrowed<'b> { + /// Creates a [`CqlDecimalBorrowed`] from a slice of bytes + /// representing [`CqlVarintBorrowed`] and a 32-bit scale. + /// + /// See: disclaimer about [non-normalized values](CqlVarint#db-data-format). + pub fn from_signed_be_bytes_slice_and_exponent(bytes: &'b [u8], scale: i32) -> Self { + Self { + int_val: CqlVarintBorrowed::from_signed_bytes_be_slice(bytes), + scale, + } + } +} + /// Conversion to raw bytes impl CqlDecimal { /// Returns a slice of bytes in two's complement @@ -507,6 +538,15 @@ impl CqlDecimal { } } +/// Conversion to raw bytes +impl CqlDecimalBorrowed<'_> { + /// Returns a slice of bytes in two's complement + /// binary big-endian representation and a scale. + pub fn as_signed_be_bytes_slice_and_exponent(&self) -> (&[u8], i32) { + (self.int_val.as_signed_bytes_be_slice(), self.scale) + } +} + #[cfg(feature = "bigdecimal-04")] impl From for bigdecimal_04::BigDecimal { fn from(value: CqlDecimal) -> Self { @@ -519,6 +559,18 @@ impl From for bigdecimal_04::BigDecimal { } } +#[cfg(feature = "bigdecimal-04")] +impl From> for bigdecimal_04::BigDecimal { + fn from(value: CqlDecimalBorrowed) -> Self { + Self::from(( + bigdecimal_04::num_bigint::BigInt::from_signed_bytes_be( + value.int_val.as_signed_bytes_be_slice(), + ), + value.scale as i64, + )) + } +} + #[cfg(feature = "bigdecimal-04")] impl TryFrom for CqlDecimal { type Error = >::Error; diff --git a/scylla-cql/src/types/deserialize/value.rs b/scylla-cql/src/types/deserialize/value.rs index 9ac6fabb6..f1979ab63 100644 --- a/scylla-cql/src/types/deserialize/value.rs +++ b/scylla-cql/src/types/deserialize/value.rs @@ -15,12 +15,15 @@ use std::fmt::Display; use thiserror::Error; use super::{make_error_replace_rust_name, DeserializationError, FrameSlice, TypeCheckError}; -use crate::frame::response::result::{deser_cql_value, ColumnType, CqlValue}; use crate::frame::types; use crate::frame::value::{ Counter, CqlDate, CqlDecimal, CqlDuration, CqlTime, CqlTimestamp, CqlTimeuuid, CqlVarint, }; use crate::frame::{frame_errors::LowLevelDeserializationError, value::CqlVarintBorrowed}; +use crate::frame::{ + response::result::{deser_cql_value, ColumnType, CqlValue}, + value::CqlDecimalBorrowed, +}; /// A type that can be deserialized from a column value inside a row that was /// returned from a query. @@ -269,6 +272,24 @@ impl_emptiable_strict_type!( } ); +impl_emptiable_strict_type!( + CqlDecimalBorrowed<'b>, + Decimal, + |typ: &'metadata ColumnType<'metadata>, v: Option>| { + let mut val = ensure_not_null_slice::(typ, v)?; + let scale = types::read_int(&mut val).map_err(|err| { + mk_deser_err::( + typ, + BuiltinDeserializationErrorKind::BadDecimalScale(err.into()), + ) + })?; + Ok(CqlDecimalBorrowed::from_signed_be_bytes_slice_and_exponent( + val, scale, + )) + }, + 'b +); + #[cfg(feature = "bigdecimal-04")] impl_emptiable_strict_type!( bigdecimal_04::BigDecimal, diff --git a/scylla-cql/src/types/deserialize/value_tests.rs b/scylla-cql/src/types/deserialize/value_tests.rs index fbded1e06..8e105ef08 100644 --- a/scylla-cql/src/types/deserialize/value_tests.rs +++ b/scylla-cql/src/types/deserialize/value_tests.rs @@ -10,8 +10,8 @@ use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; use crate::frame::response::result::{ColumnType, CqlValue}; use crate::frame::value::{ - Counter, CqlDate, CqlDecimal, CqlDuration, CqlTime, CqlTimestamp, CqlTimeuuid, CqlVarint, - CqlVarintBorrowed, + Counter, CqlDate, CqlDecimal, CqlDecimalBorrowed, CqlDuration, CqlTime, CqlTimestamp, + CqlTimeuuid, CqlVarint, CqlVarintBorrowed, }; use crate::types::deserialize::value::{TupleDeserializationErrorKind, TupleTypeCheckErrorKind}; use crate::types::deserialize::{DeserializationError, FrameSlice, TypeCheckError}; @@ -187,6 +187,12 @@ fn test_varlen_numbers() { &mut Bytes::new(), ); + assert_ser_de_identity( + &ColumnType::Decimal, + &CqlDecimalBorrowed::from_signed_be_bytes_slice_and_exponent(b"Ala ma kota", 42), + &mut Bytes::new(), + ); + #[cfg(feature = "bigdecimal-04")] assert_ser_de_identity( &ColumnType::Decimal, diff --git a/scylla-cql/src/types/serialize/value.rs b/scylla-cql/src/types/serialize/value.rs index 399302acf..e63016cda 100644 --- a/scylla-cql/src/types/serialize/value.rs +++ b/scylla-cql/src/types/serialize/value.rs @@ -16,8 +16,8 @@ use uuid::Uuid; use crate::frame::response::result::{ColumnType, CqlValue}; use crate::frame::types::vint_encode; use crate::frame::value::{ - Counter, CqlDate, CqlDecimal, CqlDuration, CqlTime, CqlTimestamp, CqlTimeuuid, CqlVarint, - CqlVarintBorrowed, MaybeUnset, Unset, Value, + Counter, CqlDate, CqlDecimal, CqlDecimalBorrowed, CqlDuration, CqlTime, CqlTimestamp, + CqlTimeuuid, CqlVarint, CqlVarintBorrowed, MaybeUnset, Unset, Value, }; #[cfg(feature = "chrono-04")] @@ -124,6 +124,18 @@ impl SerializeValue for CqlDecimal { .map_err(|_| mk_ser_err::(typ, BuiltinSerializationErrorKind::SizeOverflow))? }); } +impl SerializeValue for CqlDecimalBorrowed<'_> { + impl_serialize_via_writer!(|me, typ, writer| { + exact_type_check!(typ, Decimal); + let mut builder = writer.into_value_builder(); + let (bytes, scale) = me.as_signed_be_bytes_slice_and_exponent(); + builder.append_bytes(&scale.to_be_bytes()); + builder.append_bytes(bytes); + builder + .finish() + .map_err(|_| mk_ser_err::(typ, BuiltinSerializationErrorKind::SizeOverflow))? + }); +} #[cfg(feature = "bigdecimal-04")] impl SerializeValue for bigdecimal_04::BigDecimal { impl_serialize_via_writer!(|me, typ, writer| {