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/docs/source/data-types/varint.md b/docs/source/data-types/varint.md index 59a515eb6..c2309bb1b 100644 --- a/docs/source/data-types/varint.md +++ b/docs/source/data-types/varint.md @@ -7,8 +7,7 @@ To make use of `num_bigint::BigInt` type, user should enable one of the availabl ## value::CqlVarint -Without any feature flags, the user can interact with `Varint` type by making use of `value::CqlVarint` which -is a very simple wrapper representing the value as signed binary number in big-endian order. +Without any feature flags, the user can interact with `Varint` type by making use of `value::CqlVarint` or `value::CqlVarintBorrowed` which are very simple wrappers representing the value as signed binary number in big-endian order. ## Example diff --git a/scylla-cql/src/frame/value.rs b/scylla-cql/src/frame/value.rs index 036511e4e..3cf6f28cb 100644 --- a/scylla-cql/src/frame/value.rs +++ b/scylla-cql/src/frame/value.rs @@ -210,6 +210,9 @@ impl std::hash::Hash for CqlTimeuuid { /// The library support (e.g. conversion from [`CqlValue`]) for these types is /// enabled via `num-bigint-03` and `num-bigint-04` crate features. /// +/// This struct holds owned bytes. If you wish to borrow the bytes instead, +/// see [`CqlVarintBorrowed`] documentation. +/// /// # DB data format /// Notice that [constructors](CqlVarint#impl-CqlVarint) /// don't perform any normalization on the provided data. @@ -223,6 +226,13 @@ impl std::hash::Hash for CqlTimeuuid { #[derive(Clone, Eq, Debug)] pub struct CqlVarint(Vec); +/// A borrowed version of native CQL `varint` representation. +/// +/// Refer to the documentation of [`CqlVarint`]. +/// Especially, see the disclaimer about [non-normalized values](CqlVarint#db-data-format). +#[derive(Clone, Eq, Debug)] +pub struct CqlVarintBorrowed<'b>(&'b [u8]); + /// Constructors from bytes impl CqlVarint { /// Creates a [`CqlVarint`] from an array of bytes in @@ -242,6 +252,17 @@ impl CqlVarint { } } +/// Constructors from bytes +impl<'b> CqlVarintBorrowed<'b> { + /// Creates a [`CqlVarintBorrowed`] from a slice of bytes in + /// two's complement binary big-endian representation. + /// + /// See: disclaimer about [non-normalized values](CqlVarint#db-data-format). + pub fn from_signed_bytes_be_slice(digits: &'b [u8]) -> Self { + Self(digits) + } +} + /// Conversion to bytes impl CqlVarint { /// Converts [`CqlVarint`] to an array of bytes in two's @@ -257,9 +278,39 @@ impl CqlVarint { } } -impl CqlVarint { +/// Conversion to bytes +impl CqlVarintBorrowed<'_> { + /// Returns a slice of bytes in two's complement + /// binary big-endian representation. + pub fn as_signed_bytes_be_slice(&self) -> &[u8] { + self.0 + } +} + +/// An internal utility trait used to implement [`AsNormalizedVarintSlice`] +/// for both [`CqlVarint`] and [`CqlVarintBorrowed`]. +trait AsVarintSlice { + fn as_slice(&self) -> &[u8]; +} +impl AsVarintSlice for CqlVarint { + fn as_slice(&self) -> &[u8] { + self.as_signed_bytes_be_slice() + } +} +impl AsVarintSlice for CqlVarintBorrowed<'_> { + fn as_slice(&self) -> &[u8] { + self.as_signed_bytes_be_slice() + } +} + +/// An internal utility trait used to implement [`PartialEq`] and [`std::hash::Hash`] +/// for [`CqlVarint`] and [`CqlVarintBorrowed`]. +trait AsNormalizedVarintSlice { + fn as_normalized_slice(&self) -> &[u8]; +} +impl AsNormalizedVarintSlice for V { fn as_normalized_slice(&self) -> &[u8] { - let digits = self.0.as_slice(); + let digits = self.as_slice(); if digits.is_empty() { // num-bigint crate normalizes empty vector to 0. // We will follow the same approach. @@ -293,6 +344,58 @@ impl CqlVarint { } } +/// Compares two [`CqlVarint`] values after normalization. +/// +/// # Example +/// +/// ```rust +/// # use scylla_cql::frame::value::CqlVarint; +/// let non_normalized_bytes = vec![0x00, 0x01]; +/// let normalized_bytes = vec![0x01]; +/// assert_eq!( +/// CqlVarint::from_signed_bytes_be(non_normalized_bytes), +/// CqlVarint::from_signed_bytes_be(normalized_bytes) +/// ); +/// ``` +impl PartialEq for CqlVarint { + fn eq(&self, other: &Self) -> bool { + self.as_normalized_slice() == other.as_normalized_slice() + } +} + +/// Computes the hash of normalized [`CqlVarint`]. +impl std::hash::Hash for CqlVarint { + fn hash(&self, state: &mut H) { + self.as_normalized_slice().hash(state) + } +} + +/// Compares two [`CqlVarintBorrowed`] values after normalization. +/// +/// # Example +/// +/// ```rust +/// # use scylla_cql::frame::value::CqlVarintBorrowed; +/// let non_normalized_bytes = &[0x00, 0x01]; +/// let normalized_bytes = &[0x01]; +/// assert_eq!( +/// CqlVarintBorrowed::from_signed_bytes_be_slice(non_normalized_bytes), +/// CqlVarintBorrowed::from_signed_bytes_be_slice(normalized_bytes) +/// ); +/// ``` +impl PartialEq for CqlVarintBorrowed<'_> { + fn eq(&self, other: &Self) -> bool { + self.as_normalized_slice() == other.as_normalized_slice() + } +} + +/// Computes the hash of normalized [`CqlVarintBorrowed`]. +impl std::hash::Hash for CqlVarintBorrowed<'_> { + fn hash(&self, state: &mut H) { + self.as_normalized_slice().hash(state) + } +} + #[cfg(feature = "num-bigint-03")] impl From for CqlVarint { fn from(value: num_bigint_03::BigInt) -> Self { @@ -307,6 +410,13 @@ impl From for num_bigint_03::BigInt { } } +#[cfg(feature = "num-bigint-03")] +impl From> for num_bigint_03::BigInt { + fn from(val: CqlVarintBorrowed<'_>) -> Self { + num_bigint_03::BigInt::from_signed_bytes_be(val.0) + } +} + #[cfg(feature = "num-bigint-04")] impl From for CqlVarint { fn from(value: num_bigint_04::BigInt) -> Self { @@ -321,29 +431,10 @@ impl From for num_bigint_04::BigInt { } } -/// Compares two [`CqlVarint`] values after normalization. -/// -/// # Example -/// -/// ```rust -/// # use scylla_cql::frame::value::CqlVarint; -/// let non_normalized_bytes = vec![0x00, 0x01]; -/// let normalized_bytes = vec![0x01]; -/// assert_eq!( -/// CqlVarint::from_signed_bytes_be(non_normalized_bytes), -/// CqlVarint::from_signed_bytes_be(normalized_bytes) -/// ); -/// ``` -impl PartialEq for CqlVarint { - fn eq(&self, other: &Self) -> bool { - self.as_normalized_slice() == other.as_normalized_slice() - } -} - -/// Computes the hash of normalized [`CqlVarint`]. -impl std::hash::Hash for CqlVarint { - fn hash(&self, state: &mut H) { - self.as_normalized_slice().hash(state) +#[cfg(feature = "num-bigint-04")] +impl From> for num_bigint_04::BigInt { + fn from(val: CqlVarintBorrowed<'_>) -> Self { + num_bigint_04::BigInt::from_signed_bytes_be(val.0) } } @@ -353,6 +444,9 @@ impl std::hash::Hash for CqlVarint { /// - 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). @@ -369,6 +463,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 @@ -391,6 +499,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 @@ -406,6 +528,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 { @@ -418,6 +549,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 2ff5d228e..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::frame_errors::LowLevelDeserializationError; -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. @@ -222,6 +225,16 @@ impl_emptiable_strict_type!( } ); +impl_emptiable_strict_type!( + CqlVarintBorrowed<'b>, + Varint, + |typ: &'metadata ColumnType<'metadata>, v: Option>| { + let val = ensure_not_null_slice::(typ, v)?; + Ok(CqlVarintBorrowed::from_signed_bytes_be_slice(val)) + }, + 'b +); + #[cfg(feature = "num-bigint-03")] impl_emptiable_strict_type!( num_bigint_03::BigInt, @@ -259,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 e02915588..8e105ef08 100644 --- a/scylla-cql/src/types/deserialize/value_tests.rs +++ b/scylla-cql/src/types/deserialize/value_tests.rs @@ -10,7 +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, + Counter, CqlDate, CqlDecimal, CqlDecimalBorrowed, CqlDuration, CqlTime, CqlTimestamp, + CqlTimeuuid, CqlVarint, CqlVarintBorrowed, }; use crate::types::deserialize::value::{TupleDeserializationErrorKind, TupleTypeCheckErrorKind}; use crate::types::deserialize::{DeserializationError, FrameSlice, TypeCheckError}; @@ -159,6 +160,12 @@ fn test_varlen_numbers() { &mut Bytes::new(), ); + assert_ser_de_identity( + &ColumnType::Varint, + &CqlVarintBorrowed::from_signed_bytes_be_slice(b"Ala ma kota"), + &mut Bytes::new(), + ); + #[cfg(feature = "num-bigint-03")] assert_ser_de_identity( &ColumnType::Varint, @@ -180,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 ce6cc19a0..bba2667c6 100644 --- a/scylla-cql/src/types/serialize/value.rs +++ b/scylla-cql/src/types/serialize/value.rs @@ -17,8 +17,8 @@ use crate::frame::response::result::{ColumnType, CqlValue}; use crate::frame::types::vint_encode; #[allow(deprecated)] use crate::frame::value::{ - Counter, CqlDate, CqlDecimal, CqlDuration, CqlTime, CqlTimestamp, CqlTimeuuid, CqlVarint, - MaybeUnset, Unset, Value, + Counter, CqlDate, CqlDecimal, CqlDecimalBorrowed, CqlDuration, CqlTime, CqlTimestamp, + CqlTimeuuid, CqlVarint, CqlVarintBorrowed, MaybeUnset, Unset, Value, }; #[cfg(feature = "chrono-04")] @@ -125,6 +125,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| { @@ -253,6 +265,14 @@ impl SerializeValue for CqlVarint { .map_err(|_| mk_ser_err::(typ, BuiltinSerializationErrorKind::SizeOverflow))? }); } +impl SerializeValue for CqlVarintBorrowed<'_> { + impl_serialize_via_writer!(|me, typ, writer| { + exact_type_check!(typ, Varint); + writer + .set_value(me.as_signed_bytes_be_slice()) + .map_err(|_| mk_ser_err::(typ, BuiltinSerializationErrorKind::SizeOverflow))? + }); +} #[cfg(feature = "num-bigint-03")] impl SerializeValue for num_bigint_03::BigInt { impl_serialize_via_writer!(|me, typ, writer| {