Skip to content

Commit

Permalink
Merge pull request scylladb#1148 from muzarski/cql-varint-decimal-bor…
Browse files Browse the repository at this point in the history
…rowed

value: `Cql[Varint/Decimal]Borrowed`
  • Loading branch information
wprzytula authored Dec 15, 2024
2 parents 86904de + 5808f5a commit 847e44d
Show file tree
Hide file tree
Showing 6 changed files with 239 additions and 33 deletions.
2 changes: 1 addition & 1 deletion docs/source/data-types/decimal.md
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
3 changes: 1 addition & 2 deletions docs/source/data-types/varint.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
193 changes: 168 additions & 25 deletions scylla-cql/src/frame/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -223,6 +226,13 @@ impl std::hash::Hash for CqlTimeuuid {
#[derive(Clone, Eq, Debug)]
pub struct CqlVarint(Vec<u8>);

/// 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
Expand All @@ -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
Expand All @@ -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<V: AsVarintSlice> 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.
Expand Down Expand Up @@ -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<H: std::hash::Hasher>(&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<H: std::hash::Hasher>(&self, state: &mut H) {
self.as_normalized_slice().hash(state)
}
}

#[cfg(feature = "num-bigint-03")]
impl From<num_bigint_03::BigInt> for CqlVarint {
fn from(value: num_bigint_03::BigInt) -> Self {
Expand All @@ -307,6 +410,13 @@ impl From<CqlVarint> for num_bigint_03::BigInt {
}
}

#[cfg(feature = "num-bigint-03")]
impl From<CqlVarintBorrowed<'_>> 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<num_bigint_04::BigInt> for CqlVarint {
fn from(value: num_bigint_04::BigInt) -> Self {
Expand All @@ -321,29 +431,10 @@ impl From<CqlVarint> 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<H: std::hash::Hasher>(&self, state: &mut H) {
self.as_normalized_slice().hash(state)
#[cfg(feature = "num-bigint-04")]
impl From<CqlVarintBorrowed<'_>> for num_bigint_04::BigInt {
fn from(val: CqlVarintBorrowed<'_>) -> Self {
num_bigint_04::BigInt::from_signed_bytes_be(val.0)
}
}

Expand All @@ -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).
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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<CqlDecimal> for bigdecimal_04::BigDecimal {
fn from(value: CqlDecimal) -> Self {
Expand All @@ -418,6 +549,18 @@ impl From<CqlDecimal> for bigdecimal_04::BigDecimal {
}
}

#[cfg(feature = "bigdecimal-04")]
impl From<CqlDecimalBorrowed<'_>> 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<bigdecimal_04::BigDecimal> for CqlDecimal {
type Error = <i64 as TryInto<i32>>::Error;
Expand Down
35 changes: 33 additions & 2 deletions scylla-cql/src/types/deserialize/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -222,6 +225,16 @@ impl_emptiable_strict_type!(
}
);

impl_emptiable_strict_type!(
CqlVarintBorrowed<'b>,
Varint,
|typ: &'metadata ColumnType<'metadata>, v: Option<FrameSlice<'frame>>| {
let val = ensure_not_null_slice::<Self>(typ, v)?;
Ok(CqlVarintBorrowed::from_signed_bytes_be_slice(val))
},
'b
);

#[cfg(feature = "num-bigint-03")]
impl_emptiable_strict_type!(
num_bigint_03::BigInt,
Expand Down Expand Up @@ -259,6 +272,24 @@ impl_emptiable_strict_type!(
}
);

impl_emptiable_strict_type!(
CqlDecimalBorrowed<'b>,
Decimal,
|typ: &'metadata ColumnType<'metadata>, v: Option<FrameSlice<'frame>>| {
let mut val = ensure_not_null_slice::<Self>(typ, v)?;
let scale = types::read_int(&mut val).map_err(|err| {
mk_deser_err::<Self>(
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,
Expand Down
15 changes: 14 additions & 1 deletion scylla-cql/src/types/deserialize/value_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand Down
Loading

0 comments on commit 847e44d

Please sign in to comment.