Skip to content

Add integration test for load in the mapping #5849

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

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion graph/src/components/store/entity_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,18 @@ impl EntityCache {
// always creates it in a new style.
debug_assert!(match scope {
GetScope::Store => {
entity == self.store.get(key).unwrap().map(Arc::new)
// Release build will never call this function and hence it's OK
// when that implementation is not correct.
fn remove_vid(entity: Option<Arc<Entity>>) -> Option<Entity> {
entity.map(|e| {
#[allow(unused_mut)]
let mut entity = (*e).clone();
#[cfg(debug_assertions)]
entity.remove("vid");
entity
})
}
remove_vid(entity.clone()) == remove_vid(self.store.get(key).unwrap().map(Arc::new))
}
GetScope::InBlock => true,
});
Expand Down
96 changes: 16 additions & 80 deletions graph/src/data/store/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -740,17 +740,16 @@ lazy_static! {
}

/// An entity is represented as a map of attribute names to values.
#[derive(Clone, CacheWeight, Eq, Serialize)]
#[derive(Clone, CacheWeight, PartialEq, Eq, Serialize)]
pub struct Entity(Object<Value>);

impl<'a> IntoIterator for &'a Entity {
type Item = (&'a str, &'a Value);
type Item = (Word, Value);

type IntoIter =
std::iter::Filter<intern::ObjectIter<'a, Value>, fn(&(&'a str, &'a Value)) -> bool>;
type IntoIter = intern::ObjectOwningIter<Value>;

fn into_iter(self) -> Self::IntoIter {
(&self.0).into_iter().filter(|(k, _)| *k != VID_FIELD)
self.0.clone().into_iter()
}
}

Expand Down Expand Up @@ -875,34 +874,22 @@ impl Entity {
}

pub fn get(&self, key: &str) -> Option<&Value> {
// VID field is private and not visible outside
if key == VID_FIELD {
return None;
}
self.0.get(key)
}

pub fn contains_key(&self, key: &str) -> bool {
// VID field is private and not visible outside
if key == VID_FIELD {
return false;
}
self.0.contains_key(key)
}

// This collects the entity into an ordered vector so that it can be iterated deterministically.
pub fn sorted(self) -> Vec<(Word, Value)> {
let mut v: Vec<_> = self
.0
.into_iter()
.filter(|(k, _)| !k.eq(VID_FIELD))
.collect();
let mut v: Vec<_> = self.0.into_iter().map(|(k, v)| (k, v)).collect();
v.sort_by(|(k1, _), (k2, _)| k1.cmp(k2));
v
}

pub fn sorted_ref(&self) -> Vec<(&str, &Value)> {
let mut v: Vec<_> = self.0.iter().filter(|(k, _)| !k.eq(&VID_FIELD)).collect();
let mut v: Vec<_> = self.0.iter().collect();
v.sort_by(|(k1, _), (k2, _)| k1.cmp(k2));
v
}
Expand All @@ -928,8 +915,7 @@ impl Entity {
/// Return the VID of this entity and if its missing or of a type different than
/// i64 it panics.
pub fn vid(&self) -> i64 {
self.0
.get(VID_FIELD)
self.get(VID_FIELD)
.expect("the vid must be set")
.as_int8()
.expect("the vid must be set to a valid value")
Expand All @@ -940,6 +926,15 @@ impl Entity {
self.0.insert(VID_FIELD, value.into())
}

/// Sets the VID if it's not already set. Should be used only for tests.
#[cfg(debug_assertions)]
pub fn set_vid_if_empty(&mut self) {
let vid = self.get(VID_FIELD);
if vid.is_none() {
let _ = self.set_vid(100).expect("the vid should be set");
}
}

/// Merges an entity update `update` into this entity.
///
/// If a key exists in both entities, the value from `update` is chosen.
Expand Down Expand Up @@ -1063,13 +1058,6 @@ impl Entity {
}
}

/// Checks equality of two entities while ignoring the VID fields
impl PartialEq for Entity {
fn eq(&self, other: &Self) -> bool {
self.0.eq_ignore_key(&other.0, VID_FIELD)
}
}

/// Convenience methods to modify individual attributes for tests.
/// Production code should not use/need this.
#[cfg(debug_assertions)]
Expand All @@ -1089,14 +1077,6 @@ impl Entity {
) -> Result<Option<Value>, InternError> {
self.0.insert(name, value.into())
}

/// Sets the VID if it's not already set. Should be used only for tests.
pub fn set_vid_if_empty(&mut self) {
let vid = self.0.get(VID_FIELD);
if vid.is_none() {
let _ = self.set_vid(100).expect("the vid should be set");
}
}
}

impl<'a> From<&'a Entity> for Cow<'a, Entity> {
Expand Down Expand Up @@ -1288,47 +1268,3 @@ fn fmt_debug() {
let bi = Value::BigInt(scalar::BigInt::from(-17i32));
assert_eq!("BigInt(-17)", format!("{:?}", bi));
}

#[test]
fn entity_hidden_vid() {
use crate::schema::InputSchema;
let subgraph_id = "oneInterfaceOneEntity";
let document = "type Thing @entity {id: ID!, name: String!}";
let schema = InputSchema::raw(document, subgraph_id);

let entity = entity! { schema => id: "1", name: "test", vid: 3i64 };
let debug_str = format!("{:?}", entity);
let entity_str = "Entity { id: String(\"1\"), name: String(\"test\"), vid: Int8(3) }";
assert_eq!(debug_str, entity_str);

// get returns nothing...
assert_eq!(entity.get(VID_FIELD), None);
assert_eq!(entity.contains_key(VID_FIELD), false);
// ...while vid is present
assert_eq!(entity.vid(), 3i64);

// into_iter() misses it too
let mut it = entity.into_iter();
assert_eq!(Some(("id", &Value::String("1".to_string()))), it.next());
assert_eq!(
Some(("name", &Value::String("test".to_string()))),
it.next()
);
assert_eq!(None, it.next());

let mut entity2 = entity! { schema => id: "1", name: "test", vid: 5i64 };
assert_eq!(entity2.vid(), 5i64);
// equal with different vid
assert_eq!(entity, entity2);

entity2.remove(VID_FIELD);
// equal if one has no vid
assert_eq!(entity, entity2);
let debug_str2 = format!("{:?}", entity2);
let entity_str2 = "Entity { id: String(\"1\"), name: String(\"test\") }";
assert_eq!(debug_str2, entity_str2);

// set again
_ = entity2.set_vid(7i64);
assert_eq!(entity2.vid(), 7i64);
}
39 changes: 0 additions & 39 deletions graph/src/util/intern.rs
Original file line number Diff line number Diff line change
Expand Up @@ -308,45 +308,6 @@ impl<V> Object<V> {
}
}

impl<V: PartialEq> Object<V> {
fn len_ignore_atom(&self, atom: &Atom) -> usize {
// Because of tombstones and the ignored atom, we can't just return `self.entries.len()`.
self.entries
.iter()
.filter(|entry| entry.key != TOMBSTONE_KEY && entry.key != *atom)
.count()
}

/// Check for equality while ignoring one particular element
pub fn eq_ignore_key(&self, other: &Self, ignore_key: &str) -> bool {
let ignore = self.pool.lookup(ignore_key);
let len1 = if let Some(to_ignore) = ignore {
self.len_ignore_atom(&to_ignore)
} else {
self.len()
};
let len2 = if let Some(to_ignore) = other.pool.lookup(ignore_key) {
other.len_ignore_atom(&to_ignore)
} else {
other.len()
};
if len1 != len2 {
return false;
}

if self.same_pool(other) {
self.entries
.iter()
.filter(|e| e.key != TOMBSTONE_KEY && ignore.map_or(true, |ig| e.key != ig))
.all(|Entry { key, value }| other.get_by_atom(key).map_or(false, |o| o == value))
} else {
self.iter()
.filter(|(key, _)| *key != ignore_key)
.all(|(key, value)| other.get(key).map_or(false, |o| o == value))
}
}
}

impl<V: NullValue> Object<V> {
/// Remove `key` from the object and return the value that was
/// associated with the `key`. The entry is actually not removed for
Expand Down
33 changes: 33 additions & 0 deletions tests/integration-tests/load-save/abis/Contract.abi
Original file line number Diff line number Diff line change
@@ -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"
}
]
25 changes: 25 additions & 0 deletions tests/integration-tests/load-save/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"name": "load-save",
"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/load-save --node $GRAPH_NODE_ADMIN_URI",
"deploy:test": "graph deploy test/load-save --version-label v0.0.1 --ipfs $IPFS_URI --node $GRAPH_NODE_ADMIN_URI"
},
"devDependencies": {
"@graphprotocol/graph-cli": "0.69.0",
"@graphprotocol/graph-ts": "0.34.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"
}
}
5 changes: 5 additions & 0 deletions tests/integration-tests/load-save/schema.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
type LSData @entity {
id: ID!
data: String!
blockNumber: BigInt!
}
19 changes: 19 additions & 0 deletions tests/integration-tests/load-save/src/mapping.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { BigInt, ethereum } from '@graphprotocol/graph-ts'
import { LSData } from '../generated/schema'

export function handleBlock(block: ethereum.Block): void {
let diff = 1
let entity = new LSData(block.number.toString())
entity.data = 'original entity'
entity.blockNumber = block.number
entity.save()
let block_number = block.number
if (block_number.gt(BigInt.fromI32(diff))) {
let bn = block_number.minus(BigInt.fromI32(diff)).toString()
let entity2 = LSData.load(bn)
if (entity2 != null) {
entity2.data = 'modified entity'
entity2.save()
}
}
}
25 changes: 25 additions & 0 deletions tests/integration-tests/load-save/subgraph.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
specVersion: 1.2.0
description: Source Subgraph A
repository: https://github.com/graphprotocol/graph-node
schema:
file: ./schema.graphql
dataSources:
- kind: ethereum/contract
name: SimpleContract
network: test
source:
address: "0x5FbDB2315678afecb367f032d93F642f64180aa3"
abi: SimpleContract
startBlock: 0
mapping:
kind: ethereum/events
apiVersion: 0.0.6
language: wasm/assemblyscript
entities:
- LSData
abis:
- name: SimpleContract
file: ./abis/Contract.abi
blockHandlers:
- handler: handleBlock
file: ./src/mapping.ts
14 changes: 14 additions & 0 deletions tests/tests/integration_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -603,6 +603,19 @@ async fn subgraph_data_sources(ctx: TestContext) -> anyhow::Result<()> {
Ok(())
}

async fn load_save(ctx: TestContext) -> anyhow::Result<()> {
let subgraph = ctx.subgraph;
assert!(subgraph.healthy);

let _contract = ctx
.contracts
.iter()
.find(|x| x.name == "SimpleContract")
.unwrap();

Ok(())
}

async fn test_topic_filters(ctx: TestContext) -> anyhow::Result<()> {
let subgraph = ctx.subgraph;
assert!(subgraph.healthy);
Expand Down Expand Up @@ -980,6 +993,7 @@ async fn integration_tests() -> anyhow::Result<()> {
TestCase::new("block-handlers", test_block_handlers),
TestCase::new("timestamp", test_timestamp),
TestCase::new("ethereum-api-tests", test_eth_api),
TestCase::new("load-save", load_save),
TestCase::new("topic-filter", test_topic_filters),
TestCase::new_with_source_subgraph(
"subgraph-data-sources",
Expand Down
Loading