Skip to content

Commit e94f8f9

Browse files
authored
Merge pull request #922 from muzarski/bigdecimal-feature
Hide public usages of bigdecimal::BigDecimal behind a crate feature
2 parents 9a84387 + 10bd785 commit e94f8f9

File tree

16 files changed

+245
-50
lines changed

16 files changed

+245
-50
lines changed

.github/workflows/rust.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ jobs:
4444
run: cargo check --all-targets --manifest-path "scylla/Cargo.toml" --features "num-bigint-03"
4545
- name: Cargo check with num-bigint-04 feature
4646
run: cargo check --all-targets --manifest-path "scylla/Cargo.toml" --features "num-bigint-04"
47+
- name: Cargo check with bigdecimal-04 feature
48+
run: cargo check --all-targets --manifest-path "scylla/Cargo.toml" --features "bigdecimal-04"
4749
- name: Build scylla-cql
4850
run: cargo build --verbose --all-targets --manifest-path "scylla-cql/Cargo.toml" --features "full-serialization"
4951
- name: Build

Cargo.lock.msrv

Lines changed: 11 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/source/data-types/data-types.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ Database types and their Rust equivalents:
2626
* `Time` <----> `value::CqlTime`, `chrono::NaiveTime`, `time::Time`
2727
* `Timestamp` <----> `value::CqlTimestamp`, `chrono::DateTime<Utc>`, `time::OffsetDateTime`
2828
* `Duration` <----> `value::CqlDuration`
29-
* `Decimal` <----> `bigdecimal::Decimal`
29+
* `Decimal` <----> `value::CqlDecimal`, `bigdecimal::Decimal`
3030
* `Varint` <----> `value::CqlVarint`, `num_bigint::BigInt` (v0.3 and v0.4)
3131
* `List` <----> `Vec<T>`
3232
* `Set` <----> `Vec<T>`

docs/source/data-types/decimal.md

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,39 @@
11
# Decimal
2-
`Decimal` is represented as [`bigdecimal::BigDecimal`](https://docs.rs/bigdecimal/0.2.0/bigdecimal/struct.BigDecimal.html)
2+
`Decimal` is represented as `value::CqlDecimal` or [`bigdecimal::BigDecimal`](https://docs.rs/bigdecimal/latest/bigdecimal/struct.BigDecimal.html)
3+
4+
## value::CqlDecimal
5+
6+
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.
7+
8+
```rust
9+
# extern crate scylla;
10+
# use scylla::Session;
11+
# use std::error::Error;
12+
# async fn check_only_compiles(session: &Session) -> Result<(), Box<dyn Error>> {
13+
use scylla::IntoTypedRows;
14+
use scylla::frame::value::CqlDecimal;
15+
use std::str::FromStr;
16+
17+
// Insert a decimal (123.456) into the table
18+
let to_insert: CqlDecimal =
19+
CqlDecimal::from_signed_be_bytes_and_exponent(vec![0x01, 0xE2, 0x40], 3);
20+
session
21+
.query("INSERT INTO keyspace.table (a) VALUES(?)", (to_insert,))
22+
.await?;
23+
24+
// Read a decimal from the table
25+
if let Some(rows) = session.query("SELECT a FROM keyspace.table", &[]).await?.rows {
26+
for row in rows.into_typed::<(CqlDecimal,)>() {
27+
let (decimal_value,): (CqlDecimal,) = row?;
28+
}
29+
}
30+
# Ok(())
31+
# }
32+
```
33+
34+
## bigdecimal::BigDecimal
35+
36+
To make use of `bigdecimal::Bigdecimal` type, user should enable `bigdecimal-04` crate feature.
337

438
```rust
539
# extern crate scylla;

docs/source/quickstart/create-project.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ scylla = "0.11"
1212
tokio = { version = "1.12", features = ["full"] }
1313
futures = "0.3.6"
1414
uuid = "1.0"
15-
bigdecimal = "0.2.0"
15+
bigdecimal = "0.4"
1616
num-bigint = "0.3"
1717
tracing = "0.1.36"
1818
tracing-subscriber = { version = "0.3.14", features = ["env-filter"] }

examples/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ futures = "0.3.6"
1010
openssl = "0.10.32"
1111
rustyline = "9"
1212
rustyline-derive = "0.6"
13-
scylla = {path = "../scylla", features = ["ssl", "cloud", "chrono", "time", "num-bigint-03", "num-bigint-04"]}
13+
scylla = {path = "../scylla", features = ["ssl", "cloud", "chrono", "time", "num-bigint-03", "num-bigint-04", "bigdecimal-04"]}
1414
tokio = {version = "1.1.0", features = ["full"]}
1515
tracing = "0.1.25"
1616
tracing-subscriber = { version = "0.3.14", features = ["env-filter"] }

scylla-cql/Cargo.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ secrecy = { version = "0.7.0", optional = true }
1919
snap = "1.0"
2020
uuid = "1.0"
2121
thiserror = "1.0"
22-
bigdecimal = "0.2.0"
2322
num-bigint-03 = { package = "num-bigint", version = "0.3", optional = true }
2423
num-bigint-04 = { package = "num-bigint", version = "0.4", optional = true }
24+
bigdecimal-04 = { package = "bigdecimal", version = "0.4", optional = true }
2525
chrono = { version = "0.4.27", default-features = false, optional = true }
2626
lz4_flex = { version = "0.11.1" }
2727
async-trait = "0.1.57"
@@ -43,4 +43,5 @@ time = ["dep:time"]
4343
chrono = ["dep:chrono"]
4444
num-bigint-03 = ["dep:num-bigint-03"]
4545
num-bigint-04 = ["dep:num-bigint-04"]
46-
full-serialization = ["chrono", "time", "secret", "num-bigint-03", "num-bigint-04"]
46+
bigdecimal-04 = ["dep:bigdecimal-04"]
47+
full-serialization = ["chrono", "time", "secret", "num-bigint-03", "num-bigint-04", "bigdecimal-04"]

scylla-cql/src/frame/response/cql_to_rust.rs

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
use super::result::{CqlValue, Row};
22
use crate::frame::value::{
3-
Counter, CqlDate, CqlDuration, CqlTime, CqlTimestamp, CqlTimeuuid, CqlVarint,
3+
Counter, CqlDate, CqlDecimal, CqlDuration, CqlTime, CqlTimestamp, CqlTimeuuid, CqlVarint,
44
};
5-
use bigdecimal::BigDecimal;
65
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
76
use std::hash::{BuildHasher, Hash};
87
use std::net::IpAddr;
@@ -136,7 +135,7 @@ impl_from_cql_value_from_method!(Vec<u8>, into_blob); // Vec<u8>::from_cql<CqlVa
136135
impl_from_cql_value_from_method!(IpAddr, as_inet); // IpAddr::from_cql<CqlValue>
137136
impl_from_cql_value_from_method!(Uuid, as_uuid); // Uuid::from_cql<CqlValue>
138137
impl_from_cql_value_from_method!(CqlTimeuuid, as_timeuuid); // CqlTimeuuid::from_cql<CqlValue>
139-
impl_from_cql_value_from_method!(BigDecimal, into_decimal); // BigDecimal::from_cql<CqlValue>
138+
impl_from_cql_value_from_method!(CqlDecimal, into_cql_decimal); // CqlDecimal::from_cql<CqlValue>
140139
impl_from_cql_value_from_method!(CqlDuration, as_cql_duration); // CqlDuration::from_cql<CqlValue>
141140
impl_from_cql_value_from_method!(CqlDate, as_cql_date); // CqlDate::from_cql<CqlValue>
142141
impl_from_cql_value_from_method!(CqlTime, as_cql_time); // CqlTime::from_cql<CqlValue>
@@ -169,6 +168,16 @@ impl FromCqlVal<CqlValue> for num_bigint_04::BigInt {
169168
}
170169
}
171170

171+
#[cfg(feature = "bigdecimal-04")]
172+
impl FromCqlVal<CqlValue> for bigdecimal_04::BigDecimal {
173+
fn from_cql(cql_val: CqlValue) -> Result<Self, FromCqlValError> {
174+
match cql_val {
175+
CqlValue::Decimal(cql_decimal) => Ok(cql_decimal.into()),
176+
_ => Err(FromCqlValError::BadCqlType),
177+
}
178+
}
179+
}
180+
172181
#[cfg(feature = "chrono")]
173182
impl FromCqlVal<CqlValue> for NaiveDate {
174183
fn from_cql(cql_val: CqlValue) -> Result<Self, FromCqlValError> {
@@ -414,7 +423,6 @@ mod tests {
414423
use crate as scylla;
415424
use crate::frame::value::{Counter, CqlDate, CqlDuration, CqlTime, CqlTimestamp, CqlTimeuuid};
416425
use crate::macros::FromRow;
417-
use bigdecimal::BigDecimal;
418426
use std::collections::HashSet;
419427
use std::net::{IpAddr, Ipv4Addr};
420428
use std::str::FromStr;
@@ -502,12 +510,13 @@ mod tests {
502510
);
503511
}
504512

513+
#[cfg(feature = "bigdecimal-04")]
505514
#[test]
506515
fn decimal_from_cql() {
507-
let decimal = BigDecimal::from_str("123.4").unwrap();
516+
let decimal = bigdecimal_04::BigDecimal::from_str("123.4").unwrap();
508517
assert_eq!(
509518
Ok(decimal.clone()),
510-
BigDecimal::from_cql(CqlValue::Decimal(decimal))
519+
bigdecimal_04::BigDecimal::from_cql(CqlValue::Decimal(decimal.try_into().unwrap()))
511520
);
512521
}
513522

scylla-cql/src/frame/response/result.rs

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,9 @@ use crate::cql_to_rust::{FromRow, FromRowError};
22
use crate::frame::response::event::SchemaChangeEvent;
33
use crate::frame::types::vint_decode;
44
use crate::frame::value::{
5-
Counter, CqlDate, CqlDuration, CqlTime, CqlTimestamp, CqlTimeuuid, CqlVarint,
5+
Counter, CqlDate, CqlDecimal, CqlDuration, CqlTime, CqlTimestamp, CqlTimeuuid, CqlVarint,
66
};
77
use crate::frame::{frame_errors::ParseError, types};
8-
use bigdecimal::BigDecimal;
98
use byteorder::{BigEndian, ReadBytesExt};
109
use bytes::{Buf, Bytes};
1110
use std::{
@@ -82,7 +81,7 @@ pub enum CqlValue {
8281
Boolean(bool),
8382
Blob(Vec<u8>),
8483
Counter(Counter),
85-
Decimal(BigDecimal),
84+
Decimal(CqlDecimal),
8685
/// Days since -5877641-06-23 i.e. 2^31 days before unix epoch
8786
/// Can be converted to chrono::NaiveDate (-262145-1-1 to 262143-12-31) using as_date
8887
Date(CqlDate),
@@ -371,7 +370,7 @@ impl CqlValue {
371370
}
372371
}
373372

374-
pub fn into_decimal(self) -> Option<BigDecimal> {
373+
pub fn into_cql_decimal(self) -> Option<CqlDecimal> {
375374
match self {
376375
Self::Decimal(i) => Some(i),
377376
_ => None,
@@ -671,9 +670,10 @@ pub fn deser_cql_value(typ: &ColumnType, buf: &mut &[u8]) -> StdResult<CqlValue,
671670
CqlValue::Counter(crate::frame::value::Counter(buf.read_i64::<BigEndian>()?))
672671
}
673672
Decimal => {
674-
let scale = types::read_int(buf)? as i64;
675-
let int_value = bigdecimal::num_bigint::BigInt::from_signed_bytes_be(buf);
676-
let big_decimal: BigDecimal = BigDecimal::from((int_value, scale));
673+
let scale = types::read_int(buf)?;
674+
let bytes = buf.to_vec();
675+
let big_decimal: CqlDecimal =
676+
CqlDecimal::from_signed_be_bytes_and_exponent(bytes, scale);
677677

678678
CqlValue::Decimal(big_decimal)
679679
}
@@ -967,7 +967,6 @@ pub fn deserialize(buf: &mut &[u8]) -> StdResult<Result, ParseError> {
967967
mod tests {
968968
use crate as scylla;
969969
use crate::frame::value::{Counter, CqlDate, CqlDuration, CqlTime, CqlTimestamp, CqlTimeuuid};
970-
use bigdecimal::BigDecimal;
971970
use scylla::frame::response::result::{ColumnType, CqlValue};
972971
use std::str::FromStr;
973972
use uuid::Uuid;
@@ -1111,8 +1110,10 @@ mod tests {
11111110
}
11121111
}
11131112

1113+
#[cfg(feature = "bigdecimal-04")]
11141114
#[test]
11151115
fn test_decimal() {
1116+
use bigdecimal_04::BigDecimal;
11161117
struct Test<'a> {
11171118
value: BigDecimal,
11181119
encoding: &'a [u8],
@@ -1139,7 +1140,10 @@ mod tests {
11391140

11401141
for t in tests.iter() {
11411142
let value = super::deser_cql_value(&ColumnType::Decimal, &mut &*t.encoding).unwrap();
1142-
assert_eq!(CqlValue::Decimal(t.value.clone()), value);
1143+
assert_eq!(
1144+
CqlValue::Decimal(t.value.clone().try_into().unwrap()),
1145+
value
1146+
);
11431147
}
11441148
}
11451149

scylla-cql/src/frame/value.rs

Lines changed: 110 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
use crate::frame::frame_errors::ParseError;
22
use crate::frame::types;
3-
use bigdecimal::BigDecimal;
43
use bytes::BufMut;
54
use std::borrow::Cow;
65
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
@@ -365,6 +364,91 @@ impl std::hash::Hash for CqlVarint {
365364
}
366365
}
367366

367+
/// Native CQL `decimal` representation.
368+
///
369+
/// Represented as a pair:
370+
/// - a [`CqlVarint`] value
371+
/// - 32-bit integer which determines the position of the decimal point
372+
///
373+
/// The type is not very useful in most use cases.
374+
/// However, users can make use of more complex types
375+
/// such as `bigdecimal::BigDecimal` (v0.4).
376+
/// The library support (e.g. conversion from [`CqlValue`]) for the type is
377+
/// enabled via `bigdecimal-04` crate feature.
378+
///
379+
/// # DB data format
380+
/// Notice that [constructors](CqlDecimal#impl-CqlDecimal)
381+
/// don't perform any normalization on the provided data.
382+
/// For more details, see [`CqlVarint`] documentation.
383+
#[derive(Clone, PartialEq, Eq, Debug)]
384+
pub struct CqlDecimal {
385+
int_val: CqlVarint,
386+
scale: i32,
387+
}
388+
389+
/// Constructors
390+
impl CqlDecimal {
391+
/// Creates a [`CqlDecimal`] from an array of bytes
392+
/// representing [`CqlVarint`] and a 32-bit scale.
393+
///
394+
/// See: disclaimer about [non-normalized values](CqlVarint#db-data-format).
395+
pub fn from_signed_be_bytes_and_exponent(bytes: Vec<u8>, scale: i32) -> Self {
396+
Self {
397+
int_val: CqlVarint::from_signed_bytes_be(bytes),
398+
scale,
399+
}
400+
}
401+
402+
/// Creates a [`CqlDecimal`] from a slice of bytes
403+
/// representing [`CqlVarint`] and a 32-bit scale.
404+
///
405+
/// See: disclaimer about [non-normalized values](CqlVarint#db-data-format).
406+
pub fn from_signed_be_bytes_slice_and_exponent(bytes: &[u8], scale: i32) -> Self {
407+
Self::from_signed_be_bytes_and_exponent(bytes.to_vec(), scale)
408+
}
409+
}
410+
411+
/// Conversion to raw bytes
412+
impl CqlDecimal {
413+
/// Returns a slice of bytes in two's complement
414+
/// binary big-endian representation and a scale.
415+
pub fn as_signed_be_bytes_slice_and_exponent(&self) -> (&[u8], i32) {
416+
(self.int_val.as_signed_bytes_be_slice(), self.scale)
417+
}
418+
419+
/// Converts [`CqlDecimal`] to an array of bytes in two's
420+
/// complement binary big-endian representation and a scale.
421+
pub fn into_signed_be_bytes_and_exponent(self) -> (Vec<u8>, i32) {
422+
(self.int_val.into_signed_bytes_be(), self.scale)
423+
}
424+
}
425+
426+
#[cfg(feature = "bigdecimal-04")]
427+
impl From<CqlDecimal> for bigdecimal_04::BigDecimal {
428+
fn from(value: CqlDecimal) -> Self {
429+
Self::from((
430+
bigdecimal_04::num_bigint::BigInt::from_signed_bytes_be(
431+
value.int_val.as_signed_bytes_be_slice(),
432+
),
433+
value.scale as i64,
434+
))
435+
}
436+
}
437+
438+
#[cfg(feature = "bigdecimal-04")]
439+
impl TryFrom<bigdecimal_04::BigDecimal> for CqlDecimal {
440+
type Error = <i64 as TryInto<i32>>::Error;
441+
442+
fn try_from(value: bigdecimal_04::BigDecimal) -> Result<Self, Self::Error> {
443+
let (bigint, scale) = value.into_bigint_and_exponent();
444+
let bytes = bigint.to_signed_bytes_be();
445+
Ok(Self::from_signed_be_bytes_and_exponent(
446+
bytes,
447+
scale.try_into()?,
448+
))
449+
}
450+
}
451+
368452
/// Native CQL date representation that allows for a bigger range of dates (-262145-1-1 to 262143-12-31).
369453
///
370454
/// Represented as number of days since -5877641-06-23 i.e. 2^31 days before unix epoch.
@@ -881,14 +965,36 @@ impl Value for i64 {
881965
}
882966
}
883967

884-
impl Value for BigDecimal {
968+
impl Value for CqlDecimal {
969+
fn serialize(&self, buf: &mut Vec<u8>) -> Result<(), ValueTooBig> {
970+
let (bytes, scale) = self.as_signed_be_bytes_slice_and_exponent();
971+
972+
if bytes.len() > (i32::MAX - 4) as usize {
973+
return Err(ValueTooBig);
974+
}
975+
let serialized_len: i32 = bytes.len() as i32 + 4;
976+
977+
buf.put_i32(serialized_len);
978+
buf.put_i32(scale);
979+
buf.extend_from_slice(bytes);
980+
981+
Ok(())
982+
}
983+
}
984+
985+
#[cfg(feature = "bigdecimal-04")]
986+
impl Value for bigdecimal_04::BigDecimal {
885987
fn serialize(&self, buf: &mut Vec<u8>) -> Result<(), ValueTooBig> {
886988
let (value, scale) = self.as_bigint_and_exponent();
887989

888990
let serialized = value.to_signed_bytes_be();
889-
let serialized_len: i32 = serialized.len().try_into().map_err(|_| ValueTooBig)?;
890991

891-
buf.put_i32(serialized_len + 4);
992+
if serialized.len() > (i32::MAX - 4) as usize {
993+
return Err(ValueTooBig);
994+
}
995+
let serialized_len: i32 = serialized.len() as i32 + 4;
996+
997+
buf.put_i32(serialized_len);
892998
buf.put_i32(scale.try_into().map_err(|_| ValueTooBig)?);
893999
buf.extend_from_slice(&serialized);
8941000

0 commit comments

Comments
 (0)