diff --git a/src/query_plan/execution/execution.rs b/src/query_plan/execution/execution.rs index ac1179cc9c..5314c97e20 100644 --- a/src/query_plan/execution/execution.rs +++ b/src/query_plan/execution/execution.rs @@ -1,11 +1,16 @@ -use std::fmt::{Display, Write}; +use std::{ + fmt::{Display, Write}, + mem::{discriminant, Discriminant}, +}; use indenter::indented; use super::super::resolver::Id; +#[derive(Debug, PartialEq, Eq)] pub enum ExecutionStep { Resolve(Id), + ForEach(Id), Sequential(Vec), Parallel(Vec), } @@ -14,11 +19,12 @@ impl Display for ExecutionStep { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { ExecutionStep::Resolve(id) => writeln!(f, "Resolve({id})"), + ExecutionStep::ForEach(id) => writeln!(f, "ForEach({id})"), ExecutionStep::Sequential(steps) | ExecutionStep::Parallel(steps) => { match &self { ExecutionStep::Sequential(_) => writeln!(f, "Sequential:"), ExecutionStep::Parallel(_) => writeln!(f, "Parallel:"), - ExecutionStep::Resolve(_) => unreachable!(), + _ => unreachable!(), }?; let f = &mut indented(f); @@ -33,19 +39,169 @@ impl Display for ExecutionStep { } impl ExecutionStep { - pub fn parallel(mut steps: Vec) -> Self { - if steps.len() == 1 { - steps.pop().unwrap() - } else { - ExecutionStep::Parallel(steps) + fn inner_flatten(dscr: Discriminant, steps: Vec) -> Vec { + let mut result = Vec::with_capacity(steps.len()); + + for step in steps { + let step = step.flatten(); + if dscr == discriminant(&step) { + match step { + ExecutionStep::Sequential(sub_steps) | ExecutionStep::Parallel(sub_steps) => { + for sub_step in sub_steps { + result.push(sub_step); + } + } + _ => unreachable!(), + } + } else { + if !step.is_empty() { + result.push(step); + } + } } + + result } - pub fn sequential(mut steps: Vec) -> Self { - if steps.len() == 1 { - steps.pop().unwrap() - } else { - ExecutionStep::Sequential(steps) + pub fn is_empty(&self) -> bool { + match self { + ExecutionStep::Sequential(steps) | ExecutionStep::Parallel(steps) => steps.is_empty(), + _ => false, + } + } + + pub fn flatten(self) -> Self { + let dscr = discriminant(&self); + match self { + ExecutionStep::Resolve(_) | ExecutionStep::ForEach(_) => self, + ExecutionStep::Sequential(steps) => { + let mut steps = Self::inner_flatten(dscr, steps); + + if steps.len() == 1 { + steps.pop().unwrap() + } else { + ExecutionStep::Sequential(steps) + } + } + ExecutionStep::Parallel(steps) => { + let mut steps = Self::inner_flatten(dscr, steps); + + if steps.len() == 1 { + steps.pop().unwrap() + } else { + ExecutionStep::Parallel(steps) + } + } + } + } +} + +#[cfg(test)] +mod tests { + mod flatten { + use crate::query_plan::{execution::execution::ExecutionStep, resolver::Id}; + + #[test] + fn empty() { + assert_eq!( + ExecutionStep::Sequential(vec![]).flatten(), + ExecutionStep::Sequential(vec![]) + ); + assert_eq!( + ExecutionStep::Parallel(vec![]).flatten(), + ExecutionStep::Parallel(vec![]) + ); + } + + #[test] + fn single() { + assert_eq!( + ExecutionStep::Resolve(Id(0)).flatten(), + ExecutionStep::Resolve(Id(0)) + ); + + assert_eq!( + ExecutionStep::Sequential(vec![ExecutionStep::Resolve(Id(0))]).flatten(), + ExecutionStep::Resolve(Id(0)) + ); + + assert_eq!( + ExecutionStep::Parallel(vec![ExecutionStep::Resolve(Id(0))]).flatten(), + ExecutionStep::Resolve(Id(0)) + ); + } + + #[test] + fn sequential() { + assert_eq!( + ExecutionStep::Sequential(vec![ + ExecutionStep::Resolve(Id(0)), + ExecutionStep::Sequential(vec![ + ExecutionStep::Resolve(Id(1)), + ExecutionStep::Parallel(vec![]), + ExecutionStep::Resolve(Id(2)), + ]), + ExecutionStep::Resolve(Id(3)), + ExecutionStep::Parallel(vec![ + ExecutionStep::Resolve(Id(4)), + ExecutionStep::Resolve(Id(5)), + ]), + ExecutionStep::Sequential(vec![ExecutionStep::Sequential(vec![ + ExecutionStep::Resolve(Id(6)), + ExecutionStep::Resolve(Id(7)) + ])]) + ]) + .flatten(), + ExecutionStep::Sequential(vec![ + ExecutionStep::Resolve(Id(0)), + ExecutionStep::Resolve(Id(1)), + ExecutionStep::Resolve(Id(2)), + ExecutionStep::Resolve(Id(3)), + ExecutionStep::Parallel(vec![ + ExecutionStep::Resolve(Id(4)), + ExecutionStep::Resolve(Id(5)), + ]), + ExecutionStep::Resolve(Id(6)), + ExecutionStep::Resolve(Id(7)) + ]) + ); + } + + #[test] + fn parallel() { + assert_eq!( + ExecutionStep::Parallel(vec![ + ExecutionStep::Parallel(vec![ + ExecutionStep::Resolve(Id(0)), + ExecutionStep::Resolve(Id(1)), + ExecutionStep::Resolve(Id(2)), + ]), + ExecutionStep::Resolve(Id(3)), + ExecutionStep::Sequential(vec![ + ExecutionStep::Resolve(Id(4)), + ExecutionStep::Parallel(vec![ + ExecutionStep::Resolve(Id(5)), + ExecutionStep::Resolve(Id(6)), + ]), + ExecutionStep::Resolve(Id(7)) + ]) + ]) + .flatten(), + ExecutionStep::Parallel(vec![ + ExecutionStep::Resolve(Id(0)), + ExecutionStep::Resolve(Id(1)), + ExecutionStep::Resolve(Id(2)), + ExecutionStep::Resolve(Id(3)), + ExecutionStep::Sequential(vec![ + ExecutionStep::Resolve(Id(4)), + ExecutionStep::Parallel(vec![ + ExecutionStep::Resolve(Id(5)), + ExecutionStep::Resolve(Id(6)), + ]), + ExecutionStep::Resolve(Id(7)) + ]) + ]) + ) } } } diff --git a/src/query_plan/execution/executor.rs b/src/query_plan/execution/executor.rs index 9613955aa9..59b67057cc 100644 --- a/src/query_plan/execution/executor.rs +++ b/src/query_plan/execution/executor.rs @@ -89,6 +89,9 @@ impl<'a> ExecutorContext<'a> { self.resolved.insert(*id, result); } + ExecutionStep::ForEach(id) => { + todo!() + } ExecutionStep::Sequential(steps) => { for step in steps { self.execute(step).await; diff --git a/src/query_plan/execution/simple.rs b/src/query_plan/execution/simple.rs index b5c3caaed2..fd14358886 100644 --- a/src/query_plan/execution/simple.rs +++ b/src/query_plan/execution/simple.rs @@ -1,39 +1,32 @@ -use crate::query_plan::plan::{FieldTreeEntry, OperationPlan}; +use crate::query_plan::plan::{FieldTree, FieldTreeEntry, OperationPlan}; use super::execution::ExecutionStep; pub struct SimpleExecutionBuilder {} impl SimpleExecutionBuilder { - pub fn build(&self, operation_plan: &OperationPlan) -> ExecutionStep { + fn inner_build(&self, tree: &FieldTree) -> ExecutionStep { let mut steps = Vec::new(); - let mut queue = vec![&operation_plan.field_tree]; - - while !queue.is_empty() { - let mut new_queue = Vec::new(); - let mut parallel_steps = Vec::new(); - for tree in queue { - if let Some(field_plan_id) = &tree.field_plan_id { - parallel_steps.push(ExecutionStep::Resolve(*field_plan_id)); - } - - match &tree.entry { - FieldTreeEntry::Compound(children) | FieldTreeEntry::CompoundList(children) => { - for tree in children.values() { - new_queue.push(tree); - } - } - _ => {} + match &tree.entry { + FieldTreeEntry::Compound(children) | FieldTreeEntry::CompoundList(children) => { + for tree in children.values() { + steps.push(self.inner_build(tree)); } } + _ => {} + } - if !parallel_steps.is_empty() { - steps.push(ExecutionStep::parallel(parallel_steps)); - } - queue = new_queue; + let steps = ExecutionStep::Parallel(steps); + + if let Some(field_plan_id) = &tree.field_plan_id { + ExecutionStep::Sequential(vec![ExecutionStep::Resolve(*field_plan_id), steps]) + } else { + steps } + } - ExecutionStep::sequential(steps) + pub fn build(&self, operation_plan: &OperationPlan) -> ExecutionStep { + self.inner_build(&operation_plan.field_tree).flatten() } } diff --git a/src/query_plan/resolver.rs b/src/query_plan/resolver.rs index 0af54b0527..cf2be98a68 100644 --- a/src/query_plan/resolver.rs +++ b/src/query_plan/resolver.rs @@ -13,7 +13,7 @@ use async_graphql::{ use indenter::indented; #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct Id(usize); +pub struct Id(pub usize); impl Display for Id { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { diff --git a/src/query_plan/snapshots/tailcall__query_plan__tests__PostAndUser_execution_plan.snap b/src/query_plan/snapshots/tailcall__query_plan__tests__PostAndUser_execution_plan.snap index 1e87ede064..722f5c3b34 100644 --- a/src/query_plan/snapshots/tailcall__query_plan__tests__PostAndUser_execution_plan.snap +++ b/src/query_plan/snapshots/tailcall__query_plan__tests__PostAndUser_execution_plan.snap @@ -2,8 +2,8 @@ source: src/query_plan/tests.rs expression: execution_plan --- -Sequential: - Parallel: +Parallel: + Sequential: Resolve(3) - Resolve(6) - Resolve(4) + Resolve(4) + Resolve(6) diff --git a/src/query_plan/tests/user-posts.graphql b/src/query_plan/tests/user-posts.graphql index d44c0bd237..ec6839ecc3 100644 --- a/src/query_plan/tests/user-posts.graphql +++ b/src/query_plan/tests/user-posts.graphql @@ -17,7 +17,7 @@ type User { username: String! email: String! phone: String - website: String @const(data: "/users/website/{{value.username}}") + website: String @expr(body: "/users/website/{{value.username}}") } type Post {