diff --git a/graph/src/data/store/mod.rs b/graph/src/data/store/mod.rs index 3d1b38a095c..395da66a509 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, } @@ -172,6 +174,7 @@ impl ValueType { pub enum Value { String(String), Int(i32), + Int8(i64), BigDecimal(scalar::BigDecimal), Bool(bool), List(Vec), @@ -207,6 +210,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) } @@ -264,6 +270,10 @@ impl StableHash for Value { BigInt(inner) => { inner.stable_hash(field_address.child(0), state); 7 + }, + Int8(inner) => { + inner.stable_hash(field_address.child(0), state); + 8 } }; @@ -300,6 +310,7 @@ 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 +402,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 +423,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 +441,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 +459,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 +475,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 +493,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, diff --git a/graph/src/data/value.rs b/graph/src/data/value.rs index 7991ef3ac17..1e8b44c37ec 100644 --- a/graph/src/data/value.rs +++ b/graph/src/data/value.rs @@ -267,6 +267,7 @@ impl Value { Err(Value::Int(num)) } } + ("Int8", Value::Int(num)) => Ok(Value::Int(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..acbc5d70cff 100644 --- a/graphql/src/schema/meta.graphql +++ b/graphql/src/schema/meta.graphql @@ -19,9 +19,11 @@ 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 +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..adf5c38f4cd 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::Int(1234_i64)) + ); + assert_eq!( + coerce_to_definition(Value::Int((-1234_i32).into()), "", &resolver,), + Ok(Value::Int(-1234_i64)) + ); + } + #[test] fn coerce_bytes_scalar() { let bytes_type = TypeDefinition::Scalar(ScalarType::new("Bytes".to_string())); diff --git a/runtime/wasm/src/asc_abi/class.rs b/runtime/wasm/src/asc_abi/class.rs index 95c14b1bfb3..85c641e80be 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 @@ -540,6 +546,7 @@ impl AscValue for EthereumValueKind {} pub enum StoreValueKind { String, Int, + Int8, BigDecimal, Bool, Array, @@ -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/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..6a3f62a4025 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, Integer, Jsonb, Text, Int8}; use diesel::Connection; use graph::components::store::{DerivedEntityQuery, EntityKey}; @@ -314,6 +314,15 @@ pub trait FromColumnValue: Sized + std::fmt::Debug { number ))), }, + (j::Number(number), ColumnType::Int8) => match number.as_i64() { + Some(i) => i32::try_from(i).map(Self::from_i32).map_err(|e| { + StoreError::Unknown(anyhow!("failed to convert {} to Int8: {}", number, e)) + }), + 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()) @@ -572,6 +581,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 +600,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 +1179,7 @@ impl<'a> QueryFilter<'a> { Value::Null | Value::BigDecimal(_) | Value::Int(_) + | Value::Int8(_) | Value::Bool(_) | Value::BigInt(_) => { let filter = match negated { @@ -1238,6 +1250,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 +1390,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..1e8763cbc65 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, Integer, Text, Int8}; 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 {