diff --git a/Cargo.lock b/Cargo.lock index 54278869353..67990cd2079 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -488,9 +488,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.25" +version = "0.4.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdbc37d37da9e5bce8173f3a41b71d9bf3c674deebbaceacd0ebdabde76efb03" +checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" dependencies = [ "android-tzdata", "iana-time-zone", @@ -3497,9 +3497,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" -version = "1.0.28" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" +checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500" dependencies = [ "proc-macro2", ] @@ -4740,9 +4740,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.19.9" +version = "0.19.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92d964908cec0d030b812013af25a0e57fddfadb1e066ecc6681d86253129d4f" +checksum = "2380d56e8670370eee6566b0bfd4265f65b3f432e8c6d85623f728d4fa31f739" dependencies = [ "indexmap", "serde", diff --git a/graph/src/data/store/mod.rs b/graph/src/data/store/mod.rs index 3044ec13699..706331afd37 100644 --- a/graph/src/data/store/mod.rs +++ b/graph/src/data/store/mod.rs @@ -138,6 +138,7 @@ pub type Attribute = String; pub const BYTES_SCALAR: &str = "Bytes"; pub const BIG_INT_SCALAR: &str = "BigInt"; pub const BIG_DECIMAL_SCALAR: &str = "BigDecimal"; +pub const INT8_SCALAR: &str = "Int8"; #[derive(Clone, Debug, PartialEq)] pub enum ValueType { @@ -146,6 +147,7 @@ pub enum ValueType { Bytes, BigDecimal, Int, + Int8, String, } @@ -159,6 +161,7 @@ impl FromStr for ValueType { "Bytes" => Ok(ValueType::Bytes), "BigDecimal" => Ok(ValueType::BigDecimal), "Int" => Ok(ValueType::Int), + "Int8" => Ok(ValueType::Int8), "String" | "ID" => Ok(ValueType::String), s => Err(anyhow!("Type not available in this context: {}", s)), } @@ -187,6 +190,7 @@ pub enum IdType { pub enum Value { String(String), Int(i32), + Int8(i64), BigDecimal(scalar::BigDecimal), Bool(bool), List(Vec), @@ -224,6 +228,9 @@ impl stable_hash_legacy::StableHash for Value { Int(inner) => { stable_hash_legacy::StableHash::stable_hash(inner, sequence_number, state) } + Int8(inner) => { + stable_hash_legacy::StableHash::stable_hash(inner, sequence_number, state) + } BigDecimal(inner) => { stable_hash_legacy::StableHash::stable_hash(inner, sequence_number, state) } @@ -282,6 +289,10 @@ impl StableHash for Value { inner.stable_hash(field_address.child(0), state); 7 } + Int8(inner) => { + inner.stable_hash(field_address.child(0), state); + 8 + } }; state.write(field_address, &[variant]) @@ -325,6 +336,9 @@ impl Value { QueryExecutionError::ValueParseError("BigInt".to_string(), format!("{}", e)) })?), BIG_DECIMAL_SCALAR => Value::BigDecimal(scalar::BigDecimal::from_str(s)?), + INT8_SCALAR => Value::Int8(s.parse::().map_err(|_| { + QueryExecutionError::ValueParseError("Int8".to_string(), format!("{}", s)) + })?), _ => Value::String(s.clone()), } } @@ -416,6 +430,7 @@ impl Value { Value::Bool(_) => "Boolean".to_owned(), Value::Bytes(_) => "Bytes".to_owned(), Value::Int(_) => "Int".to_owned(), + Value::Int8(_) => "Int8".to_owned(), Value::List(values) => { if let Some(v) = values.first() { format!("[{}]", v.type_name()) @@ -436,6 +451,7 @@ impl Value { | (Value::Bool(_), ValueType::Boolean) | (Value::Bytes(_), ValueType::Bytes) | (Value::Int(_), ValueType::Int) + | (Value::Int8(_), ValueType::Int8) | (Value::Null, _) => true, (Value::List(values), _) if is_list => values .iter() @@ -457,6 +473,7 @@ impl fmt::Display for Value { match self { Value::String(s) => s.to_string(), Value::Int(i) => i.to_string(), + Value::Int8(i) => i.to_string(), Value::BigDecimal(d) => d.to_string(), Value::Bool(b) => b.to_string(), Value::Null => "null".to_string(), @@ -474,6 +491,7 @@ impl fmt::Debug for Value { match self { Self::String(s) => f.debug_tuple("String").field(s).finish(), Self::Int(i) => f.debug_tuple("Int").field(i).finish(), + Self::Int8(i) => f.debug_tuple("Int8").field(i).finish(), Self::BigDecimal(d) => d.fmt(f), Self::Bool(arg0) => f.debug_tuple("Bool").field(arg0).finish(), Self::List(arg0) => f.debug_tuple("List").field(arg0).finish(), @@ -489,6 +507,7 @@ impl From for q::Value { match value { Value::String(s) => q::Value::String(s), Value::Int(i) => q::Value::Int(q::Number::from(i)), + Value::Int8(i) => q::Value::String(i.to_string()), Value::BigDecimal(d) => q::Value::String(d.to_string()), Value::Bool(b) => q::Value::Boolean(b), Value::Null => q::Value::Null, @@ -506,6 +525,7 @@ impl From for r::Value { match value { Value::String(s) => r::Value::String(s), Value::Int(i) => r::Value::Int(i as i64), + Value::Int8(i) => r::Value::String(i.to_string()), Value::BigDecimal(d) => r::Value::String(d.to_string()), Value::Bool(b) => r::Value::Boolean(b), Value::Null => r::Value::Null, @@ -572,6 +592,12 @@ impl From for Value { } } +impl From for Value { + fn from(value: i64) -> Value { + Value::Int8(value.into()) + } +} + impl TryFrom for Option { type Error = Error; diff --git a/graph/src/data/store/sql.rs b/graph/src/data/store/sql.rs index 2df61a8ebe9..b7c1e4c3096 100644 --- a/graph/src/data/store/sql.rs +++ b/graph/src/data/store/sql.rs @@ -1,7 +1,7 @@ use anyhow::anyhow; use diesel::pg::Pg; use diesel::serialize::{self, Output, ToSql}; -use diesel::sql_types::{Binary, Bool, Integer, Text}; +use diesel::sql_types::{Binary, Bool, Int8, Integer, Text}; use std::io::Write; use std::str::FromStr; @@ -34,6 +34,19 @@ impl ToSql for Value { } } +impl ToSql for Value { + fn to_sql(&self, out: &mut Output) -> serialize::Result { + match self { + Value::Int8(i) => >::to_sql(i, out), + v => Err(anyhow!( + "Failed to convert non-int8 attribute value to int8 in SQL: {}", + v + ) + .into()), + } + } +} + impl ToSql for Value { fn to_sql(&self, out: &mut Output) -> serialize::Result { match self { diff --git a/graph/src/data/value.rs b/graph/src/data/value.rs index 365c1c7eecc..960db616429 100644 --- a/graph/src/data/value.rs +++ b/graph/src/data/value.rs @@ -330,6 +330,8 @@ impl Value { Err(Value::Int(num)) } } + ("Int8", Value::Int(num)) => Ok(Value::String(num.to_string())), + ("Int8", Value::String(num)) => Ok(Value::String(num)), ("String", Value::String(s)) => Ok(Value::String(s)), ("ID", Value::String(s)) => Ok(Value::String(s)), ("ID", Value::Int(n)) => Ok(Value::String(n.to_string())), diff --git a/graph/src/runtime/gas/size_of.rs b/graph/src/runtime/gas/size_of.rs index 59a5b2cba47..1ec140e4f9c 100644 --- a/graph/src/runtime/gas/size_of.rs +++ b/graph/src/runtime/gas/size_of.rs @@ -15,6 +15,7 @@ impl GasSizeOf for Value { Value::Null => Gas(1), Value::List(list) => list.gas_size_of(), Value::Int(int) => int.gas_size_of(), + Value::Int8(int) => int.gas_size_of(), Value::Bytes(bytes) => bytes.gas_size_of(), Value::Bool(bool) => bool.gas_size_of(), Value::BigInt(big_int) => big_int.gas_size_of(), diff --git a/graph/src/schema/api.rs b/graph/src/schema/api.rs index 7b38c92e564..3d084f79c92 100644 --- a/graph/src/schema/api.rs +++ b/graph/src/schema/api.rs @@ -587,6 +587,7 @@ fn field_scalar_filter_input_values( "BigDecimal" => vec!["", "not", "gt", "lt", "gte", "lte", "in", "not_in"], "ID" => vec!["", "not", "gt", "lt", "gte", "lte", "in", "not_in"], "Int" => vec!["", "not", "gt", "lt", "gte", "lte", "in", "not_in"], + "Int8" => vec!["", "not", "gt", "lt", "gte", "lte", "in", "not_in"], "String" => vec![ "", "not", @@ -1114,6 +1115,9 @@ mod tests { schema .get_named_type("String") .expect("String type is missing in API schema"); + schema + .get_named_type("Int8") + .expect("Int8 type is missing in API schema"); } #[test] diff --git a/graph/src/schema/meta.graphql b/graph/src/schema/meta.graphql index b2b5ffd9a87..ff543b1f9c8 100644 --- a/graph/src/schema/meta.graphql +++ b/graph/src/schema/meta.graphql @@ -1,6 +1,7 @@ # GraphQL core functionality scalar Boolean scalar ID +""" 4 bytes signed integer """ scalar Int scalar Float scalar String @@ -19,9 +20,12 @@ directive @subgraphId(id: String!) on OBJECT "creates a virtual field on the entity that may be queried but cannot be set manually through the mappings API." directive @derivedFrom(field: String!) on FIELD_DEFINITION +# Additional scalar types scalar BigDecimal scalar Bytes scalar BigInt +""" 8 bytes signed integer """ +scalar Int8 # The type names are purposely awkward to minimize the risk of them # colliding with user-supplied types diff --git a/graph/src/util/cache_weight.rs b/graph/src/util/cache_weight.rs index af15a82b25d..57a68a3205e 100644 --- a/graph/src/util/cache_weight.rs +++ b/graph/src/util/cache_weight.rs @@ -93,7 +93,7 @@ impl CacheWeight for Value { Value::List(values) => values.indirect_weight(), Value::Bytes(bytes) => bytes.indirect_weight(), Value::BigInt(n) => n.indirect_weight(), - Value::Int(_) | Value::Bool(_) | Value::Null => 0, + Value::Int8(_) | Value::Int(_) | Value::Bool(_) | Value::Null => 0, } } } diff --git a/graphql/src/values/coercion.rs b/graphql/src/values/coercion.rs index 257a70c67f9..343c7264494 100644 --- a/graphql/src/values/coercion.rs +++ b/graphql/src/values/coercion.rs @@ -42,6 +42,10 @@ impl MaybeCoercible for q::Value { Err(q::Value::Int(num)) } } + ("Int8", q::Value::Int(num)) => { + let n = num.as_i64().ok_or_else(|| q::Value::Int(num.clone()))?; + Ok(r::Value::Int(n)) + } ("String", q::Value::String(s)) => Ok(r::Value::String(s)), ("ID", q::Value::String(s)) => Ok(r::Value::String(s)), ("ID", q::Value::Int(n)) => Ok(r::Value::String( @@ -395,6 +399,21 @@ mod tests { ); } + #[test] + fn coerce_int8_scalar() { + let int8_type = TypeDefinition::Scalar(ScalarType::new("Int8".to_string())); + let resolver = |_: &str| Some(&int8_type); + + assert_eq!( + coerce_to_definition(Value::Int(1234.into()), "", &resolver), + Ok(Value::String("1234".to_string())) + ); + assert_eq!( + coerce_to_definition(Value::Int((-1234_i32).into()), "", &resolver,), + Ok(Value::String("-1234".to_string())) + ); + } + #[test] fn coerce_bytes_scalar() { let bytes_type = TypeDefinition::Scalar(ScalarType::new("Bytes".to_string())); diff --git a/runtime/test/src/test/abi.rs b/runtime/test/src/test/abi.rs index 5d8e2a1b864..43cda3ccdac 100644 --- a/runtime/test/src/test/abi.rs +++ b/runtime/test/src/test/abi.rs @@ -291,6 +291,12 @@ async fn test_abi_store_value(api_version: Version) { let new_value: Value = module.asc_get(new_value_ptr).unwrap(); assert_eq!(new_value, Value::Int(int)); + // Value::Int8 + let int8 = i64::min_value(); + let new_value_ptr = module.takes_val_returns_ptr("value_from_int8", int8); + let new_value: Value = module.asc_get(new_value_ptr).unwrap(); + assert_eq!(new_value, Value::Int8(int8)); + // Value::BigDecimal let big_decimal = BigDecimal::from_str("3.14159001").unwrap(); let new_value_ptr = module.invoke_export1("value_from_big_decimal", &big_decimal); diff --git a/runtime/test/wasm_test/api_version_0_0_4/abi_store_value.ts b/runtime/test/wasm_test/api_version_0_0_4/abi_store_value.ts index 69e67eab20a..d95a0402a3b 100644 --- a/runtime/test/wasm_test/api_version_0_0_4/abi_store_value.ts +++ b/runtime/test/wasm_test/api_version_0_0_4/abi_store_value.ts @@ -11,6 +11,7 @@ enum ValueKind { NULL = 5, BYTES = 6, BIG_INT = 7, + INT8 = 8, } // Big enough to fit any pointer or native `this.data`. @@ -43,6 +44,13 @@ export function value_from_int(int: i32): Value { return value } +export function value_from_int8(int: i64): Value { + let value = new Value(); + value.kind = ValueKind.INT8; + value.data = int as i64 + return value +} + export function value_from_big_decimal(float: BigInt): Value { let value = new Value(); value.kind = ValueKind.BIG_DECIMAL; diff --git a/runtime/test/wasm_test/api_version_0_0_4/abi_store_value.wasm b/runtime/test/wasm_test/api_version_0_0_4/abi_store_value.wasm index 635271b6f39..8e2be6f47a6 100644 Binary files a/runtime/test/wasm_test/api_version_0_0_4/abi_store_value.wasm and b/runtime/test/wasm_test/api_version_0_0_4/abi_store_value.wasm differ diff --git a/runtime/test/wasm_test/api_version_0_0_5/abi_store_value.ts b/runtime/test/wasm_test/api_version_0_0_5/abi_store_value.ts index 4a2a58b99d7..25c96b5b741 100644 --- a/runtime/test/wasm_test/api_version_0_0_5/abi_store_value.ts +++ b/runtime/test/wasm_test/api_version_0_0_5/abi_store_value.ts @@ -15,6 +15,14 @@ export function value_from_int(int: i32): Value { return value } +export function value_from_int8(int: i64): Value { + let value = new Value(); + value.kind = ValueKind.INT8; + value.data = int as i64 + return value +} + + export function value_from_big_decimal(float: BigInt): Value { let value = new Value(); value.kind = ValueKind.BIG_DECIMAL; diff --git a/runtime/test/wasm_test/api_version_0_0_5/abi_store_value.wasm b/runtime/test/wasm_test/api_version_0_0_5/abi_store_value.wasm index 5ac9f91d55d..69e3621f090 100644 Binary files a/runtime/test/wasm_test/api_version_0_0_5/abi_store_value.wasm and b/runtime/test/wasm_test/api_version_0_0_5/abi_store_value.wasm differ diff --git a/runtime/test/wasm_test/api_version_0_0_5/common/types.ts b/runtime/test/wasm_test/api_version_0_0_5/common/types.ts index b69e83487ea..ab1acadf0a6 100644 --- a/runtime/test/wasm_test/api_version_0_0_5/common/types.ts +++ b/runtime/test/wasm_test/api_version_0_0_5/common/types.ts @@ -30,6 +30,7 @@ export enum ValueKind { NULL = 5, BYTES = 6, BIG_INT = 7, + INT8 = 8 } // Big enough to fit any pointer or native `this.data`. export type Payload = u64 diff --git a/runtime/wasm/src/asc_abi/class.rs b/runtime/wasm/src/asc_abi/class.rs index 95c14b1bfb3..7da371e9d34 100644 --- a/runtime/wasm/src/asc_abi/class.rs +++ b/runtime/wasm/src/asc_abi/class.rs @@ -428,6 +428,12 @@ impl From for f64 { } } +impl From for i64 { + fn from(payload: EnumPayload) -> i64 { + payload.0 as i64 + } +} + impl From for bool { fn from(payload: EnumPayload) -> bool { payload.0 != 0 @@ -546,6 +552,7 @@ pub enum StoreValueKind { Null, Bytes, BigInt, + Int8, } impl StoreValueKind { @@ -555,6 +562,7 @@ impl StoreValueKind { match value { Value::String(_) => StoreValueKind::String, Value::Int(_) => StoreValueKind::Int, + Value::Int8(_) => StoreValueKind::Int8, Value::BigDecimal(_) => StoreValueKind::BigDecimal, Value::Bool(_) => StoreValueKind::Bool, Value::List(_) => StoreValueKind::Array, diff --git a/runtime/wasm/src/to_from/external.rs b/runtime/wasm/src/to_from/external.rs index d8eca138743..83452369e33 100644 --- a/runtime/wasm/src/to_from/external.rs +++ b/runtime/wasm/src/to_from/external.rs @@ -257,6 +257,7 @@ impl FromAscObj> for store::Value { Value::String(asc_get(heap, ptr, gas, depth)?) } StoreValueKind::Int => Value::Int(i32::from(payload)), + StoreValueKind::Int8 => Value::Int8(i64::from(payload)), StoreValueKind::BigDecimal => { let ptr: AscPtr = AscPtr::from(payload); Value::BigDecimal(asc_get(heap, ptr, gas, depth)?) @@ -292,6 +293,7 @@ impl ToAscObj> for store::Value { let payload = match self { Value::String(string) => asc_new(heap, string.as_str(), gas)?.into(), Value::Int(n) => EnumPayload::from(*n), + Value::Int8(n) => EnumPayload::from(*n), Value::BigDecimal(n) => asc_new(heap, n, gas)?.into(), Value::Bool(b) => EnumPayload::from(*b), Value::List(array) => asc_new(heap, array.as_slice(), gas)?.into(), diff --git a/store/postgres/examples/layout.rs b/store/postgres/examples/layout.rs index f166e891630..5cbf312cdbf 100644 --- a/store/postgres/examples/layout.rs +++ b/store/postgres/examples/layout.rs @@ -53,6 +53,7 @@ fn print_diesel_tables(layout: &Layout) { ColumnType::BigDecimal | ColumnType::BigInt => "Numeric", ColumnType::Bytes => "Binary", ColumnType::Int => "Integer", + ColumnType::Int8 => "Int8", ColumnType::String | ColumnType::Enum(_) | ColumnType::TSVector(_) => "Text", } .to_owned(); @@ -72,6 +73,7 @@ fn print_diesel_tables(layout: &Layout) { ColumnType::BigDecimal | ColumnType::BigInt => "BigDecimal", ColumnType::Bytes => "Vec", ColumnType::Int => "i32", + ColumnType::Int8 => "i64", ColumnType::String | ColumnType::Enum(_) | ColumnType::TSVector(_) => "String", } .to_owned(); diff --git a/store/postgres/src/relational.rs b/store/postgres/src/relational.rs index 02d657fb3c1..8593242db3e 100644 --- a/store/postgres/src/relational.rs +++ b/store/postgres/src/relational.rs @@ -1030,6 +1030,7 @@ pub enum ColumnType { BigInt, Bytes, Int, + Int8, String, TSVector(FulltextConfig), Enum(EnumType), @@ -1087,6 +1088,7 @@ impl ColumnType { ValueType::BigInt => Ok(ColumnType::BigInt), ValueType::Bytes => Ok(ColumnType::Bytes), ValueType::Int => Ok(ColumnType::Int), + ValueType::Int8 => Ok(ColumnType::Int8), ValueType::String => Ok(ColumnType::String), } } @@ -1098,6 +1100,7 @@ impl ColumnType { ColumnType::BigInt => "numeric", ColumnType::Bytes => "bytea", ColumnType::Int => "integer", + ColumnType::Int8 => "int8", ColumnType::String => "text", ColumnType::TSVector(_) => "tsvector", ColumnType::Enum(enum_type) => enum_type.name.as_str(), diff --git a/store/postgres/src/relational_queries.rs b/store/postgres/src/relational_queries.rs index 48eda4903f1..6f4e6f8ebf3 100644 --- a/store/postgres/src/relational_queries.rs +++ b/store/postgres/src/relational_queries.rs @@ -9,7 +9,7 @@ use diesel::pg::{Pg, PgConnection}; use diesel::query_builder::{AstPass, QueryFragment, QueryId}; use diesel::query_dsl::{LoadQuery, RunQueryDsl}; use diesel::result::{Error as DieselError, QueryResult}; -use diesel::sql_types::{Array, BigInt, Binary, Bool, Integer, Jsonb, Range, Text}; +use diesel::sql_types::{Array, BigInt, Binary, Bool, Int8, Integer, Jsonb, Range, Text}; use diesel::Connection; use graph::components::store::write::WriteChunk; @@ -289,6 +289,8 @@ pub trait FromColumnValue: Sized + std::fmt::Debug { fn from_i32(i: i32) -> Self; + fn from_i64(i: i64) -> Self; + fn from_big_decimal(d: scalar::BigDecimal) -> Self; fn from_big_int(i: serde_json::Number) -> Result; @@ -318,6 +320,13 @@ pub trait FromColumnValue: Sized + std::fmt::Debug { number ))), }, + (j::Number(number), ColumnType::Int8) => match number.as_i64() { + Some(i) => Ok(Self::from_i64(i)), + None => Err(StoreError::Unknown(anyhow!( + "failed to convert {} to Int8", + number + ))), + }, (j::Number(number), ColumnType::BigDecimal) => { let s = number.to_string(); scalar::BigDecimal::from_str(s.as_str()) @@ -379,6 +388,10 @@ impl FromColumnValue for r::Value { r::Value::Int(i.into()) } + fn from_i64(i: i64) -> Self { + r::Value::String(i.to_string()) + } + fn from_big_decimal(d: scalar::BigDecimal) -> Self { r::Value::String(d.to_string()) } @@ -424,6 +437,10 @@ impl FromColumnValue for graph::prelude::Value { graph::prelude::Value::Int(i) } + fn from_i64(i: i64) -> Self { + graph::prelude::Value::Int8(i) + } + fn from_big_decimal(d: scalar::BigDecimal) -> Self { graph::prelude::Value::BigDecimal(d) } @@ -562,6 +579,15 @@ impl<'a> QueryFragment for QueryValue<'a> { match self.0 { Value::String(s) => match &column_type { ColumnType::String => out.push_bind_param::(s), + ColumnType::Int8 => { + out.push_bind_param::(&s.parse::().map_err(|e| { + constraint_violation!( + "failed to convert `{}` to an Int8: {}", + s, + e.to_string() + ) + })?) + } ColumnType::Enum(enum_type) => { out.push_bind_param::(s)?; out.push_sql("::"); @@ -584,6 +610,7 @@ impl<'a> QueryFragment for QueryValue<'a> { ), }, Value::Int(i) => out.push_bind_param::(i), + Value::Int8(i) => out.push_bind_param::(i), Value::BigDecimal(d) => { out.push_bind_param::(&d.to_string())?; out.push_sql("::numeric"); @@ -601,6 +628,7 @@ impl<'a> QueryFragment for QueryValue<'a> { ColumnType::Boolean => out.push_bind_param::, _>(values), ColumnType::Bytes => out.push_bind_param::, _>(values), ColumnType::Int => out.push_bind_param::, _>(values), + ColumnType::Int8 => out.push_bind_param::, _>(&values), ColumnType::String => out.push_bind_param::, _>(values), ColumnType::Enum(enum_type) => { out.push_bind_param::, _>(values)?; @@ -1179,6 +1207,7 @@ impl<'a> QueryFilter<'a> { Value::Null | Value::BigDecimal(_) | Value::Int(_) + | Value::Int8(_) | Value::Bool(_) | Value::BigInt(_) => { let filter = match negated { @@ -1249,6 +1278,7 @@ impl<'a> QueryFilter<'a> { | Value::Bytes(_) | Value::BigDecimal(_) | Value::Int(_) + | Value::Int8(_) | Value::String(_) => QueryValue(value, &column.column_type).walk_ast(out)?, Value::Bool(_) | Value::List(_) | Value::Null => { return Err(UnsupportedFilter { @@ -1388,6 +1418,7 @@ impl<'a> QueryFilter<'a> { | Value::Bytes(_) | Value::BigDecimal(_) | Value::Int(_) + | Value::Int8(_) | Value::List(_) | Value::Null => { return Err(UnsupportedFilter { diff --git a/store/test-store/tests/graphql/query.rs b/store/test-store/tests/graphql/query.rs index b9b08d6528a..086874291a9 100644 --- a/store/test-store/tests/graphql/query.rs +++ b/store/test-store/tests/graphql/query.rs @@ -172,6 +172,7 @@ fn test_schema(id: DeploymentHash, id_type: IdType) -> InputSchema { mainBand: Band bands: [Band!]! writtenSongs: [Song!]! @derivedFrom(field: \"writtenBy\") + favoriteCount: Int8! } type Band @entity { @@ -341,8 +342,8 @@ async fn insert_test_entities( ( "Musician", vec![ - entity! { is => id: "m1", name: "John", mainBand: "b1", bands: vec!["b1", "b2"] }, - entity! { is => id: "m2", name: "Lisa", mainBand: "b1", bands: vec!["b1"] }, + entity! { is => id: "m1", name: "John", mainBand: "b1", bands: vec!["b1", "b2"], favoriteCount: 10 }, + entity! { is => id: "m2", name: "Lisa", mainBand: "b1", bands: vec!["b1"], favoriteCount: 100 }, ], ), ("Publisher", vec![entity! { is => id: "0xb1" }]), @@ -437,8 +438,8 @@ async fn insert_test_entities( let entities1 = vec![( "Musician", vec![ - entity! { is => id: "m3", name: "Tom", mainBand: "b2", bands: vec!["b1", "b2"] }, - entity! { is => id: "m4", name: "Valerie", bands: Vec::::new() }, + entity! { is => id: "m3", name: "Tom", mainBand: "b2", bands: vec!["b1", "b2"], favoriteCount: 5 }, + entity! { is => id: "m4", name: "Valerie", bands: Vec::::new(), favoriteCount: 20 }, ], )]; let entities1 = insert_ops(entities1); @@ -642,6 +643,7 @@ fn can_query_one_to_one_relationship() { mainBand { name } + favoriteCount } songStats(first: 100, orderBy: id) { id @@ -658,10 +660,10 @@ fn can_query_one_to_one_relationship() { let s = id_type.songs(); let exp = object! { musicians: vec![ - object! { name: "John", mainBand: object! { name: "The Musicians" } }, - object! { name: "Lisa", mainBand: object! { name: "The Musicians" } }, - object! { name: "Tom", mainBand: object! { name: "The Amateurs"} }, - object! { name: "Valerie", mainBand: r::Value::Null } + object! { name: "John", mainBand: object! { name: "The Musicians" }, favoriteCount: "10" }, + object! { name: "Lisa", mainBand: object! { name: "The Musicians" }, favoriteCount: "100" }, + object! { name: "Tom", mainBand: object! { name: "The Amateurs" }, favoriteCount: "5" }, + object! { name: "Valerie", mainBand: r::Value::Null, favoriteCount: "20" } ], songStats: vec![ object! { diff --git a/store/test-store/tests/postgres/relational.rs b/store/test-store/tests/postgres/relational.rs index c8976454ccd..146bed87192 100644 --- a/store/test-store/tests/postgres/relational.rs +++ b/store/test-store/tests/postgres/relational.rs @@ -86,6 +86,7 @@ const THINGS_GQL: &str = r#" bigInt: BigInt, bigIntArray: [BigInt!]! color: Color, + int8: Int8, } interface Pet { @@ -119,6 +120,7 @@ const THINGS_GQL: &str = r#" bin_name: Bytes!, email: String!, age: Int!, + visits: Int8! seconds_age: BigInt!, weight: BigDecimal!, coffee: Boolean!, @@ -178,6 +180,7 @@ lazy_static! { id: "one", bool: true, int: std::i32::MAX, + int8: std::i64::MAX, bigDecimal: decimal.clone(), bigDecimalArray: vec![decimal.clone(), (decimal + 1.into())], string: "scalar", @@ -288,6 +291,7 @@ fn insert_user_entity( coffee: bool, favorite_color: Option<&str>, drinks: Option>, + visits: i64, block: BlockNumber, ) { let user = make_user( @@ -300,6 +304,7 @@ fn insert_user_entity( coffee, favorite_color, drinks, + visits, ); insert_entity_at(conn, layout, entity_type, vec![user], block); @@ -315,6 +320,7 @@ fn make_user( coffee: bool, favorite_color: Option<&str>, drinks: Option>, + visits: i64, ) -> Entity { let favorite_color = favorite_color .map(|s| Value::String(s.to_owned())) @@ -329,7 +335,8 @@ fn make_user( seconds_age: BigInt::from(age) * BigInt::from(31557600_u64), weight: BigDecimal::from(weight), coffee: coffee, - favorite_color: favorite_color + favorite_color: favorite_color, + visits: visits }; if let Some(drinks) = drinks { user.insert("drinks", drinks.into()).unwrap(); @@ -350,6 +357,7 @@ fn insert_users(conn: &PgConnection, layout: &Layout) { false, Some("yellow"), None, + 60, 0, ); insert_user_entity( @@ -364,6 +372,7 @@ fn insert_users(conn: &PgConnection, layout: &Layout) { true, Some("red"), Some(vec!["beer", "wine"]), + 50, 0, ); insert_user_entity( @@ -378,6 +387,7 @@ fn insert_users(conn: &PgConnection, layout: &Layout) { false, None, Some(vec!["coffee", "tea"]), + 22, 0, ); } @@ -394,6 +404,7 @@ fn update_user_entity( coffee: bool, favorite_color: Option<&str>, drinks: Option>, + visits: i64, block: BlockNumber, ) { let user = make_user( @@ -406,6 +417,7 @@ fn update_user_entity( coffee, favorite_color, drinks, + visits, ); update_entity_at(conn, layout, entity_type, vec![user], block); } @@ -1012,6 +1024,7 @@ impl<'a> QueryChecker<'a> { false, Some("yellow"), None, + 23, 0, ); insert_pets(conn, layout); @@ -1120,6 +1133,7 @@ fn check_block_finds() { false, Some("yellow"), None, + 55, 1, ); @@ -1389,6 +1403,14 @@ fn check_find() { .first(5), ); + // int 8 attributes + let checker = checker.check( + vec!["3"], + user_query() + .filter(EntityFilter::Equal("visits".to_owned(), Value::Int(22_i32))) + .desc("name"), + ); + // int attributes let checker = checker .check( diff --git a/tests/integration-tests/int8/abis/Contract.abi b/tests/integration-tests/int8/abis/Contract.abi new file mode 100644 index 00000000000..02da1a9e7f3 --- /dev/null +++ b/tests/integration-tests/int8/abis/Contract.abi @@ -0,0 +1,33 @@ +[ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint16", + "name": "x", + "type": "uint16" + } + ], + "name": "Trigger", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "uint16", + "name": "x", + "type": "uint16" + } + ], + "name": "emitTrigger", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/tests/integration-tests/int8/package.json b/tests/integration-tests/int8/package.json new file mode 100644 index 00000000000..79a45fc4368 --- /dev/null +++ b/tests/integration-tests/int8/package.json @@ -0,0 +1,25 @@ +{ + "name": "int8", + "version": "0.1.0", + "scripts": { + "build-contracts": "../../common/build-contracts.sh", + "codegen": "graph codegen --skip-migrations", + "test": "yarn build-contracts && truffle test --compile-none --network test", + "create:test": "graph create test/int8 --node $GRAPH_NODE_ADMIN_URI", + "deploy:test": "graph deploy test/int8 --version-label v0.0.1 --ipfs $IPFS_URI --node $GRAPH_NODE_ADMIN_URI" + }, + "devDependencies": { + "@graphprotocol/graph-cli": "0.51.0", + "@graphprotocol/graph-ts": "0.31.0", + "solc": "^0.8.2" + }, + "dependencies": { + "@truffle/contract": "^4.3", + "@truffle/hdwallet-provider": "^1.2", + "apollo-fetch": "^0.7.0", + "babel-polyfill": "^6.26.0", + "babel-register": "^6.26.0", + "gluegun": "^4.6.1", + "truffle": "^5.2" + } +} diff --git a/tests/integration-tests/int8/schema.graphql b/tests/integration-tests/int8/schema.graphql new file mode 100644 index 00000000000..493c4ceef04 --- /dev/null +++ b/tests/integration-tests/int8/schema.graphql @@ -0,0 +1,4 @@ +type Foo @entity { + id: ID! + value: Int8! +} diff --git a/tests/integration-tests/int8/src/mapping.ts b/tests/integration-tests/int8/src/mapping.ts new file mode 100644 index 00000000000..445d0fa20a1 --- /dev/null +++ b/tests/integration-tests/int8/src/mapping.ts @@ -0,0 +1,11 @@ +import { Trigger } from "../generated/Contract/Contract"; +import { Foo } from "../generated/schema"; + +export function handleTrigger(event: Trigger): void { + let obj = new Foo("0"); + obj.value = i64.MAX_VALUE; + obj.save(); + + obj = Foo.load("0"); + assert(obj.value == i64.MAX_VALUE, "maybe invalid value"); +} diff --git a/tests/integration-tests/int8/subgraph.yaml b/tests/integration-tests/int8/subgraph.yaml new file mode 100644 index 00000000000..0ab4801ce44 --- /dev/null +++ b/tests/integration-tests/int8/subgraph.yaml @@ -0,0 +1,23 @@ +specVersion: 0.0.4 +schema: + file: ./schema.graphql +dataSources: + - kind: ethereum/contract + name: Contract + network: test + source: + address: "0xCfEB869F69431e42cdB54A4F4f105C19C080A601" + abi: Contract + mapping: + kind: ethereum/events + apiVersion: 0.0.6 + language: wasm/assemblyscript + abis: + - name: Contract + file: ./abis/Contract.abi + entities: + - Call + eventHandlers: + - event: Trigger(uint16) + handler: handleTrigger + file: ./src/mapping.ts diff --git a/tests/integration-tests/int8/test/test.js b/tests/integration-tests/int8/test/test.js new file mode 100644 index 00000000000..14273bf98c7 --- /dev/null +++ b/tests/integration-tests/int8/test/test.js @@ -0,0 +1,102 @@ +const path = require("path"); +const execSync = require("child_process").execSync; +const { system, patching } = require("gluegun"); +const { createApolloFetch } = require("apollo-fetch"); + +const Contract = artifacts.require("./Contract.sol"); + +const srcDir = path.join(__dirname, ".."); + +const httpPort = process.env.GRAPH_NODE_HTTP_PORT || 18000; +const indexPort = process.env.GRAPH_NODE_INDEX_PORT || 18030; + +const fetchSubgraphs = createApolloFetch({ + uri: `http://localhost:${indexPort}/graphql`, +}); +const fetchSubgraph = createApolloFetch({ + uri: `http://localhost:${httpPort}/subgraphs/name/test/int8`, +}); + +const exec = (cmd) => { + try { + return execSync(cmd, { cwd: srcDir, stdio: "inherit" }); + } catch (e) { + throw new Error(`Failed to run command \`${cmd}\``); + } +}; + +const waitForSubgraphToBeSynced = async () => + new Promise((resolve, reject) => { + // Wait for 60s + let deadline = Date.now() + 60 * 1000; + + // Function to check if the subgraph is synced + const checkSubgraphSynced = async () => { + try { + let result = await fetchSubgraphs({ + query: `{ indexingStatuses { synced, health } }`, + }); + + if (result.data.indexingStatuses[0].synced) { + resolve(); + } else if (result.data.indexingStatuses[0].health != "healthy") { + reject(new Error("Subgraph failed")); + } else { + throw new Error("reject or retry"); + } + } catch (e) { + if (Date.now() > deadline) { + reject(new Error(`Timed out waiting for the subgraph to sync`)); + } else { + setTimeout(checkSubgraphSynced, 500); + } + } + }; + + // Periodically check whether the subgraph has synced + setTimeout(checkSubgraphSynced, 0); + }); + +contract("Contract", (accounts) => { + // Deploy the subgraph once before all tests + before(async () => { + // Deploy the contract + const contract = await Contract.deployed(); + + // Insert its address into subgraph manifest + await patching.replace( + path.join(srcDir, "subgraph.yaml"), + "0x0000000000000000000000000000000000000000", + contract.address + ); + + // Create and deploy the subgraph + exec(`yarn codegen`); + exec(`yarn create:test`); + exec(`yarn deploy:test`); + + // Wait for the subgraph to be indexed + await waitForSubgraphToBeSynced(); + }); + + it("test query", async () => { + // Also test that multiple block constraints do not result in a graphql error. + let result = await fetchSubgraph({ + query: `{ + foos_0: foos(orderBy: id, block: { number: 0 }) { id } + foos(orderBy: id) { id value } + }`, + }); + + expect(result.errors).to.be.undefined; + expect(result.data).to.deep.equal({ + foos_0: [], + foos: [ + { + id: "0", + value: "9223372036854775807", + }, + ], + }); + }); +}); diff --git a/tests/integration-tests/int8/truffle.js b/tests/integration-tests/int8/truffle.js new file mode 100644 index 00000000000..27a9675b4d7 --- /dev/null +++ b/tests/integration-tests/int8/truffle.js @@ -0,0 +1,22 @@ +require("babel-register"); +require("babel-polyfill"); + +module.exports = { + contracts_directory: "../../common", + migrations_directory: "../../common", + contracts_build_directory: "./truffle_output", + networks: { + test: { + host: "localhost", + port: process.env.GANACHE_TEST_PORT || 18545, + network_id: "*", + gas: "100000000000", + gasPrice: "1" + } + }, + compilers: { + solc: { + version: "0.8.2" + } + } +}; diff --git a/tests/integration-tests/package.json b/tests/integration-tests/package.json index 1d272de326c..7768fb7d02d 100644 --- a/tests/integration-tests/package.json +++ b/tests/integration-tests/package.json @@ -8,6 +8,7 @@ "overloaded-contract-functions", "poi-for-failed-subgraph", "remove-then-update", - "value-roundtrip" + "value-roundtrip", + "int8" ] } diff --git a/tests/integration-tests/yarn.lock b/tests/integration-tests/yarn.lock index 0f10ad49261..2232422fcb5 100644 --- a/tests/integration-tests/yarn.lock +++ b/tests/integration-tests/yarn.lock @@ -903,6 +903,38 @@ which "2.0.2" yaml "1.10.2" +"@graphprotocol/graph-cli@0.51.0": + version "0.51.0" + resolved "https://registry.yarnpkg.com/@graphprotocol/graph-cli/-/graph-cli-0.51.0.tgz#c9b864b249b98946b4b666045d7e7a56154c72ec" + integrity sha512-Yvwhx9Q31egUOVUQH2Hti9ysqPk1Ti9+si8Ii/xpGnXp6qZC/elSr/1rzEOgi84OSR1Y74GLwmNNGZImjD/uLg== + dependencies: + "@float-capital/float-subgraph-uncrashable" "^0.0.0-alpha.4" + "@oclif/core" "2.8.4" + "@whatwg-node/fetch" "^0.8.4" + assemblyscript "0.19.23" + binary-install-raw "0.0.13" + chalk "3.0.0" + chokidar "3.5.3" + debug "4.3.4" + docker-compose "0.23.19" + dockerode "2.5.8" + fs-extra "9.1.0" + glob "9.3.5" + gluegun "5.1.2" + graphql "15.5.0" + immutable "4.2.1" + ipfs-http-client "55.0.0" + jayson "4.0.0" + js-yaml "3.14.1" + prettier "1.19.1" + request "2.88.2" + semver "7.4.0" + sync-request "6.1.0" + tmp-promise "3.0.3" + web3-eth-abi "1.7.0" + which "2.0.2" + yaml "1.10.2" + "@graphprotocol/graph-cli@https://github.com/graphprotocol/graph-cli#v0.21.1": version "0.21.1" resolved "https://github.com/graphprotocol/graph-cli#352f34d66e3fc7ebd55fa0a2848ce32e191baf5f" @@ -935,6 +967,13 @@ dependencies: assemblyscript "0.19.10" +"@graphprotocol/graph-ts@0.31.0": + version "0.31.0" + resolved "https://registry.yarnpkg.com/@graphprotocol/graph-ts/-/graph-ts-0.31.0.tgz#730668c0369828b31bef81e8d9bc66b9b48e3480" + integrity sha512-xreRVM6ho2BtolyOh2flDkNoGZximybnzUnF53zJVp0+Ed0KnAlO1/KOCUYw06euVI9tk0c9nA2Z/D5SIQV2Rg== + dependencies: + assemblyscript "0.19.10" + "@graphprotocol/graph-ts@https://github.com/graphprotocol/graph-ts#v0.21.1": version "0.20.0" resolved "https://github.com/graphprotocol/graph-ts#56adb62d9e4233c6fc6c38bc0519a8a566afdd9e" diff --git a/tests/tests/integration_tests.rs b/tests/tests/integration_tests.rs index c9b68d6cc35..7c4a682e4ae 100644 --- a/tests/tests/integration_tests.rs +++ b/tests/tests/integration_tests.rs @@ -39,6 +39,7 @@ pub const INTEGRATION_TEST_DIRS: &[&str] = &[ "poi-for-failed-subgraph", "remove-then-update", "value-roundtrip", + "int8", ]; #[derive(Debug, Clone)]