Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

value: Cql[Varint/Decimal]Borrowed #1148

Merged
merged 3 commits into from
Dec 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -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.
Expand All @@ -233,6 +236,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]);

muzarski marked this conversation as resolved.
Show resolved Hide resolved
/// Constructors from bytes
impl CqlVarint {
/// Creates a [`CqlVarint`] from an array of bytes in
Expand All @@ -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
Expand All @@ -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<V: AsVarintSlice> AsNormalizedVarintSlice for V {
muzarski marked this conversation as resolved.
Show resolved Hide resolved
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 @@ -303,6 +354,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 @@ -317,6 +420,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 @@ -331,29 +441,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 @@ -363,6 +454,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 @@ -379,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
Expand All @@ -401,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
Expand All @@ -416,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<CqlDecimal> for bigdecimal_04::BigDecimal {
fn from(value: CqlDecimal) -> Self {
Expand All @@ -428,6 +559,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
Loading