From d541339a239f3d85a2eb3c0c961d152bb4aa24d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Uzarski?= Date: Wed, 11 Dec 2024 12:01:03 +0100 Subject: [PATCH 1/3] value: move From conversions after PartialEq and Hash impls I believe this is more readable, and should simplify the diff from the following commit. --- scylla-cql/src/frame/value.rs | 52 +++++++++++++++++------------------ 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/scylla-cql/src/frame/value.rs b/scylla-cql/src/frame/value.rs index a6b6c1c7f..75c557d7b 100644 --- a/scylla-cql/src/frame/value.rs +++ b/scylla-cql/src/frame/value.rs @@ -303,6 +303,32 @@ 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) + } +} + #[cfg(feature = "num-bigint-03")] impl From for CqlVarint { fn from(value: num_bigint_03::BigInt) -> Self { @@ -331,32 +357,6 @@ 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) - } -} - /// Native CQL `decimal` representation. /// /// Represented as a pair: From 10add756cc816a422196822a19ea721be2c980e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Uzarski?= Date: Wed, 11 Dec 2024 12:00:29 +0100 Subject: [PATCH 2/3] value: introduce CqlVarintBorrowed This is a native representation of varint that holds borrowed bytes. Implemented SerializeValue and DeserializeValue for new type. --- docs/source/data-types/varint.md | 3 +- scylla-cql/src/frame/value.rs | 95 ++++++++++++++++++- scylla-cql/src/types/deserialize/value.rs | 12 ++- .../src/types/deserialize/value_tests.rs | 7 ++ scylla-cql/src/types/serialize/value.rs | 10 +- 5 files changed, 121 insertions(+), 6 deletions(-) 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 75c557d7b..0cac789b1 100644 --- a/scylla-cql/src/frame/value.rs +++ b/scylla-cql/src/frame/value.rs @@ -220,6 +220,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. @@ -233,6 +236,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 @@ -252,6 +262,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 @@ -267,9 +288,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. @@ -329,6 +380,32 @@ impl std::hash::Hash for CqlVarint { } } +/// 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 { @@ -343,6 +420,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 { @@ -357,6 +441,13 @@ impl From for num_bigint_04::BigInt { } } +#[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) + } +} + /// Native CQL `decimal` representation. /// /// Represented as a pair: diff --git a/scylla-cql/src/types/deserialize/value.rs b/scylla-cql/src/types/deserialize/value.rs index 2ff5d228e..9ac6fabb6 100644 --- a/scylla-cql/src/types/deserialize/value.rs +++ b/scylla-cql/src/types/deserialize/value.rs @@ -15,12 +15,12 @@ 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}; /// A type that can be deserialized from a column value inside a row that was /// returned from a query. @@ -222,6 +222,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, diff --git a/scylla-cql/src/types/deserialize/value_tests.rs b/scylla-cql/src/types/deserialize/value_tests.rs index e02915588..fbded1e06 100644 --- a/scylla-cql/src/types/deserialize/value_tests.rs +++ b/scylla-cql/src/types/deserialize/value_tests.rs @@ -11,6 +11,7 @@ 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, }; 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, diff --git a/scylla-cql/src/types/serialize/value.rs b/scylla-cql/src/types/serialize/value.rs index 78e169aa4..399302acf 100644 --- a/scylla-cql/src/types/serialize/value.rs +++ b/scylla-cql/src/types/serialize/value.rs @@ -17,7 +17,7 @@ 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, - MaybeUnset, Unset, Value, + CqlVarintBorrowed, MaybeUnset, Unset, Value, }; #[cfg(feature = "chrono-04")] @@ -252,6 +252,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| { From 5808f5ac4a491e90d64a36c90fcdd9ae97beb811 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Uzarski?= Date: Wed, 11 Dec 2024 12:25:00 +0100 Subject: [PATCH 3/3] value: introduce CqlDecimalBorrowed Has the same semantics as CqlDecimal, but borrows the bytes. --- docs/source/data-types/decimal.md | 2 +- scylla-cql/src/frame/value.rs | 52 +++++++++++++++++++ scylla-cql/src/types/deserialize/value.rs | 23 +++++++- .../src/types/deserialize/value_tests.rs | 10 +++- scylla-cql/src/types/serialize/value.rs | 16 +++++- 5 files changed, 97 insertions(+), 6 deletions(-) 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| {