From 865a9bc1bb7ba3bdad9878b731a7f745fa9d23a0 Mon Sep 17 00:00:00 2001 From: Dotan Simha Date: Sun, 2 Apr 2023 15:52:12 +0300 Subject: [PATCH] Initial WIP on adding new `Int8` scalar fixes and tests --- graph/src/data/store/mod.rs | 26 ++++++++++ graph/src/data/value.rs | 2 + graph/src/runtime/gas/size_of.rs | 1 + graph/src/util/cache_weight.rs | 2 +- graphql/src/schema/api.rs | 4 ++ graphql/src/schema/meta.graphql | 4 ++ graphql/src/values/coercion.rs | 19 ++++++++ runtime/test/src/test/abi.rs | 6 +++ .../api_version_0_0_4/abi_store_value.ts | 8 +++ .../api_version_0_0_4/abi_store_value.wasm | Bin 5050 -> 5106 bytes .../api_version_0_0_5/abi_store_value.ts | 8 +++ .../api_version_0_0_5/abi_store_value.wasm | Bin 7855 -> 7909 bytes .../api_version_0_0_5/common/types.ts | 1 + runtime/wasm/src/asc_abi/class.rs | 8 +++ runtime/wasm/src/to_from/external.rs | 2 + store/postgres/examples/layout.rs | 2 + store/postgres/src/relational.rs | 3 ++ store/postgres/src/relational_queries.rs | 25 +++++++++- store/postgres/src/sql_value.rs | 15 +++++- store/test-store/tests/graphql/query.rs | 18 ++++--- store/test-store/tests/postgres/relational.rs | 46 ++++++++++++++++-- 21 files changed, 186 insertions(+), 14 deletions(-) diff --git a/graph/src/data/store/mod.rs b/graph/src/data/store/mod.rs index 3d1b38a095c..733f2f33caa 100644 --- a/graph/src/data/store/mod.rs +++ b/graph/src/data/store/mod.rs @@ -130,6 +130,7 @@ pub const ID: &str = "ID"; 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 { @@ -138,6 +139,7 @@ pub enum ValueType { Bytes, BigDecimal, Int, + Int8, String, } @@ -151,6 +153,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)), } @@ -172,6 +175,7 @@ impl ValueType { pub enum Value { String(String), Int(i32), + Int8(i64), BigDecimal(scalar::BigDecimal), Bool(bool), List(Vec), @@ -207,6 +211,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) } @@ -265,6 +272,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]) @@ -300,6 +311,9 @@ impl Value { BYTES_SCALAR => Value::Bytes(scalar::Bytes::from_str(s)?), BIG_INT_SCALAR => Value::BigInt(scalar::BigInt::from_str(s)?), 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()), } } @@ -391,6 +405,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()) @@ -411,6 +426,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() @@ -428,6 +444,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(), @@ -445,6 +462,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(), @@ -460,6 +478,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, @@ -477,6 +496,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, @@ -543,6 +563,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/value.rs b/graph/src/data/value.rs index 7991ef3ac17..fffc07998af 100644 --- a/graph/src/data/value.rs +++ b/graph/src/data/value.rs @@ -267,6 +267,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 8f4e535a1fd..5eace713353 100644 --- a/graph/src/runtime/gas/size_of.rs +++ b/graph/src/runtime/gas/size_of.rs @@ -16,6 +16,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/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/schema/api.rs b/graphql/src/schema/api.rs index fc9186e2d6d..fa2545d8eb6 100644 --- a/graphql/src/schema/api.rs +++ b/graphql/src/schema/api.rs @@ -367,6 +367,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", @@ -894,6 +895,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/graphql/src/schema/meta.graphql b/graphql/src/schema/meta.graphql index b2b5ffd9a87..4477313c57d 100644 --- a/graphql/src/schema/meta.graphql +++ b/graphql/src/schema/meta.graphql @@ -1,6 +1,7 @@ # GraphQL core functionality scalar Boolean scalar ID +""" 4 bytes int """ 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/graphql/src/values/coercion.rs b/graphql/src/values/coercion.rs index 7f4f8cedd50..87067631a6f 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 274d44395fc..baeb27b190e 100644 --- a/runtime/test/src/test/abi.rs +++ b/runtime/test/src/test/abi.rs @@ -290,6 +290,12 @@ async fn test_abi_store_value(api_version: Version) { let new_value_ptr = module.takes_val_returns_ptr("value_from_int", int); 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(); 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 635271b6f39808312ac95381e99b0b3bff54206a..8e2be6f47a6054c7b5ff32b0b27e1741f72604c1 100644 GIT binary patch delta 659 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 YxG&=AUjhf7t#GJH3kTQ#rvH}y2lJMlS^xk5 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 73a6189c13b..206e30fce98 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 fd8a2e2ad16..7bd61642c9a 100644 --- a/runtime/wasm/src/to_from/external.rs +++ b/runtime/wasm/src/to_from/external.rs @@ -250,6 +250,7 @@ impl FromAscObj> for store::Value { Value::String(asc_get(heap, ptr, gas)?) } 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)?) @@ -285,6 +286,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 94daa9657f4..76fe6ada116 100644 --- a/store/postgres/examples/layout.rs +++ b/store/postgres/examples/layout.rs @@ -52,6 +52,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(); @@ -71,6 +72,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 4e578db1ec1..ddcec318118 100644 --- a/store/postgres/src/relational.rs +++ b/store/postgres/src/relational.rs @@ -1010,6 +1010,7 @@ pub enum ColumnType { BigInt, Bytes, Int, + Int8, String, TSVector(FulltextConfig), Enum(EnumType), @@ -1067,6 +1068,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), } } @@ -1078,6 +1080,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 35b86278d5d..21ad58b575d 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, Text}; +use diesel::sql_types::{Array, BigInt, Binary, Bool, Int8, Integer, Jsonb, Text}; use diesel::Connection; use graph::components::store::{DerivedEntityQuery, EntityKey}; @@ -285,6 +285,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; @@ -314,6 +316,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()) @@ -375,6 +384,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()) } @@ -420,6 +433,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) } @@ -550,6 +567,7 @@ 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::().unwrap()), ColumnType::Enum(enum_type) => { out.push_bind_param::(s)?; out.push_sql("::"); @@ -572,6 +590,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"); @@ -590,6 +609,7 @@ impl<'a> QueryFragment for QueryValue<'a> { ColumnType::Boolean => out.push_bind_param::, _>(&sql_values), ColumnType::Bytes => out.push_bind_param::, _>(&sql_values), ColumnType::Int => out.push_bind_param::, _>(&sql_values), + ColumnType::Int8 => out.push_bind_param::, _>(&sql_values), ColumnType::String => out.push_bind_param::, _>(&sql_values), ColumnType::Enum(enum_type) => { out.push_bind_param::, _>(&sql_values)?; @@ -1168,6 +1188,7 @@ impl<'a> QueryFilter<'a> { Value::Null | Value::BigDecimal(_) | Value::Int(_) + | Value::Int8(_) | Value::Bool(_) | Value::BigInt(_) => { let filter = match negated { @@ -1238,6 +1259,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 { @@ -1377,6 +1399,7 @@ impl<'a> QueryFilter<'a> { | Value::Bytes(_) | Value::BigDecimal(_) | Value::Int(_) + | Value::Int8(_) | Value::List(_) | Value::Null => { return Err(UnsupportedFilter { diff --git a/store/postgres/src/sql_value.rs b/store/postgres/src/sql_value.rs index 22439449f2b..966ad3234fb 100644 --- a/store/postgres/src/sql_value.rs +++ b/store/postgres/src/sql_value.rs @@ -1,6 +1,6 @@ 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 graph::prelude::anyhow::anyhow; use std::io::Write; use std::str::FromStr; @@ -42,6 +42,19 @@ impl ToSql for SqlValue { } } +impl ToSql for SqlValue { + fn to_sql(&self, out: &mut Output) -> serialize::Result { + match &self.0 { + 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 SqlValue { fn to_sql(&self, out: &mut Output) -> serialize::Result { match &self.0 { diff --git a/store/test-store/tests/graphql/query.rs b/store/test-store/tests/graphql/query.rs index 1f4c02a97ae..ecd13d60988 100644 --- a/store/test-store/tests/graphql/query.rs +++ b/store/test-store/tests/graphql/query.rs @@ -171,6 +171,7 @@ fn test_schema(id: DeploymentHash, id_type: IdType) -> Schema { mainBand: Band bands: [Band!]! writtenSongs: [Song!]! @derivedFrom(field: \"writtenBy\") + favoriteCount: Int8! } type Band @entity { @@ -313,8 +314,8 @@ async fn insert_test_entities( let s = id_type.songs(); let md = id_type.medias(); let entities0 = vec![ - entity! { __typename: "Musician", id: "m1", name: "John", mainBand: "b1", bands: vec!["b1", "b2"] }, - entity! { __typename: "Musician", id: "m2", name: "Lisa", mainBand: "b1", bands: vec!["b1"] }, + entity! { __typename: "Musician", id: "m1", name: "John", mainBand: "b1", bands: vec!["b1", "b2"], favoriteCount: 2 }, + entity! { __typename: "Musician", id: "m2", name: "Lisa", mainBand: "b1", bands: vec!["b1"], favoriteCount: 100 }, entity! { __typename: "Publisher", id: "0xb1" }, entity! { __typename: "Band", id: "b1", name: "The Musicians", originalSongs: vec![s[1], s[2]] }, entity! { __typename: "Band", id: "b2", name: "The Amateurs", originalSongs: vec![s[1], s[3], s[4]] }, @@ -345,8 +346,8 @@ async fn insert_test_entities( ]; let entities1 = vec![ - entity! { __typename: "Musician", id: "m3", name: "Tom", mainBand: "b2", bands: vec!["b1", "b2"] }, - entity! { __typename: "Musician", id: "m4", name: "Valerie", bands: Vec::::new() }, + entity! { __typename: "Musician", id: "m3", name: "Tom", mainBand: "b2", bands: vec!["b1", "b2"], favoriteCount: 5 }, + entity! { __typename: "Musician", id: "m4", name: "Valerie", bands: Vec::::new(), favoriteCount: 10 }, ]; async fn insert_at(entities: Vec, deployment: &DeploymentLocator, block_ptr: BlockPtr) { @@ -567,6 +568,7 @@ fn can_query_one_to_one_relationship() { mainBand { name } + favoriteCount } songStats(first: 100, orderBy: id) { id @@ -583,10 +585,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: "2" }, + 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: "10" } ], songStats: vec![ object! { diff --git a/store/test-store/tests/postgres/relational.rs b/store/test-store/tests/postgres/relational.rs index ca68338933e..4a10114b1c1 100644 --- a/store/test-store/tests/postgres/relational.rs +++ b/store/test-store/tests/postgres/relational.rs @@ -83,6 +83,7 @@ const THINGS_GQL: &str = r#" bigInt: BigInt, bigIntArray: [BigInt!]! color: Color, + int8: Int8, } interface Pet { @@ -116,6 +117,7 @@ const THINGS_GQL: &str = r#" bin_name: Bytes!, email: String!, age: Int!, + visits: Int8! seconds_age: BigInt!, weight: BigDecimal!, coffee: Boolean!, @@ -173,6 +175,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", @@ -295,9 +298,20 @@ fn insert_user_entity( coffee: bool, favorite_color: Option<&str>, drinks: Option>, + visits: i64, block: BlockNumber, ) { - let user = make_user(id, name, email, age, weight, coffee, favorite_color, drinks); + let user = make_user( + id, + name, + email, + age, + weight, + coffee, + favorite_color, + drinks, + visits, + ); insert_entity_at(conn, layout, entity_type, vec![user], block); } @@ -311,6 +325,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())) @@ -325,7 +340,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".to_owned(), drinks.into()); @@ -346,6 +362,7 @@ fn insert_users(conn: &PgConnection, layout: &Layout) { false, Some("yellow"), None, + 60, 0, ); insert_user_entity( @@ -360,6 +377,7 @@ fn insert_users(conn: &PgConnection, layout: &Layout) { true, Some("red"), Some(vec!["beer", "wine"]), + 50, 0, ); insert_user_entity( @@ -374,6 +392,7 @@ fn insert_users(conn: &PgConnection, layout: &Layout) { false, None, Some(vec!["coffee", "tea"]), + 22, 0, ); } @@ -390,9 +409,20 @@ fn update_user_entity( coffee: bool, favorite_color: Option<&str>, drinks: Option>, + visits: i64, block: BlockNumber, ) { - let user = make_user(id, name, email, age, weight, coffee, favorite_color, drinks); + let user = make_user( + id, + name, + email, + age, + weight, + coffee, + favorite_color, + drinks, + visits, + ); update_entity_at(conn, layout, entity_type, vec![user], block); } @@ -1003,6 +1033,7 @@ impl<'a> QueryChecker<'a> { false, Some("yellow"), None, + 23, 0, ); insert_pets(conn, layout); @@ -1111,6 +1142,7 @@ fn check_block_finds() { false, Some("yellow"), None, + 55, 1, ); @@ -1380,6 +1412,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(