From 57f7b9ba2b3ff7724b00dc5cca2b7104389f273a Mon Sep 17 00:00:00 2001 From: Zoran Cvetkov Date: Tue, 25 Feb 2025 18:49:26 +0200 Subject: [PATCH 1/3] add a test --- .../load-save/abis/Contract.abi | 33 +++++++++++++++++++ .../integration-tests/load-save/package.json | 25 ++++++++++++++ .../load-save/schema.graphql | 5 +++ .../load-save/src/mapping.ts | 19 +++++++++++ .../integration-tests/load-save/subgraph.yaml | 25 ++++++++++++++ tests/tests/integration_tests.rs | 14 ++++++++ 6 files changed, 121 insertions(+) create mode 100644 tests/integration-tests/load-save/abis/Contract.abi create mode 100644 tests/integration-tests/load-save/package.json create mode 100644 tests/integration-tests/load-save/schema.graphql create mode 100644 tests/integration-tests/load-save/src/mapping.ts create mode 100644 tests/integration-tests/load-save/subgraph.yaml diff --git a/tests/integration-tests/load-save/abis/Contract.abi b/tests/integration-tests/load-save/abis/Contract.abi new file mode 100644 index 00000000000..02da1a9e7f3 --- /dev/null +++ b/tests/integration-tests/load-save/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/load-save/package.json b/tests/integration-tests/load-save/package.json new file mode 100644 index 00000000000..41504e2323e --- /dev/null +++ b/tests/integration-tests/load-save/package.json @@ -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" + } +} \ No newline at end of file diff --git a/tests/integration-tests/load-save/schema.graphql b/tests/integration-tests/load-save/schema.graphql new file mode 100644 index 00000000000..5ae77bda7f9 --- /dev/null +++ b/tests/integration-tests/load-save/schema.graphql @@ -0,0 +1,5 @@ +type LSData @entity { + id: ID! + data: String! + blockNumber: BigInt! +} \ No newline at end of file diff --git a/tests/integration-tests/load-save/src/mapping.ts b/tests/integration-tests/load-save/src/mapping.ts new file mode 100644 index 00000000000..ad3e01f267d --- /dev/null +++ b/tests/integration-tests/load-save/src/mapping.ts @@ -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() + } + } +} diff --git a/tests/integration-tests/load-save/subgraph.yaml b/tests/integration-tests/load-save/subgraph.yaml new file mode 100644 index 00000000000..fa2e74c2646 --- /dev/null +++ b/tests/integration-tests/load-save/subgraph.yaml @@ -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 \ No newline at end of file diff --git a/tests/tests/integration_tests.rs b/tests/tests/integration_tests.rs index d10df25698b..8d7124bc8b1 100644 --- a/tests/tests/integration_tests.rs +++ b/tests/tests/integration_tests.rs @@ -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); @@ -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", From 5cf0ad5c86b570ab5628c3987897fadcc63bcc6e Mon Sep 17 00:00:00 2001 From: Zoran Cvetkov Date: Tue, 4 Mar 2025 14:41:09 +0200 Subject: [PATCH 2/3] Revert "store: filter out vids" This reverts commit 3c448deb6c0cb330784919276d58e406f6df9f69. --- graph/src/data/store/mod.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/graph/src/data/store/mod.rs b/graph/src/data/store/mod.rs index 25c0d3e0813..983c70e3cf4 100644 --- a/graph/src/data/store/mod.rs +++ b/graph/src/data/store/mod.rs @@ -892,17 +892,13 @@ impl Entity { // 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 } From bd95fffdfc195c514c885a13b7c96b25f35bbef0 Mon Sep 17 00:00:00 2001 From: Zoran Cvetkov Date: Tue, 4 Mar 2025 14:59:13 +0200 Subject: [PATCH 3/3] Revert "Make VID element internal to the Entity (#5857)" This reverts commit 6bbd0d285e5c8c648c67802c3bedfe553f8ac8f3. --- graph/src/components/store/entity_cache.rs | 13 +++- graph/src/data/store/mod.rs | 88 ++++------------------ graph/src/util/intern.rs | 39 ---------- 3 files changed, 26 insertions(+), 114 deletions(-) diff --git a/graph/src/components/store/entity_cache.rs b/graph/src/components/store/entity_cache.rs index 062dd67dfc2..1a002789d8f 100644 --- a/graph/src/components/store/entity_cache.rs +++ b/graph/src/components/store/entity_cache.rs @@ -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>) -> Option { + 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, }); diff --git a/graph/src/data/store/mod.rs b/graph/src/data/store/mod.rs index 983c70e3cf4..557dc5ad28e 100644 --- a/graph/src/data/store/mod.rs +++ b/graph/src/data/store/mod.rs @@ -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); impl<'a> IntoIterator for &'a Entity { - type Item = (&'a str, &'a Value); + type Item = (Word, Value); - type IntoIter = - std::iter::Filter, fn(&(&'a str, &'a Value)) -> bool>; + type IntoIter = intern::ObjectOwningIter; fn into_iter(self) -> Self::IntoIter { - (&self.0).into_iter().filter(|(k, _)| *k != VID_FIELD) + self.0.clone().into_iter() } } @@ -875,18 +874,10 @@ 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) } @@ -924,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") @@ -936,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. @@ -1059,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)] @@ -1085,14 +1077,6 @@ impl Entity { ) -> Result, 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> { @@ -1284,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); -} diff --git a/graph/src/util/intern.rs b/graph/src/util/intern.rs index 62ff3b4618f..c29f4a3672e 100644 --- a/graph/src/util/intern.rs +++ b/graph/src/util/intern.rs @@ -308,45 +308,6 @@ impl Object { } } -impl Object { - 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 Object { /// Remove `key` from the object and return the value that was /// associated with the `key`. The entry is actually not removed for