Skip to content

Commit c350e4f

Browse files
authored
graph, tests : Fix loadDerived not taking entity cache into consideration when loading derived entities (#4799)
- graph: Consider `udpates` and `handler_updates` in `load_related` - Add more tests for derived loaders
1 parent 27cbcdd commit c350e4f

File tree

20 files changed

+772
-247
lines changed

20 files changed

+772
-247
lines changed

graph/src/components/store/entity_cache.rs

Lines changed: 103 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
use anyhow::anyhow;
2-
use inflector::Inflector;
32
use std::borrow::Cow;
43
use std::collections::HashMap;
54
use std::fmt::{self, Debug};
@@ -203,18 +202,114 @@ impl EntityCache {
203202

204203
let query = DerivedEntityQuery {
205204
entity_type: EntityType::new(base_type.to_string()),
206-
entity_field: field.name.clone().to_snake_case().into(),
205+
entity_field: field.name.clone().into(),
207206
value: eref.entity_id.clone(),
208207
causality_region: eref.causality_region,
209208
id_is_bytes: id_is_bytes,
210209
};
211210

212-
let entities = self.store.get_derived(&query)?;
213-
entities.iter().for_each(|(key, e)| {
214-
self.current.insert(key.clone(), Some(e.clone()));
215-
});
216-
let entities: Vec<Entity> = entities.values().cloned().collect();
217-
Ok(entities)
211+
let mut entity_map = self.store.get_derived(&query)?;
212+
213+
for (key, entity) in entity_map.iter() {
214+
// Only insert to the cache if it's not already there
215+
if !self.current.contains_key(&key) {
216+
self.current.insert(key.clone(), Some(entity.clone()));
217+
}
218+
}
219+
220+
let mut keys_to_remove = Vec::new();
221+
222+
// Apply updates from `updates` and `handler_updates` directly to entities in `entity_map` that match the query
223+
for (key, entity) in entity_map.iter_mut() {
224+
let mut entity_cow = Some(Cow::Borrowed(entity));
225+
226+
if let Some(op) = self.updates.get(key).cloned() {
227+
op.apply_to(&mut entity_cow)
228+
.map_err(|e| key.unknown_attribute(e))?;
229+
}
230+
231+
if let Some(op) = self.handler_updates.get(key).cloned() {
232+
op.apply_to(&mut entity_cow)
233+
.map_err(|e| key.unknown_attribute(e))?;
234+
}
235+
236+
if let Some(updated_entity) = entity_cow {
237+
*entity = updated_entity.into_owned();
238+
} else {
239+
// if entity_cow is None, it means that the entity was removed by an update
240+
// mark the key for removal from the map
241+
keys_to_remove.push(key.clone());
242+
}
243+
}
244+
245+
// A helper function that checks if an update matches the query and returns the updated entity if it does
246+
fn matches_query(
247+
op: &EntityOp,
248+
query: &DerivedEntityQuery,
249+
key: &EntityKey,
250+
) -> Result<Option<Entity>, anyhow::Error> {
251+
match op {
252+
EntityOp::Update(entity) | EntityOp::Overwrite(entity)
253+
if query.matches(key, entity) =>
254+
{
255+
Ok(Some(entity.clone()))
256+
}
257+
EntityOp::Remove => Ok(None),
258+
_ => Ok(None),
259+
}
260+
}
261+
262+
// Iterate over self.updates to find entities that:
263+
// - Aren't already present in the entity_map
264+
// - Match the query
265+
// If these conditions are met:
266+
// - Check if there's an update for the same entity in handler_updates and apply it.
267+
// - Add the entity to entity_map.
268+
for (key, op) in self.updates.iter() {
269+
if !entity_map.contains_key(key) {
270+
if let Some(entity) = matches_query(op, &query, key)? {
271+
if let Some(handler_op) = self.handler_updates.get(key).cloned() {
272+
// If there's a corresponding update in handler_updates, apply it to the entity
273+
// and insert the updated entity into entity_map
274+
let mut entity_cow = Some(Cow::Borrowed(&entity));
275+
handler_op
276+
.apply_to(&mut entity_cow)
277+
.map_err(|e| key.unknown_attribute(e))?;
278+
279+
if let Some(updated_entity) = entity_cow {
280+
entity_map.insert(key.clone(), updated_entity.into_owned());
281+
}
282+
} else {
283+
// If there isn't a corresponding update in handler_updates or the update doesn't match the query, just insert the entity from self.updates
284+
entity_map.insert(key.clone(), entity);
285+
}
286+
}
287+
}
288+
}
289+
290+
// Iterate over handler_updates to find entities that:
291+
// - Aren't already present in the entity_map.
292+
// - Aren't present in self.updates.
293+
// - Match the query.
294+
// If these conditions are met, add the entity to entity_map.
295+
for (key, handler_op) in self.handler_updates.iter() {
296+
if !entity_map.contains_key(key) && !self.updates.contains_key(key) {
297+
if let Some(entity) = matches_query(handler_op, &query, key)? {
298+
entity_map.insert(key.clone(), entity);
299+
}
300+
}
301+
}
302+
303+
// Remove entities that are in the store but have been removed by an update.
304+
// We do this last since the loops over updates and handler_updates are only
305+
// concerned with entities that are not in the store yet and by leaving removed
306+
// keys in entity_map we avoid processing these updates a second time when we
307+
// already looked at them when we went through entity_map
308+
for key in keys_to_remove {
309+
entity_map.remove(&key);
310+
}
311+
312+
Ok(entity_map.into_values().collect())
218313
}
219314

220315
pub fn remove(&mut self, key: EntityKey) {

graph/src/components/store/mod.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ use std::borrow::Borrow;
2020
use std::collections::btree_map::Entry;
2121
use std::collections::{BTreeMap, BTreeSet, HashSet};
2222
use std::fmt::Display;
23+
use std::str::FromStr;
2324
use std::sync::atomic::{AtomicUsize, Ordering};
2425
use std::sync::{Arc, RwLock};
2526
use std::time::Duration;
@@ -199,6 +200,22 @@ pub struct DerivedEntityQuery {
199200
pub causality_region: CausalityRegion,
200201
}
201202

203+
impl DerivedEntityQuery {
204+
/// Checks if a given key and entity match this query.
205+
pub fn matches(&self, key: &EntityKey, entity: &Entity) -> bool {
206+
key.entity_type == self.entity_type
207+
&& entity
208+
.get(&self.entity_field)
209+
.map(|v| match v {
210+
Value::String(s) => s.as_str() == self.value.as_str(),
211+
Value::Bytes(b) => Bytes::from_str(self.value.as_str())
212+
.map_or(false, |bytes_value| &bytes_value == b),
213+
_ => false,
214+
})
215+
.unwrap_or(false)
216+
}
217+
}
218+
202219
impl EntityKey {
203220
// For use in tests only
204221
#[cfg(debug_assertions)]

graph/src/util/lfu_cache.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,12 @@ impl<K: Clone + Ord + Eq + Hash + Debug + CacheWeight, V: CacheWeight + Default>
177177
})
178178
}
179179

180+
pub fn iter<'a>(&'a self) -> impl Iterator<Item = (&K, &V)> {
181+
self.queue
182+
.iter()
183+
.map(|entry| (&entry.0.key, &entry.0.value))
184+
}
185+
180186
pub fn get(&mut self, key: &K) -> Option<&V> {
181187
self.get_mut(key.clone()).map(|x| &x.value)
182188
}

store/postgres/src/relational_queries.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ use graph::{
2727
components::store::{AttributeNames, EntityType},
2828
data::store::scalar,
2929
};
30+
use inflector::Inflector;
3031
use itertools::Itertools;
3132
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
3233
use std::convert::TryFrom;
@@ -1760,11 +1761,20 @@ impl<'a> QueryFragment<Pg> for FindDerivedQuery<'a> {
17601761
if i > 0 {
17611762
out.push_sql(", ");
17621763
}
1763-
out.push_bind_param::<Text, _>(&value.entity_id.as_str())?;
1764+
1765+
if *id_is_bytes {
1766+
out.push_sql("decode(");
1767+
out.push_bind_param::<Text, _>(
1768+
&value.entity_id.as_str().strip_prefix("0x").unwrap(),
1769+
)?;
1770+
out.push_sql(", 'hex')");
1771+
} else {
1772+
out.push_bind_param::<Text, _>(&value.entity_id.as_str())?;
1773+
}
17641774
}
17651775
out.push_sql(") and ");
17661776
}
1767-
out.push_identifier(entity_field.as_str())?;
1777+
out.push_identifier(entity_field.to_snake_case().as_str())?;
17681778
out.push_sql(" = ");
17691779
if *id_is_bytes {
17701780
out.push_sql("decode(");

store/postgres/src/writable.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::collections::BTreeSet;
22
use std::ops::Deref;
3+
use std::str::FromStr;
34
use std::sync::atomic::{AtomicBool, Ordering};
45
use std::sync::{Mutex, RwLock, TryLockError as RwLockError};
56
use std::time::{Duration, Instant};
@@ -10,6 +11,8 @@ use graph::components::store::{
1011
Batch, DeploymentCursorTracker, DerivedEntityQuery, EntityKey, ReadStore,
1112
};
1213
use graph::constraint_violation;
14+
use graph::data::store::scalar::Bytes;
15+
use graph::data::store::Value;
1316
use graph::data::subgraph::schema;
1417
use graph::data_source::CausalityRegion;
1518
use graph::prelude::{
@@ -1100,7 +1103,12 @@ impl Queue {
11001103
fn is_related(derived_query: &DerivedEntityQuery, entity: &Entity) -> bool {
11011104
entity
11021105
.get(&derived_query.entity_field)
1103-
.map(|related_id| related_id.as_str() == Some(&derived_query.value))
1106+
.map(|v| match v {
1107+
Value::String(s) => s.as_str() == derived_query.value.as_str(),
1108+
Value::Bytes(b) => Bytes::from_str(derived_query.value.as_str())
1109+
.map_or(false, |bytes_value| &bytes_value == b),
1110+
_ => false,
1111+
})
11041112
.unwrap_or(false)
11051113
}
11061114

tests/integration-tests/derived-loaders/abis/Contract.abi

Lines changed: 0 additions & 33 deletions
This file was deleted.

tests/integration-tests/derived-loaders/schema.graphql

Lines changed: 0 additions & 21 deletions
This file was deleted.

tests/integration-tests/derived-loaders/src/mapping.ts

Lines changed: 0 additions & 34 deletions
This file was deleted.

0 commit comments

Comments
 (0)