Skip to content

Commit

Permalink
Add an iterator over all the required properties of a Vertex (#449)
Browse files Browse the repository at this point in the history
* adding a vec of the vertex properties

* test files

* rust fmt

* Update trustfall_core/test_data/tests/valid_queries/required_properties.graphql.ron

Co-authored-by: Predrag Gruevski <2348618+obi1kenobi@users.noreply.github.com>

* Update trustfall_core/src/interpreter/hints/vertex_info.rs

Co-authored-by: Predrag Gruevski <2348618+obi1kenobi@users.noreply.github.com>

* returning iterator instead of vec

* remove edges from the iterator

* simplify closure

* flat_map instead of pure map followed by flatten

* removing commented code

* use HashSet internally instead of vec

* uses filter_map

* chain iterators

* documentation

* using vec instead of hashset for test

* one more test covering duplicated properties and fold with transform

* more docs

* fix ilegal test

* fix wording

* exporting new struct

* Update trustfall_core/src/interpreter/hints/vertex_info.rs

---------

Co-authored-by: Predrag Gruevski <2348618+obi1kenobi@users.noreply.github.com>
  • Loading branch information
era and obi1kenobi committed Aug 21, 2023
1 parent 84a2b4a commit f1b81aa
Show file tree
Hide file tree
Showing 15 changed files with 1,770 additions and 5 deletions.
4 changes: 2 additions & 2 deletions trustfall/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ pub mod provider {
pub use trustfall_core::interpreter::basic_adapter::BasicAdapter;
pub use trustfall_core::interpreter::{
Adapter, CandidateValue, ContextIterator, ContextOutcomeIterator, DataContext,
DynamicallyResolvedValue, EdgeInfo, QueryInfo, Range, ResolveEdgeInfo, ResolveInfo,
Typename, VertexInfo, VertexIterator,
DynamicallyResolvedValue, EdgeInfo, QueryInfo, Range, RequiredProperty, ResolveEdgeInfo,
ResolveInfo, Typename, VertexInfo, VertexIterator,
};
pub use trustfall_core::ir::{EdgeParameters, Eid, Vid};

Expand Down
2 changes: 1 addition & 1 deletion trustfall_core/src/interpreter/hints/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ mod vertex_info;

pub use candidates::{CandidateValue, Range};
pub use dynamic::DynamicallyResolvedValue;
pub use vertex_info::VertexInfo;
pub use vertex_info::{RequiredProperty, VertexInfo};

/// Contains overall information about the query being executed, such as its outputs and variables.
#[non_exhaustive]
Expand Down
52 changes: 51 additions & 1 deletion trustfall_core/src/interpreter/hints/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -546,7 +546,7 @@ mod static_property_values {
use std::ops::Bound;

use crate::{
interpreter::hints::{CandidateValue, Range},
interpreter::hints::{vertex_info::RequiredProperty, CandidateValue, Range},
ir::FieldValue,
};

Expand Down Expand Up @@ -580,6 +580,56 @@ mod static_property_values {
assert_eq!(adapter.on_starting_vertices.borrow()[&vid(1)].calls, 1);
}

#[test]
fn required_properties_test() {
let input_name = "required_properties";

let adapter = TestAdapter {
on_starting_vertices: btreemap! {
vid(1) => TrackCalls::<ResolveInfoFn>::new_underlying(Box::new(|info| {
assert!(info.coerced_to_type().is_none());
assert_eq!(vid(1), info.vid());

assert_eq!(vec![
RequiredProperty::new("value".into()),
RequiredProperty::new("__typename".into()),
RequiredProperty::new("name".into()),
], info.required_properties().collect::<Vec<RequiredProperty>>());
})),
}
.into(),
..Default::default()
};

let adapter = run_query(adapter, input_name);
assert_eq!(adapter.on_starting_vertices.borrow()[&vid(1)].calls, 1);
}

#[test]
fn required_properties_with_output_and_filter() {
let input_name = "required_properties_filter_and_output";

let adapter = TestAdapter {
on_starting_vertices: btreemap! {
vid(1) => TrackCalls::<ResolveInfoFn>::new_underlying(Box::new(|info| {
assert!(info.coerced_to_type().is_none());
assert_eq!(vid(1), info.vid());

assert_eq!(vec![
RequiredProperty::new("value".into()),
RequiredProperty::new("vowelsInName".into()),
RequiredProperty::new("__typename".into()),
], info.required_properties().collect::<Vec<RequiredProperty>>());
})),
}
.into(),
..Default::default()
};

let adapter = run_query(adapter, input_name);
assert_eq!(adapter.on_starting_vertices.borrow()[&vid(1)].calls, 1);
}

#[test]
fn typename_filter() {
let input_name = "typename_filter";
Expand Down
63 changes: 63 additions & 0 deletions trustfall_core/src/interpreter/hints/vertex_info.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use std::collections::HashSet;
use std::{
collections::BTreeMap,
ops::{Bound, RangeBounds},
Expand All @@ -14,6 +15,19 @@ use crate::{

use super::{dynamic::DynamicallyResolvedValue, CandidateValue, EdgeInfo, Range};

/// Represents a required property of a specific vertex
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct RequiredProperty {
pub name: Arc<str>,
}

impl RequiredProperty {
pub fn new(name: Arc<str>) -> Self {
Self { name }
}
}

/// Information about what the currently-executing query needs at a specific vertex.
#[cfg_attr(docsrs, doc(notable_trait))]
pub trait VertexInfo: super::sealed::__Sealed {
Expand All @@ -23,6 +37,17 @@ pub trait VertexInfo: super::sealed::__Sealed {
/// The type coercion (`... on SomeType`) applied by the query at this vertex, if any.
fn coerced_to_type(&self) -> Option<&Arc<str>>;

/// Return all properties required for the current vertex, including: output, filtered, and
/// tagged properties. It's guaranteed that each property will only show once in the iterator,
/// so even if a property has been used as a filter and output, it will only show once.
///
/// There is no guaranteed order.
///
/// This can be especially useful for adapters doing network calls. For example, if the adapter
/// is using a relational database, it can retrieve the name of all properties and
/// only request those columns from the table.
fn required_properties(&self) -> Box<dyn Iterator<Item = RequiredProperty> + '_>;

/// Check whether the query demands this vertex property to have specific values:
/// a single value, or one of a set or range of values. The candidate values
/// are known *statically*: up-front, without executing any of the query.
Expand Down Expand Up @@ -132,6 +157,44 @@ impl<T: InternalVertexInfo + super::sealed::__Sealed> VertexInfo for T {
}
}

fn required_properties(&self) -> Box<dyn Iterator<Item = RequiredProperty> + '_> {
let current_component = self.current_component();

let current_vertex = self.current_vertex();

let properties = current_component
.outputs
.values()
.filter(|c| c.vertex_id == current_vertex.vid)
.map(|c| RequiredProperty::new(c.field_name.clone()));

let properties = properties.chain(
current_vertex
.filters
.iter()
.map(|f| RequiredProperty::new(f.left().field_name.clone())),
);

let properties = properties.chain(current_component.vertices.values().flat_map(|v| {
v.filters
.iter()
.filter_map(|f| match f.right() {
Some(Argument::Tag(FieldRef::ContextField(ctx))) => {
if current_vertex.vid == ctx.vertex_id {
Some(ctx.field_name.clone())
} else {
None
}
}
_ => None,
})
.map(RequiredProperty::new)
}));

let mut seen_property = HashSet::new();
Box::new(properties.filter(move |r| seen_property.insert(r.name.clone())))
}

fn statically_required_property(&self, property: &str) -> Option<CandidateValue<&FieldValue>> {
if self.non_binding_filters() {
// This `VertexInfo` is in a place where the filters applied to fields
Expand Down
2 changes: 1 addition & 1 deletion trustfall_core/src/interpreter/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ pub mod trace;

pub use hints::{
CandidateValue, DynamicallyResolvedValue, EdgeInfo, NeighborInfo, QueryInfo, Range,
ResolveEdgeInfo, ResolveInfo, VertexInfo,
RequiredProperty, ResolveEdgeInfo, ResolveInfo, VertexInfo,
};

/// An iterator of vertices representing data points we are querying.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
Ok(TestParsedGraphQLQuery(
schema_name: "numbers",
query: Query(
root_connection: FieldConnection(
position: Pos(
line: 3,
column: 5,
),
name: "Number",
arguments: {
"max": Int64(2),
"min": Int64(1),
},
),
root_field: FieldNode(
position: Pos(
line: 3,
column: 5,
),
name: "Number",
connections: [
(FieldConnection(
position: Pos(
line: 4,
column: 9,
),
name: "__typename",
), FieldNode(
position: Pos(
line: 4,
column: 9,
),
name: "__typename",
filter: [
FilterDirective(
operation: Equals((), VariableRef("type")),
),
],
)),
(FieldConnection(
position: Pos(
line: 5,
column: 9,
),
name: "value",
), FieldNode(
position: Pos(
line: 5,
column: 9,
),
name: "value",
output: [
OutputDirective(),
],
)),
(FieldConnection(
position: Pos(
line: 6,
column: 9,
),
name: "name",
), FieldNode(
position: Pos(
line: 6,
column: 9,
),
name: "name",
tag: [
TagDirective(),
],
)),
(FieldConnection(
position: Pos(
line: 7,
column: 9,
),
name: "predecessor",
fold: Some(FoldGroup(
fold: FoldDirective(),
)),
), FieldNode(
position: Pos(
line: 7,
column: 9,
),
name: "predecessor",
connections: [
(FieldConnection(
position: Pos(
line: 8,
column: 13,
),
name: "name",
), FieldNode(
position: Pos(
line: 8,
column: 13,
),
name: "name",
output: [
OutputDirective(),
],
)),
],
)),
(FieldConnection(
position: Pos(
line: 10,
column: 9,
),
name: "multiple",
arguments: {
"max": Int64(5),
},
), FieldNode(
position: Pos(
line: 10,
column: 9,
),
name: "multiple",
connections: [
(FieldConnection(
position: Pos(
line: 11,
column: 13,
),
name: "name",
), FieldNode(
position: Pos(
line: 11,
column: 13,
),
name: "name",
filter: [
FilterDirective(
operation: Equals((), TagRef("name")),
),
],
)),
],
)),
],
),
),
arguments: {
"type": String("Prime"),
},
))
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
TestGraphQLQuery (
schema_name: "numbers",
query: r#"
{
Number(min: 1, max: 2) {
__typename @filter(op: "=", value: ["$type"])
value @output
name @tag
predecessor @fold {
name @output
}
multiple(max: 5) {
name @filter(op: "=", value: ["%name"])
}
}
}"#,
arguments: {
"type": String("Prime"),
},
)
Loading

0 comments on commit f1b81aa

Please sign in to comment.