Skip to content

Commit eb5e78f

Browse files
authored
store, graph, graphql: Fix prefetching bug when selecting by specific attributes: allow orderBy
This fixes errors in prefetching phase for queries that orders the selection set with the `orderBy` attribute. The changes rely on complementing the selection set with the fields used for ordering. The problematic part is that each visited selection set is unaware of those ordering fields due to them not being in scope during the recursive invocations of the `execute_selection_set` function. This led to malformed SQL queries that tried to `ORDER BY` columns that were not being selected in sub-queries. The solution presented here was to pass those complementary fields as arguments along the recursive traversal and injecting them into their respective selection sets.
1 parent 3353b8d commit eb5e78f

File tree

4 files changed

+171
-43
lines changed

4 files changed

+171
-43
lines changed

graph/src/components/store.rs

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1785,7 +1785,7 @@ pub enum AttributeNames {
17851785
}
17861786

17871787
impl AttributeNames {
1788-
pub fn insert(&mut self, column_name: &str) {
1788+
fn insert(&mut self, column_name: &str) {
17891789
match self {
17901790
AttributeNames::All => {
17911791
let mut set = BTreeSet::new();
@@ -1798,14 +1798,27 @@ impl AttributeNames {
17981798
}
17991799
}
18001800

1801-
pub fn update(&mut self, field: &q::Field) {
1802-
// ignore "meta" field names
1803-
if field.name.starts_with("__") {
1801+
/// Adds a attribute name. Ignores meta fields.
1802+
pub fn add(&mut self, field: &q::Field) {
1803+
if Self::is_meta_field(&field.name) {
18041804
return;
18051805
}
18061806
self.insert(&field.name)
18071807
}
18081808

1809+
/// Adds a attribute name. Ignores meta fields.
1810+
pub fn add_str(&mut self, field_name: &str) {
1811+
if Self::is_meta_field(field_name) {
1812+
return;
1813+
}
1814+
self.insert(field_name);
1815+
}
1816+
1817+
/// Returns `true` for meta field names, `false` otherwise.
1818+
fn is_meta_field(field_name: &str) -> bool {
1819+
field_name.starts_with("__")
1820+
}
1821+
18091822
pub fn extend(&mut self, other: Self) {
18101823
use AttributeNames::*;
18111824
match (self, other) {

graph/src/data/graphql/object_or_interface.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::prelude::Schema;
22
use crate::{components::store::EntityType, prelude::s};
3+
use std::cmp::Ordering;
34
use std::collections::BTreeMap;
45
use std::hash::{Hash, Hasher};
56
use std::mem;
@@ -32,6 +33,24 @@ impl<'a> Hash for ObjectOrInterface<'a> {
3233
}
3334
}
3435

36+
impl<'a> PartialOrd for ObjectOrInterface<'a> {
37+
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
38+
Some(self.cmp(other))
39+
}
40+
}
41+
42+
impl<'a> Ord for ObjectOrInterface<'a> {
43+
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
44+
use ObjectOrInterface::*;
45+
match (self, other) {
46+
(Object(a), Object(b)) => a.name.cmp(&b.name),
47+
(Interface(a), Interface(b)) => a.name.cmp(&b.name),
48+
(Interface(_), Object(_)) => Ordering::Less,
49+
(Object(_), Interface(_)) => Ordering::Greater,
50+
}
51+
}
52+
}
53+
3554
impl<'a> From<&'a s::ObjectType> for ObjectOrInterface<'a> {
3655
fn from(object: &'a s::ObjectType) -> Self {
3756
ObjectOrInterface::Object(object)

graphql/src/store/prefetch.rs

Lines changed: 101 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
//! final result
33
44
use anyhow::{anyhow, Error};
5+
use graph::constraint_violation;
56
use graph::prelude::{r, CacheWeight};
67
use graph::slog::warn;
78
use graph::util::cache_weight::btree_node_size;
@@ -18,12 +19,12 @@ use graph::{
1819
prelude::{
1920
q, s, ApiSchema, AttributeNames, BlockNumber, ChildMultiplicity, EntityCollection,
2021
EntityFilter, EntityLink, EntityOrder, EntityWindow, Logger, ParentLink,
21-
QueryExecutionError, QueryStore, Value as StoreValue, WindowAttribute,
22+
QueryExecutionError, QueryStore, StoreError, Value as StoreValue, WindowAttribute,
2223
},
2324
};
2425

2526
use crate::execution::{ExecutionContext, Resolver};
26-
use crate::query::ast as qast;
27+
use crate::query::ast::{self as qast, get_argument_value};
2728
use crate::runner::ResultSizeMetrics;
2829
use crate::schema::ast as sast;
2930
use crate::store::{build_query, StoreResolver};
@@ -49,6 +50,10 @@ lazy_static! {
4950

5051
type GroupedFieldSet<'a> = IndexMap<&'a str, CollectedResponseKey<'a>>;
5152

53+
/// Used for associating objects or interfaces and the field names used in `orderBy` query field
54+
/// attributes.
55+
type ComplementaryFields<'a> = BTreeMap<ObjectOrInterface<'a>, String>;
56+
5257
/// An `ObjectType` with `Hash` and `Eq` derived from the name.
5358
#[derive(Clone, Debug)]
5459
pub struct ObjectCondition<'a>(&'a s::ObjectType);
@@ -555,10 +560,17 @@ fn execute_root_selection_set(
555560
) -> Result<Vec<Node>, Vec<QueryExecutionError>> {
556561
// Obtain the root Query type and fail if there isn't one
557562
let query_type = ctx.query.schema.query_type.as_ref().into();
558-
let grouped_field_set = collect_fields(ctx, query_type, once(selection_set));
563+
let (grouped_field_set, _complementary_fields) =
564+
collect_fields(ctx, query_type, once(selection_set));
559565

560566
// Execute the root selection set against the root query type
561-
execute_selection_set(resolver, ctx, make_root_node(), grouped_field_set)
567+
execute_selection_set(
568+
resolver,
569+
ctx,
570+
make_root_node(),
571+
grouped_field_set,
572+
ComplementaryFields::new(),
573+
)
562574
}
563575

564576
fn check_result_size(logger: &Logger, size: usize) -> Result<(), QueryExecutionError> {
@@ -576,6 +588,7 @@ fn execute_selection_set<'a>(
576588
ctx: &'a ExecutionContext<impl Resolver>,
577589
mut parents: Vec<Node>,
578590
grouped_field_set: GroupedFieldSet<'a>,
591+
mut complementary_fields: ComplementaryFields<'a>,
579592
) -> Result<Vec<Node>, Vec<QueryExecutionError>> {
580593
let schema = &ctx.query.schema;
581594
let mut errors: Vec<QueryExecutionError> = Vec::new();
@@ -619,7 +632,7 @@ fn execute_selection_set<'a>(
619632
&field.name,
620633
);
621634
// Group fields with the same response key, so we can execute them together
622-
let mut grouped_field_set =
635+
let (mut grouped_field_set, new_complementary_fields) =
623636
collect_fields(ctx, child_type, fields.iter().map(|f| &f.selection_set));
624637

625638
// "Select by Specific Attribute Names" is an experimental feature and can be disabled completely.
@@ -630,8 +643,10 @@ fn execute_selection_set<'a>(
630643
if *DISABLE_EXPERIMENTAL_FEATURE_SELECT_BY_SPECIFIC_ATTRIBUTE_NAMES {
631644
BTreeMap::new()
632645
} else {
633-
CollectedAttributeNames::consolidate_column_names(&mut grouped_field_set)
634-
.resolve_interfaces(&ctx.query.schema.types_for_interface())
646+
let mut collected =
647+
CollectedAttributeNames::consolidate_column_names(&mut grouped_field_set);
648+
collected.populate_complementary_fields(&mut complementary_fields);
649+
collected.resolve_interfaces(&ctx.query.schema.types_for_interface())
635650
};
636651

637652
match execute_field(
@@ -645,7 +660,13 @@ fn execute_selection_set<'a>(
645660
collected_columns,
646661
) {
647662
Ok(children) => {
648-
match execute_selection_set(resolver, ctx, children, grouped_field_set) {
663+
match execute_selection_set(
664+
resolver,
665+
ctx,
666+
children,
667+
grouped_field_set,
668+
new_complementary_fields,
669+
) {
649670
Ok(children) => {
650671
Join::perform(&mut parents, children, response_key);
651672
let weight =
@@ -662,6 +683,23 @@ fn execute_selection_set<'a>(
662683
}
663684
}
664685

686+
// Confidence check: all complementary fields must be consumed, otherwise constructed SQL
687+
// queries will be malformed.
688+
if !*DISABLE_EXPERIMENTAL_FEATURE_SELECT_BY_SPECIFIC_ATTRIBUTE_NAMES {
689+
complementary_fields
690+
.into_iter()
691+
.for_each(|(parent, complementary_field)| {
692+
errors.push(
693+
constraint_violation!(
694+
"Complementary field \"{}\" was not prefetched by its parent: {}",
695+
complementary_field,
696+
parent.name().to_string(),
697+
)
698+
.into(),
699+
)
700+
});
701+
}
702+
665703
if errors.is_empty() {
666704
Ok(parents)
667705
} else {
@@ -755,8 +793,9 @@ fn collect_fields<'a>(
755793
ctx: &'a ExecutionContext<impl Resolver>,
756794
parent_ty: ObjectOrInterface<'a>,
757795
selection_sets: impl Iterator<Item = &'a q::SelectionSet>,
758-
) -> GroupedFieldSet<'a> {
796+
) -> (GroupedFieldSet<'a>, ComplementaryFields<'a>) {
759797
let mut grouped_fields = IndexMap::new();
798+
let mut complementary_fields = ComplementaryFields::new();
760799

761800
for selection_set in selection_sets {
762801
collect_fields_inner(
@@ -765,6 +804,7 @@ fn collect_fields<'a>(
765804
selection_set,
766805
&mut HashSet::new(),
767806
&mut grouped_fields,
807+
&mut complementary_fields,
768808
);
769809
}
770810

@@ -778,7 +818,7 @@ fn collect_fields<'a>(
778818
}
779819
}
780820

781-
grouped_fields
821+
(grouped_fields, complementary_fields)
782822
}
783823

784824
// When querying an object type, `type_condition` will always be that object type, even if it passes
@@ -792,6 +832,7 @@ fn collect_fields_inner<'a>(
792832
selection_set: &'a q::SelectionSet,
793833
visited_fragments: &mut HashSet<&'a str>,
794834
output: &mut GroupedFieldSet<'a>,
835+
complementary_fields: &mut ComplementaryFields<'a>,
795836
) {
796837
fn collect_fragment<'a>(
797838
ctx: &'a ExecutionContext<impl Resolver>,
@@ -800,6 +841,7 @@ fn collect_fields_inner<'a>(
800841
frag_selection_set: &'a q::SelectionSet,
801842
visited_fragments: &mut HashSet<&'a str>,
802843
output: &mut GroupedFieldSet<'a>,
844+
complementary_fields: &mut ComplementaryFields<'a>,
803845
) {
804846
let schema = &ctx.query.schema.document();
805847
let fragment_ty = match frag_ty_condition {
@@ -820,6 +862,7 @@ fn collect_fields_inner<'a>(
820862
&frag_selection_set,
821863
visited_fragments,
822864
output,
865+
complementary_fields,
823866
);
824867
} else {
825868
// This is an interface fragment in the root selection for an interface.
@@ -842,6 +885,7 @@ fn collect_fields_inner<'a>(
842885
&frag_selection_set,
843886
visited_fragments,
844887
output,
888+
complementary_fields,
845889
);
846890
}
847891
}
@@ -863,6 +907,34 @@ fn collect_fields_inner<'a>(
863907
type_condition,
864908
field,
865909
);
910+
911+
// Collect complementary fields used in the `orderBy` query attribute, if present.
912+
if let Some(arguments) = get_argument_value(&field.arguments, "orderBy") {
913+
let schema_field = type_condition.field(&field.name).expect(&format!(
914+
"the field {:?} to exist in {:?}",
915+
&field.name,
916+
&type_condition.name()
917+
));
918+
let field_name = sast::get_field_name(&schema_field.field_type);
919+
let object_or_interface_for_field = ctx
920+
.query
921+
.schema
922+
.document()
923+
.object_or_interface(&field_name)
924+
.expect(&format!(
925+
"The field {:?} to exist in the Document",
926+
field_name
927+
));
928+
match arguments {
929+
graphql_parser::schema::Value::Enum(complementary_field_name) => {
930+
complementary_fields.insert(
931+
object_or_interface_for_field,
932+
complementary_field_name.clone(),
933+
);
934+
}
935+
_ => unimplemented!("unsure on what to do about other variants"),
936+
}
937+
}
866938
}
867939

868940
q::Selection::FragmentSpread(spread) => {
@@ -878,6 +950,7 @@ fn collect_fields_inner<'a>(
878950
&fragment.selection_set,
879951
visited_fragments,
880952
output,
953+
complementary_fields,
881954
);
882955
}
883956
}
@@ -890,6 +963,7 @@ fn collect_fields_inner<'a>(
890963
&fragment.selection_set,
891964
visited_fragments,
892965
output,
966+
complementary_fields,
893967
);
894968
}
895969
}
@@ -999,7 +1073,22 @@ impl<'a> CollectedAttributeNames<'a> {
9991073
self.0
10001074
.entry(object_or_interface)
10011075
.or_insert(AttributeNames::All)
1002-
.update(field);
1076+
.add(field);
1077+
}
1078+
1079+
/// Injects complementary fields that were collected priviously in upper hierarchical levels of
1080+
/// the query into `self`.
1081+
fn populate_complementary_fields(
1082+
&mut self,
1083+
complementary_fields: &mut ComplementaryFields<'a>,
1084+
) {
1085+
for (object_or_interface, selected_attributes) in self.0.iter_mut() {
1086+
if let Some(complementary_field_name) =
1087+
complementary_fields.remove(&object_or_interface)
1088+
{
1089+
selected_attributes.add_str(&complementary_field_name)
1090+
}
1091+
}
10031092
}
10041093

10051094
/// Consume this instance and transform it into a mapping from
@@ -1090,7 +1179,7 @@ fn filter_derived_fields(
10901179
None // field does not exist
10911180
}
10921181
})
1093-
.for_each(|col| filtered.insert(&col));
1182+
.for_each(|col| filtered.add_str(&col));
10941183
filtered
10951184
}
10961185
}

0 commit comments

Comments
 (0)