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

graph, store: bump num-bigint and bigdecimal #2781

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
34 changes: 29 additions & 5 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions graph/Cargo.toml
Expand Up @@ -7,7 +7,10 @@ edition = "2018"
anyhow = "1.0"
async-trait = "0.1.50"
atomic_refcell = "0.1.7"
bigdecimal = { version = "0.1.0", features = ["serde"] }
bigdecimal01 = { package = "bigdecimal", version = "0.1.0" }
bigdecimal = { version = "0.3.0", features = ["serde"] }
num-bigint02 = { package = "num-bigint", version = "^0.2.6" }
num-bigint = { version = "^0.4.2", features = ["serde"] }
bytes = "1.0.1"
diesel = { version = "1.4.7", features = ["postgres", "serde_json", "numeric", "r2d2", "chrono"] }
diesel_derives = "1.4"
Expand All @@ -29,7 +32,6 @@ futures = "0.1.21"
graphql-parser = "0.3.0"
lazy_static = "1.4.0"
mockall = "0.8.3"
num-bigint = { version = "^0.2.6", features = ["serde"] }
num_cpus = "1.13.0"
num-traits = "0.2.14"
rand = "0.6.1"
Expand Down
125 changes: 83 additions & 42 deletions graph/src/data/store/scalar.rs
Expand Up @@ -3,6 +3,7 @@ use diesel::serialize::ToSql;
use diesel_derives::{AsExpression, FromSqlRow};
use hex;
use num_bigint;
use num_traits::Signed;
use serde::{self, Deserialize, Serialize};
use thiserror::Error;
use web3::types::*;
Expand Down Expand Up @@ -64,25 +65,9 @@ impl BigDecimal {
self.0.digits()
}

// Copy-pasted from `bigdecimal::BigDecimal::normalize`. We can use the upstream version once it
// is included in a released version supported by Diesel.
#[must_use]
pub fn normalized(&self) -> BigDecimal {
if self == &BigDecimal::zero() {
return BigDecimal::zero();
}

// Round to the maximum significant digits.
let big_decimal = self.0.with_prec(Self::MAX_SIGNFICANT_DIGITS as u64);

let (bigint, exp) = big_decimal.as_bigint_and_exponent();
let (sign, mut digits) = bigint.to_radix_be(10);
let trailing_count = digits.iter().rev().take_while(|i| **i == 0).count();
digits.truncate(digits.len() - trailing_count);
let int_val = num_bigint::BigInt::from_radix_be(sign, &digits, 10).unwrap();
let scale = exp - trailing_count as i64;

BigDecimal(bigdecimal::BigDecimal::new(int_val.into(), scale))
Self::from(self.0.normalized())
}
}

Expand Down Expand Up @@ -118,9 +103,13 @@ impl From<u64> for BigDecimal {
}
}

impl From<f64> for BigDecimal {
fn from(n: f64) -> Self {
Self::from(bigdecimal::BigDecimal::from(n))
impl TryFrom<f64> for BigDecimal {
type Error = BigDecimalError;
fn try_from(n: f64) -> Result<Self, Self::Error> {
use bigdecimal::FromPrimitive;

let decimal = bigdecimal::BigDecimal::from_f64(n).ok_or(BigDecimalError::FromF64(n))?;
Ok(Self::from(decimal))
}
}

Expand Down Expand Up @@ -160,21 +149,74 @@ impl Div for BigDecimal {
}
}

pub type OldBigInt = num_bigint02::BigInt;
pub type OldBigDecimal = bigdecimal01::BigDecimal;

pub struct BigDecimalRetrocompatibility;

impl BigDecimalRetrocompatibility {
/// Converts a BigDecimal from bigdecimal v0.3 to a BigDecimal from bigdecimal v0.1
pub fn v03_to_v01(newer: &bigdecimal::BigDecimal) -> OldBigDecimal {
let convert_sign_versions = |sign| match sign {
num_bigint::Sign::Minus => num_bigint02::Sign::Minus,
num_bigint::Sign::NoSign => num_bigint02::Sign::NoSign,
num_bigint::Sign::Plus => num_bigint02::Sign::Plus,
};

// let (sign, biguint) = newer.into_parts();
let (big_int, scale) = newer.as_bigint_and_exponent();

// Converting into little-endian bytes is faster
let (sign, le_bytes) = big_int.to_bytes_le();

// let bytes = biguint.to_bytes_be();
let sign = convert_sign_versions(sign);

let new_big_int = OldBigInt::from_bytes_le(sign, &le_bytes);

OldBigDecimal::new(new_big_int, scale)
}

pub fn v01_to_v03(older: OldBigDecimal) -> bigdecimal::BigDecimal {
let convert_sign_versions = |sign| match sign {
num_bigint02::Sign::Minus => num_bigint::Sign::Minus,
num_bigint02::Sign::NoSign => num_bigint::Sign::NoSign,
num_bigint02::Sign::Plus => num_bigint::Sign::Plus,
};

let (big_int, scale) = older.into_bigint_and_exponent();

// Converting into little-endian bytes is faster
let (sign, le_bytes) = big_int.to_bytes_le();

// let bytes = biguint.to_bytes_be();
let sign = convert_sign_versions(sign);

let new_big_int = num_bigint::BigInt::from_bytes_le(sign, &le_bytes);

bigdecimal::BigDecimal::new(new_big_int, scale)
}
}

// Used only for JSONB support
impl ToSql<diesel::sql_types::Numeric, diesel::pg::Pg> for BigDecimal {
fn to_sql<W: Write>(
&self,
out: &mut diesel::serialize::Output<W, diesel::pg::Pg>,
) -> diesel::serialize::Result {
<_ as ToSql<diesel::sql_types::Numeric, _>>::to_sql(&self.0, out)
let decimal01 = BigDecimalRetrocompatibility::v03_to_v01(&self.0);

<_ as ToSql<diesel::sql_types::Numeric, _>>::to_sql(&decimal01, out)
}
}

impl FromSql<diesel::sql_types::Numeric, diesel::pg::Pg> for BigDecimal {
fn from_sql(
bytes: Option<&<diesel::pg::Pg as diesel::backend::Backend>::RawValue>,
) -> diesel::deserialize::Result<Self> {
Ok(Self::from(bigdecimal::BigDecimal::from_sql(bytes)?))
let decimal01 = bigdecimal01::BigDecimal::from_sql(bytes)?;
let decimal03 = BigDecimalRetrocompatibility::v01_to_v03(decimal01);
Ok(Self::from(decimal03))
}
}

Expand Down Expand Up @@ -219,27 +261,24 @@ pub enum BigIntOutOfRangeError {
Overflow,
}

#[derive(Error, Debug)]
pub enum BigDecimalError {
#[error("{0}")]
OutOfRange(BigIntOutOfRangeError),
#[error("Failed to create big decimal from {0}")]
FromF64(f64),
}

impl<'a> TryFrom<&'a BigInt> for u64 {
type Error = BigIntOutOfRangeError;
fn try_from(value: &'a BigInt) -> Result<u64, BigIntOutOfRangeError> {
let (sign, bytes) = value.to_bytes_le();

if sign == num_bigint::Sign::Minus {
return Err(BigIntOutOfRangeError::Negative);
}

if bytes.len() > 8 {
return Err(BigIntOutOfRangeError::Overflow);
}

// Replace this with u64::from_le_bytes when stabilized
let mut n = 0u64;
let mut shift_dist = 0;
for b in bytes {
n = ((b as u64) << shift_dist) | n;
shift_dist += 8;
if value.0.is_positive() {
(&value.0)
.try_into()
.or(Err(BigIntOutOfRangeError::Overflow))
} else {
Err(BigIntOutOfRangeError::Negative)
}
Ok(n)
}
}

Expand Down Expand Up @@ -322,7 +361,7 @@ impl BigInt {
BigInt(self.0.pow(&exponent))
}

pub fn bits(&self) -> usize {
pub fn bits(&self) -> u64 {
self.0.bits()
}
}
Expand Down Expand Up @@ -468,15 +507,17 @@ impl Shl<u8> for BigInt {
type Output = Self;

fn shl(self, bits: u8) -> Self {
Self::from(self.0.shl(bits.into()))
// let t = self.0.shl(bits.into());
let t = self.0 << bits;
Self(t)
}
}

impl Shr<u8> for BigInt {
type Output = Self;

fn shr(self, bits: u8) -> Self {
Self::from(self.0.shr(bits.into()))
Self::from(self.0 >> bits)
}
}

Expand Down
2 changes: 1 addition & 1 deletion graph/src/util/cache_weight.rs
Expand Up @@ -91,7 +91,7 @@ impl CacheWeight for BigDecimal {

impl CacheWeight for BigInt {
fn indirect_weight(&self) -> usize {
self.bits() / 8
(self.bits() / 8) as usize
}
}

Expand Down
13 changes: 11 additions & 2 deletions store/postgres/tests/graft.rs
@@ -1,6 +1,8 @@
use std::convert::TryInto;
use std::{marker::PhantomData, str::FromStr};

use hex_literal::hex;
use lazy_static::lazy_static;
use std::{marker::PhantomData, str::FromStr};
use test_store::*;

use graph::components::store::{
Expand Down Expand Up @@ -221,7 +223,14 @@ fn create_test_entity(
"seconds_age".to_owned(),
Value::BigInt(BigInt::from(age) * 31557600.into()),
);
test_entity.insert("weight".to_owned(), Value::BigDecimal(weight.into()));
test_entity.insert(
"weight".to_owned(),
Value::BigDecimal(
weight
.try_into()
.expect("failed to convert f64 into big decimal"),
),
);
test_entity.insert("coffee".to_owned(), Value::Bool(coffee));
test_entity.insert(
"favorite_color".to_owned(),
Expand Down