edgedb-protocol 0.6.1

Low-level protocol implemenentation for EdgeDB database client. Use edgedb-tokio for applications instead.
Documentation
use super::BigInt;
use crate::model::OutOfRangeError;

impl std::convert::TryFrom<num_bigint::BigInt> for BigInt {
    type Error = OutOfRangeError;
    fn try_from(v: num_bigint::BigInt) -> Result<BigInt, Self::Error> {
        use num_traits::{ToPrimitive, Zero};
        use std::convert::TryInto;

        if v.is_zero() {
            return Ok(BigInt {
                negative: false,
                weight: 0,
                digits: Vec::new(),
            });
        }

        let mut digits = Vec::new();
        let (negative, mut val) = match v.sign() {
            num_bigint::Sign::Minus => (true, -v),
            num_bigint::Sign::NoSign => (false, v),
            num_bigint::Sign::Plus => (false, v),
        };
        while !val.is_zero() {
            digits.push((&val % 10000u16).to_u16().unwrap());
            val /= 10000;
        }
        digits.reverse();

        // This returns "out of range integral type conversion attempted"
        // which should be good enough for this error
        let weight = (digits.len() - 1).try_into()?;

        // TODO(tailhook) normalization can be optimized here
        Ok(BigInt {
            negative,
            weight,
            digits,
        }
        .normalize())
    }
}

impl From<BigInt> for num_bigint::BigInt {
    fn from(v: BigInt) -> num_bigint::BigInt {
        (&v).into()
    }
}

impl From<&BigInt> for num_bigint::BigInt {
    fn from(v: &BigInt) -> num_bigint::BigInt {
        use num_bigint::BigInt;
        use num_traits::pow;

        let mut r = BigInt::from(0);
        for &digit in &v.digits {
            r *= 10000;
            r += digit;
        }
        if (v.weight + 1) as usize > v.digits.len() {
            r *= pow(
                BigInt::from(10000),
                (v.weight + 1) as usize - v.digits.len(),
            );
        }
        if v.negative {
            -r
        } else {
            r
        }
    }
}

#[cfg(test)]
mod test {
    use super::BigInt;
    use std::convert::TryFrom;
    use std::str::FromStr;

    #[test]
    fn big_big_int_conversion() -> Result<(), Box<dyn std::error::Error>> {
        let x = BigInt::try_from(num_bigint::BigInt::from_str(
            "10000000000000000000000000000000000000",
        )?)?;
        assert_eq!(x.weight, 9);
        assert_eq!(&x.digits, &[10]);
        Ok(())
    }
}

// conceptually these tests work on BigInt, but depend on the bigdecimal feature
#[cfg(all(test, feature = "bigdecimal"))]
mod test_with_decimal {
    use super::super::test_helpers::gen_i64;
    use super::BigInt;
    use bigdecimal::BigDecimal;
    use num_bigint::ToBigInt;
    use rand::{rngs::StdRng, Rng, SeedableRng};
    use std::convert::TryFrom;
    use std::str::FromStr;

    #[test]
    fn bigint_conversion() -> Result<(), Box<dyn std::error::Error>> {
        let x = BigInt::try_from(BigDecimal::from_str("1e20")?.to_bigint().unwrap())?;
        assert_eq!(x.weight, 5);
        assert_eq!(x.digits, &[1]);
        Ok(())
    }

    fn int_roundtrip(s: &str) -> num_bigint::BigInt {
        let decimal = BigDecimal::from_str(s).expect("can parse decimal");
        let rust = decimal.to_bigint().expect("can convert to big int");
        let edgedb = BigInt::try_from(rust).expect("can convert for edgedb");
        num_bigint::BigInt::from(edgedb)
    }

    #[test]
    fn big_int_roundtrip() -> Result<(), Box<dyn std::error::Error>> {
        use num_bigint::BigInt as N;

        assert_eq!(int_roundtrip("1"), N::from_str("1")?);
        assert_eq!(int_roundtrip("1000"), N::from_str("1000")?);
        assert_eq!(int_roundtrip("1e20"), N::from_str("100000000000000000000")?);
        assert_eq!(int_roundtrip("0"), N::from_str("0")?);
        assert_eq!(int_roundtrip("-1000"), N::from_str("-1000")?);
        assert_eq!(
            int_roundtrip("10000000000000000000000000000000000000000000"),
            N::from_str("10000000000000000000000000000000000000000000")?
        );
        assert_eq!(
            int_roundtrip("12345678901234567890012345678901234567890123"),
            N::from_str("12345678901234567890012345678901234567890123")?
        );
        assert_eq!(
            int_roundtrip("10000000000000000000000000000000000000"),
            N::from_str("10000000000000000000000000000000000000")?
        );
        Ok(())
    }

    #[test]
    fn int_rand_i64() -> Result<(), Box<dyn std::error::Error>> {
        use num_bigint::BigInt as B;

        let mut rng = StdRng::seed_from_u64(7);
        for _ in 0..10000 {
            let head = gen_i64(&mut rng);
            let txt = format!("{}", head);
            assert_eq!(int_roundtrip(&txt), B::from_str(&txt)?, "parsing: {}", txt);
        }
        Ok(())
    }

    #[test]
    fn int_rand_nulls() -> Result<(), Box<dyn std::error::Error>> {
        use num_bigint::BigInt as B;

        let mut rng = StdRng::seed_from_u64(8);
        for iter in 0..10000 {
            let head = gen_i64(&mut rng);
            let nulls = rng.gen_range(0..100);
            let txt = format!("{0}{1:0<2$}", head, "", nulls);
            assert_eq!(
                int_roundtrip(&txt),
                B::from_str(&txt)?,
                "parsing {}: {}",
                iter,
                txt
            );
        }
        Ok(())
    }

    #[test]
    fn int_rand_eplus() -> Result<(), Box<dyn std::error::Error>> {
        use num_bigint::BigInt as B;

        let mut rng = StdRng::seed_from_u64(9);
        for iter in 0..10000 {
            let head = gen_i64(&mut rng);
            let nulls = rng.gen_range(0..100);
            let edb = format!("{}e{}", head, nulls);
            let bigint = format!("{}{1:0<2$}", head, "", nulls);
            assert_eq!(
                int_roundtrip(&edb),
                B::from_str(&bigint)?,
                "parsing {}: {}",
                iter,
                edb
            );
        }
        Ok(())
    }

    #[test]
    fn int_rand_nulls_eplus() -> Result<(), Box<dyn std::error::Error>> {
        use num_bigint::BigInt as B;

        let mut rng = StdRng::seed_from_u64(10);
        for iter in 0..10000 {
            let head = gen_i64(&mut rng);
            let nulls1 = rng.gen_range(0..100);
            let nulls2 = rng.gen_range(0..100);
            let edb = format!("{0}{1:0<2$}e{3}", head, "", nulls1, nulls2);
            let bigint = format!("{}{1:0<2$}", head, "", nulls1 + nulls2);
            assert_eq!(
                int_roundtrip(&edb),
                B::from_str(&bigint)?,
                "parsing {}: {}",
                iter,
                edb
            );
        }
        Ok(())
    }
}