cassandra-rs icon indicating copy to clipboard operation
cassandra-rs copied to clipboard

Decimal type related functionality is missing

Open gszabo opened this issue 3 years ago • 1 comments

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?

gszabo avatar Jul 09 '21 12:07 gszabo

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.

kw217 avatar Aug 06 '21 14:08 kw217

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);
            
    }

M5135213521asd3 avatar Nov 28 '23 09:11 M5135213521asd3

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?

kw217 avatar Dec 01 '23 10:12 kw217

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);
    }

M5135213521asd3 avatar Dec 05 '23 15:12 M5135213521asd3

Thanks - that looks perfectly fine to me :-)

kw217 avatar Dec 15 '23 10:12 kw217

this can be closed

M5135213521asd3 avatar Mar 21 '24 13:03 M5135213521asd3

Thanks for flagging! Indeed, fixed in #177 .

kw217 avatar Mar 21 '24 15:03 kw217