cassandra-rs
cassandra-rs copied to clipboard
Decimal type related functionality is missing
I noticed the Decimal type related functionality of the driver is not exported in this crate. For example the bind_decimal
method is commented out: https://github.com/Metaswitch/cassandra-rs/blob/master/src/cassandra/statement.rs#L682
What is the reason behind that? Is there a workaround clients can use? What would need to be solved so this crate can expose that functionality?
Thanks @gszabo and apologies for slow reply. This is simply a gap - we'd welcome a PR that added it. Please feel free to propose a design here and we can advise before you start implementing - I guess the key question is what Rust type to use. The functionality is present in the underlying cassandra-sys-rs crate, so it's just this crate that needs to be updated.
I was playing around with this a little bit. Cpp driver provide 3 values: varint - address varint_size - number of bytes to read from varint scale - scale value
as an example, number 6.31: 3 bytes from varint: [0, 2, 119] => 631 as int scale: 2 631/(10^2)
This is fine, now tricky part. Cassandra can return more that 16 bytes which doesn't fit to i128. For example, this is valid cassandra decimal value:
size:17, scale:31 bytes: [0, 13, 122, 129, 44, 68, 99, 173, 234, 226, 22, 226, 101, 176, 138, 84, 65] decimal number: 1791604.4099988662637770175933837890625
Deep dive to cql docu follow me to java type BigDecimal (https://docs.oracle.com/javase/7/docs/api/java/math/BigDecimal.html) arbitrary-precision signed decimal numbers. A BigDecimal consists of an arbitrary precision integer unscaled value and a 32-bit integer scale.
I was looking for some implementation of bigdecimal crates, but I didn't found anything satisfying.
Here is my implementation of get_dec(), but I have limit to max number which can be saved to DB.
#[derive(Debug)]
pub struct Decimal {
pub value: i32,
pub scale: i32
}
pub fn get_decimal(&self) -> Result<Decimal> {
let mut varint = std::ptr::null();
let mut varint_size= 0;
let mut scale= 0;
let mut decimal_bytes: [u8; 4] = [0,0,0,0];
unsafe{
// get decimal values from c driver
if cass_value_get_decimal(self.0, &mut varint, &mut varint_size, &mut scale) != CASS_OK {
return Err(CASS_ERROR_LIB_INVALID_VALUE_TYPE.to_error());
}
// check if bytes fits to i32
if varint_size > 4 {
return Err(CASS_ERROR_LIB_INVALID_VALUE_TYPE.to_error());
}
// read varint_size bytes from varint and convert to vec
let var_bytes: Vec<u8> = std::slice::from_raw_parts( varint, varint_size).to_vec();
// fill decimal_bytes with var_bytes on shifted position base on varint_size
let mut current_byte_loc = 0;
for n in (4-varint_size)..4 {
decimal_bytes[n] = var_bytes[current_byte_loc];
current_byte_loc = current_byte_loc + 1;
}
}
// convert decimal_bytes to i32
let value: i32 = i32::from_be_bytes(decimal_bytes);
// create decimal struct
let decimal = Decimal {
value: value,
scale: scale,
};
return Ok(decimal);
}
Thanks. I'd rather we didn't roll our own bignum type. A quick search on crates.io turned up two likely options:
- https://crates.io/crates/bigdecimal - this is pure Rust
- https://crates.io/crates/rug - this is built on the GNU MP (multiprecision) library, so it brings a C library dependency
There were others, but they looked less well maintained or used. (I may have missed some though.) There's an interesting table here https://github.com/rust-num/num-bigint#alternatives but many of those are just big integer libraries; they don't do decimals. There's also num-rational, but that doesn't have any special handling for decimals, so (e.g.) printing wouldn't come out right.
I prefer to avoid additional dependencies, so prefer https://crates.io/crates/bigdecimal . What do you think?
This pass my tests, but more tests are required...
pub fn get_decimal(&self) -> Result<BigDecimal> {
let mut varint = std::ptr::null();
let mut varint_size= 0;
let mut scale= 0;
let slice = unsafe{
// get decimal values from c driver
if cass_value_get_decimal(self.0, &mut varint, &mut varint_size, &mut scale) != cassandra_cpp_sys::CassError::CASS_OK {
return Err(CASS_ERROR_LIB_INVALID_VALUE_TYPE.to_error());
}
slice::from_raw_parts(varint, varint_size)
};
let bigint = BigInt::from_signed_bytes_be(slice);
let bigdec = BigDecimal::new(bigint, scale as i64);
return Ok(bigdec);
}
Thanks - that looks perfectly fine to me :-)
this can be closed
Thanks for flagging! Indeed, fixed in #177 .