Skip to content

Commit 49e9f4e

Browse files
committed
graphql, store: Prefetch q::Value, not Entity, to avoid conversion
1 parent 97f11d5 commit 49e9f4e

File tree

7 files changed

+159
-27
lines changed

7 files changed

+159
-27
lines changed

graph/src/components/store.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -826,6 +826,11 @@ pub trait Store: Send + Sync + 'static {
826826
/// Queries the store for entities that match the store query.
827827
fn find(&self, query: EntityQuery) -> Result<Vec<Entity>, QueryExecutionError>;
828828

829+
fn find_query_values(
830+
&self,
831+
query: EntityQuery,
832+
) -> Result<Vec<BTreeMap<String, graphql_parser::query::Value>>, QueryExecutionError>;
833+
829834
/// Queries the store for a single entity matching the store query.
830835
fn find_one(&self, query: EntityQuery) -> Result<Option<Entity>, QueryExecutionError>;
831836

@@ -1250,6 +1255,13 @@ impl Store for MockStore {
12501255
unimplemented!()
12511256
}
12521257

1258+
fn find_query_values(
1259+
&self,
1260+
_: EntityQuery,
1261+
) -> Result<Vec<BTreeMap<String, graphql_parser::query::Value>>, QueryExecutionError> {
1262+
unimplemented!()
1263+
}
1264+
12531265
fn find_one(&self, _query: EntityQuery) -> Result<Option<Entity>, QueryExecutionError> {
12541266
unimplemented!()
12551267
}

graphql/src/store/prefetch.rs

Lines changed: 36 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,14 @@ use graphql_parser::query as q;
55
use graphql_parser::schema as s;
66
use lazy_static::lazy_static;
77
use std::collections::{BTreeMap, HashMap, HashSet};
8-
use std::ops::Deref;
98
use std::rc::Rc;
109
use std::time::Instant;
1110

1211
use graph::data::graphql::ext::ObjectTypeExt;
1312
use graph::prelude::{
14-
BlockNumber, ChildMultiplicity, Entity, EntityCollection, EntityFilter, EntityLink,
15-
EntityOrder, EntityWindow, Logger, ParentLink, QueryExecutionError, Schema, Store,
16-
Value as StoreValue, WindowAttribute,
13+
BlockNumber, ChildMultiplicity, EntityCollection, EntityFilter, EntityLink, EntityOrder,
14+
EntityWindow, Logger, ParentLink, QueryExecutionError, Schema, Store, Value as StoreValue,
15+
WindowAttribute,
1716
};
1817

1918
use crate::execution::{ExecutionContext, ObjectOrInterface, Resolver};
@@ -87,7 +86,7 @@ impl From<Option<q::TypeCondition>> for TypeCondition {
8786
/// has an entry mapping the response key to the list of nodes.
8887
#[derive(Debug, Clone)]
8988
struct Node {
90-
entity: Entity,
89+
entity: BTreeMap<String, q::Value>,
9190
/// We are using an `Rc` here for two reasons: it allows us to defer
9291
/// copying objects until the end, when converting to `q::Value` forces
9392
/// us to copy any child that is referenced by multiple parents. It also
@@ -97,8 +96,8 @@ struct Node {
9796
children: BTreeMap<String, Vec<Rc<Node>>>,
9897
}
9998

100-
impl From<Entity> for Node {
101-
fn from(entity: Entity) -> Self {
99+
impl From<BTreeMap<String, q::Value>> for Node {
100+
fn from(entity: BTreeMap<String, q::Value>) -> Self {
102101
Node {
103102
entity,
104103
children: BTreeMap::default(),
@@ -137,7 +136,7 @@ fn is_root_node(nodes: &Vec<Node>) -> bool {
137136

138137
fn make_root_node() -> Vec<Node> {
139138
vec![Node {
140-
entity: Entity::new(),
139+
entity: BTreeMap::new(),
141140
children: BTreeMap::default(),
142141
}]
143142
}
@@ -148,23 +147,44 @@ fn make_root_node() -> Vec<Node> {
148147
/// with any field of the entity.
149148
impl From<Node> for q::Value {
150149
fn from(node: Node) -> Self {
151-
let mut map: BTreeMap<_, _> = node.entity.into();
150+
let mut map = node.entity;
152151
for (key, nodes) in node.children.into_iter() {
153152
map.insert(format!("prefetch:{}", key), node_list_as_value(nodes));
154153
}
155154
q::Value::Object(map)
156155
}
157156
}
158157

159-
impl Deref for Node {
160-
type Target = Entity;
158+
trait ValueExt {
159+
fn as_str(&self) -> Option<&str>;
160+
}
161161

162-
fn deref(&self) -> &Self::Target {
163-
&self.entity
162+
impl ValueExt for q::Value {
163+
fn as_str(&self) -> Option<&str> {
164+
match self {
165+
q::Value::String(s) => Some(s),
166+
_ => None,
167+
}
164168
}
165169
}
166170

167171
impl Node {
172+
fn id(&self) -> Result<String, graph::prelude::failure::Error> {
173+
match self.get("id") {
174+
None => Err(graph::prelude::failure::format_err!(
175+
"Entity is missing an `id` attribute"
176+
)),
177+
Some(q::Value::String(s)) => Ok(s.to_owned()),
178+
_ => Err(graph::prelude::failure::format_err!(
179+
"Entity has non-string `id` attribute"
180+
)),
181+
}
182+
}
183+
184+
fn get(&self, key: &str) -> Option<&q::Value> {
185+
self.entity.get(key)
186+
}
187+
168188
fn typename(&self) -> &str {
169189
self.get("__typename")
170190
.expect("all entities have a __typename")
@@ -288,7 +308,7 @@ impl<'a> JoinCond<'a> {
288308
.filter_map(|(id, node)| {
289309
node.get(*child_field)
290310
.and_then(|value| match value {
291-
StoreValue::List(values) => {
311+
q::Value::List(values) => {
292312
let values: Vec<_> = values
293313
.into_iter()
294314
.filter_map(|value| {
@@ -382,7 +402,7 @@ impl<'a> Join<'a> {
382402
.get("g$parent_id")
383403
.expect("the query that produces 'child' ensures there is always a g$parent_id")
384404
{
385-
StoreValue::String(key) => grouped.entry(key).or_default().push(child.clone()),
405+
q::Value::String(key) => grouped.entry(&key).or_default().push(child.clone()),
386406
_ => unreachable!("the parent_id returned by the query is always a string"),
387407
}
388408
}
@@ -871,6 +891,6 @@ fn fetch<S: Store>(
871891
}
872892

873893
store
874-
.find(query)
894+
.find_query_values(query)
875895
.map(|entities| entities.into_iter().map(|entity| entity.into()).collect())
876896
}

mock/src/store.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,13 @@ impl Store for MockStore {
9696
unimplemented!()
9797
}
9898

99+
fn find_query_values(
100+
&self,
101+
_: EntityQuery,
102+
) -> Result<Vec<BTreeMap<String, graphql_parser::query::Value>>, QueryExecutionError> {
103+
unimplemented!()
104+
}
105+
99106
fn find_one(&self, _query: EntityQuery) -> Result<Option<Entity>, QueryExecutionError> {
100107
unimplemented!()
101108
}

store/postgres/src/relational_queries.rs

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,14 @@ impl FromEntityData for Entity {
185185
}
186186
}
187187

188+
impl FromEntityData for BTreeMap<String, graphql_parser::query::Value> {
189+
type Value = graphql_parser::query::Value;
190+
191+
fn insert_entity_data(&mut self, key: String, v: Self::Value) {
192+
self.insert(key, v);
193+
}
194+
}
195+
188196
pub trait FromColumnValue: Sized {
189197
fn is_null(&self) -> bool;
190198

@@ -196,6 +204,79 @@ pub trait FromColumnValue: Sized {
196204
) -> Result<Self, StoreError>;
197205
}
198206

207+
impl FromColumnValue for graphql_parser::query::Value {
208+
fn is_null(&self) -> bool {
209+
self == &graphql_parser::query::Value::Null
210+
}
211+
212+
fn from_string(s: String) -> Self {
213+
graphql_parser::query::Value::String(s)
214+
}
215+
216+
fn from_column_value(
217+
column_type: &ColumnType,
218+
json: serde_json::Value,
219+
) -> Result<graphql_parser::query::Value, StoreError> {
220+
use graphql_parser::query::Value as q;
221+
use serde_json::Value as j;
222+
// Many possible conversion errors are already caught by how
223+
// we define the schema; for example, we can only get a NULL for
224+
// a column that is actually nullable
225+
match (json, column_type) {
226+
(j::Null, _) => Ok(q::Null),
227+
(j::Bool(b), _) => Ok(q::Boolean(b)),
228+
(j::Number(number), ColumnType::Int) => match number.as_i64() {
229+
Some(i) => i32::try_from(i).map(|i| q::Int(i.into())).map_err(|e| {
230+
StoreError::Unknown(format_err!("failed to convert {} to Int: {}", number, e))
231+
}),
232+
None => Err(StoreError::Unknown(format_err!(
233+
"failed to convert {} to Int",
234+
number
235+
))),
236+
},
237+
(j::Number(number), ColumnType::BigDecimal) => {
238+
let s = number.to_string();
239+
scalar::BigDecimal::from_str(s.as_str())
240+
.map(|d| q::String(d.to_string()))
241+
.map_err(|e| {
242+
StoreError::Unknown(format_err!(
243+
"failed to convert {} to BigDecimal: {}",
244+
number,
245+
e
246+
))
247+
})
248+
}
249+
(j::Number(number), ColumnType::BigInt) => Ok(q::String(number.to_string())),
250+
(j::Number(number), column_type) => Err(StoreError::Unknown(format_err!(
251+
"can not convert number {} to {:?}",
252+
number,
253+
column_type
254+
))),
255+
(j::String(s), ColumnType::String) | (j::String(s), ColumnType::Enum(_)) => {
256+
Ok(q::String(s))
257+
}
258+
(j::String(s), ColumnType::Bytes) => {
259+
Ok(q::String(format!("0x{}", s.trim_start_matches("\\x"))))
260+
}
261+
(j::String(s), ColumnType::BytesId) => Ok(q::String(bytes_as_str(&s))),
262+
(j::String(s), column_type) => Err(StoreError::Unknown(format_err!(
263+
"can not convert string {} to {:?}",
264+
s,
265+
column_type
266+
))),
267+
(j::Array(values), _) => Ok(q::List(
268+
values
269+
.into_iter()
270+
.map(|v| Self::from_column_value(column_type, v))
271+
.collect::<Result<Vec<_>, _>>()?,
272+
)),
273+
(j::Object(_), _) => {
274+
unimplemented!("objects as entity attributes are not needed/supported")
275+
}
276+
}
277+
}
278+
}
279+
199280
impl FromColumnValue for graph::prelude::Value {
200281
fn is_null(&self) -> bool {
201282
self == &Value::Null

store/postgres/src/store.rs

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -465,14 +465,16 @@ impl Store {
465465
entity_ids: mut expected_entity_ids,
466466
} => {
467467
// Execute query
468+
let actual_entities =
468469
self.execute_query::<Entity>(conn, query.clone())
469-
format_err!(
470-
"AbortUnless ({}): query execution error: {:?}, {}",
471-
description,
472-
query,
473-
e
474-
)
475-
})?;
470+
.map_err(|e| {
471+
format_err!(
472+
"AbortUnless ({}): query execution error: {:?}, {}",
473+
description,
474+
query,
475+
e
476+
)
477+
})?;
476478

477479
// Extract IDs from entities
478480
let mut actual_entity_ids: Vec<String> = actual_entities
@@ -1018,6 +1020,16 @@ impl StoreTrait for Store {
10181020
self.execute_query(&conn, query)
10191021
}
10201022

1023+
fn find_query_values(
1024+
&self,
1025+
query: EntityQuery,
1026+
) -> Result<Vec<BTreeMap<String, graphql_parser::query::Value>>, QueryExecutionError> {
1027+
let conn = self
1028+
.get_entity_conn(&query.subgraph_id)
1029+
.map_err(|e| QueryExecutionError::StoreError(e.into()))?;
1030+
self.execute_query(&conn, query)
1031+
}
1032+
10211033
fn find_one(&self, mut query: EntityQuery) -> Result<Option<Entity>, QueryExecutionError> {
10221034
query.range = EntityRange::first(1);
10231035

store/postgres/tests/relational.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -538,7 +538,7 @@ fn count_scalar_entities(conn: &PgConnection, layout: &Layout) -> usize {
538538
]);
539539
let collection = EntityCollection::All(vec!["Scalar".to_owned()]);
540540
layout
541-
.query(
541+
.query::<Entity>(
542542
&*LOGGER,
543543
&conn,
544544
collection,
@@ -638,7 +638,7 @@ fn test_find(expected_entity_ids: Vec<&str>, query: EntityQuery) {
638638

639639
let unordered = matches!(query.order, EntityOrder::Unordered);
640640
let entities = layout
641-
.query(
641+
.query::<Entity>(
642642
&*LOGGER,
643643
conn,
644644
query.collection,
@@ -1374,7 +1374,7 @@ fn text_find(expected_entity_ids: Vec<&str>, filter: EntityFilter) {
13741374
let query = query(vec!["Ferret"]).filter(filter).asc("id");
13751375

13761376
let entities = layout
1377-
.query(
1377+
.query::<Entity>(
13781378
&*LOGGER,
13791379
conn,
13801380
query.collection,

store/postgres/tests/relational_bytes.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -385,7 +385,7 @@ fn make_thing_tree(conn: &PgConnection, layout: &Layout) -> (Entity, Entity, Ent
385385
fn query() {
386386
fn fetch(conn: &PgConnection, layout: &Layout, coll: EntityCollection) -> Vec<String> {
387387
layout
388-
.query(
388+
.query::<Entity>(
389389
&*LOGGER,
390390
conn,
391391
coll,

0 commit comments

Comments
 (0)