From c9d8754c1487736f29a0ef1520da603c2a129080 Mon Sep 17 00:00:00 2001 From: laststylebender Date: Tue, 26 Nov 2024 17:13:23 +0530 Subject: [PATCH 1/9] - wip --- src/core/ir/model.rs | 1 + src/core/jit/builder.rs | 29 +- src/core/jit/model.rs | 6 + src/core/jit/request.rs | 1 + ...ilcall__core__jit__model__test__defer.snap | 911 ++++++++++++++++++ src/core/jit/transform/mod.rs | 2 + src/core/jit/transform/wrap_defer.rs | 47 + 7 files changed, 996 insertions(+), 1 deletion(-) create mode 100644 src/core/jit/snapshots/tailcall__core__jit__model__test__defer.snap create mode 100644 src/core/jit/transform/wrap_defer.rs diff --git a/src/core/ir/model.rs b/src/core/ir/model.rs index 234e38beef..a65339a466 100644 --- a/src/core/ir/model.rs +++ b/src/core/ir/model.rs @@ -30,6 +30,7 @@ pub enum IR { Entity(HashMap), /// Apollo Federation _service resolver Service(String), + Deferred(Box), } #[derive(Clone, Debug)] diff --git a/src/core/jit/builder.rs b/src/core/jit/builder.rs index 26ab6d1f66..6f63082f0a 100644 --- a/src/core/jit/builder.rs +++ b/src/core/jit/builder.rs @@ -278,7 +278,34 @@ impl Builder { .map(|cond| cond.node.on.node.as_str()) .unwrap_or(type_condition); - fields.extend(self.iter(&fragment.selection_set.node, type_of, fragments)); + // what if we've directive defined in the fragment? + let mut directives = Vec::with_capacity(fragment.directives.len()); + for directive in fragment.directives.iter() { + let directive = &directive.node; + if directive.name.node == "skip" || directive.name.node == "include" { + continue; + } + let arguments = directive + .arguments + .iter() + .map(|(k, v)| (k.node.to_string(), v.node.clone())) + .collect::>(); + + directives + .push(JitDirective { name: directive.name.to_string(), arguments }); + } + + if directives.iter().find(|d| d.name == "defer").is_some() { + let mut child_fields = + self.iter(&fragment.selection_set.node, type_of, fragments); + + child_fields.iter_mut().for_each(|f| { + f.directives.extend(directives.clone()); + }); + fields.extend(child_fields); + } else { + fields.extend(self.iter(&fragment.selection_set.node, type_of, fragments)); + } } } } diff --git a/src/core/jit/model.rs b/src/core/jit/model.rs index 1b833b0ed0..135effe755 100644 --- a/src/core/jit/model.rs +++ b/src/core/jit/model.rs @@ -601,4 +601,10 @@ mod test { assert!(actual.is_dedupe); } + + #[test] + fn test_defer(){ + let proposed_plan = plan(r#"{ users { id ... @defer(label: "comment-defer") { comments { body } } } }"#); + insta::assert_debug_snapshot!(proposed_plan); + } } diff --git a/src/core/jit/request.rs b/src/core/jit/request.rs index dc08ee53d7..20f2c9f820 100644 --- a/src/core/jit/request.rs +++ b/src/core/jit/request.rs @@ -50,6 +50,7 @@ impl Request { .pipe(transform::AuthPlanner::new()) .pipe(transform::CheckDedupe::new()) .pipe(transform::CheckCache::new()) + .pipe(transform::WrapDefer::new()) .transform(plan) .to_result() // both transformers are infallible right now diff --git a/src/core/jit/snapshots/tailcall__core__jit__model__test__defer.snap b/src/core/jit/snapshots/tailcall__core__jit__model__test__defer.snap new file mode 100644 index 0000000000..706a69d001 --- /dev/null +++ b/src/core/jit/snapshots/tailcall__core__jit__model__test__defer.snap @@ -0,0 +1,911 @@ +--- +source: src/core/jit/model.rs +expression: proposed_plan +--- +OperationPlan { + root_name: "Query", + operation_type: Query, + index: Index { + map: { + "Comment": ( + Object( + ObjectTypeDefinition { + name: "Comment", + fields: [ + FieldDefinition { + name: "body", + args: [], + of_type: String!, + resolver: None, + directives: [], + description: None, + default_value: None, + }, + FieldDefinition { + name: "email", + args: [], + of_type: String!, + resolver: None, + directives: [], + description: None, + default_value: None, + }, + FieldDefinition { + name: "id", + args: [], + of_type: ID!, + resolver: None, + directives: [], + description: None, + default_value: None, + }, + FieldDefinition { + name: "name", + args: [], + of_type: String!, + resolver: None, + directives: [], + description: None, + default_value: None, + }, + FieldDefinition { + name: "postId", + args: [], + of_type: ID!, + resolver: None, + directives: [], + description: None, + default_value: None, + }, + ], + description: None, + implements: {}, + directives: [], + }, + ), + { + "body": Field( + ( + FieldDefinition { + name: "body", + args: [], + of_type: String!, + resolver: None, + directives: [], + description: None, + default_value: None, + }, + {}, + ), + ), + "email": Field( + ( + FieldDefinition { + name: "email", + args: [], + of_type: String!, + resolver: None, + directives: [], + description: None, + default_value: None, + }, + {}, + ), + ), + "id": Field( + ( + FieldDefinition { + name: "id", + args: [], + of_type: ID!, + resolver: None, + directives: [], + description: None, + default_value: None, + }, + {}, + ), + ), + "name": Field( + ( + FieldDefinition { + name: "name", + args: [], + of_type: String!, + resolver: None, + directives: [], + description: None, + default_value: None, + }, + {}, + ), + ), + "postId": Field( + ( + FieldDefinition { + name: "postId", + args: [], + of_type: ID!, + resolver: None, + directives: [], + description: None, + default_value: None, + }, + {}, + ), + ), + }, + ), + "Post": ( + Object( + ObjectTypeDefinition { + name: "Post", + fields: [ + FieldDefinition { + name: "body", + args: [], + of_type: String!, + resolver: None, + directives: [], + description: None, + default_value: None, + }, + FieldDefinition { + name: "id", + args: [], + of_type: ID!, + resolver: None, + directives: [], + description: None, + default_value: None, + }, + FieldDefinition { + name: "title", + args: [], + of_type: String!, + resolver: None, + directives: [], + description: None, + default_value: None, + }, + FieldDefinition { + name: "user", + args: [], + of_type: User, + resolver: Some( + IO( + Http { + req_template: RequestTemplate { + root_url: Mustache( + [ + Literal( + "http://jsonplaceholder.typicode.com/users/", + ), + Expression( + [ + "value", + "userId", + ], + ), + ], + ), + query: [], + method: GET, + headers: [], + body_path: None, + endpoint: Endpoint { + path: "http://jsonplaceholder.typicode.com/users/{{.value.userId}}", + query: [], + method: GET, + input: Obj( + {}, + ), + output: Obj( + {}, + ), + headers: {}, + body: None, + description: None, + encoding: ApplicationJson, + }, + encoding: ApplicationJson, + query_encoder: RepeatedKey, + }, + group_by: None, + dl_id: None, + http_filter: None, + is_list: false, + dedupe: true, + }, + ), + ), + directives: [], + description: None, + default_value: None, + }, + FieldDefinition { + name: "userId", + args: [], + of_type: ID!, + resolver: None, + directives: [], + description: None, + default_value: None, + }, + ], + description: None, + implements: {}, + directives: [], + }, + ), + { + "body": Field( + ( + FieldDefinition { + name: "body", + args: [], + of_type: String!, + resolver: None, + directives: [], + description: None, + default_value: None, + }, + {}, + ), + ), + "id": Field( + ( + FieldDefinition { + name: "id", + args: [], + of_type: ID!, + resolver: None, + directives: [], + description: None, + default_value: None, + }, + {}, + ), + ), + "title": Field( + ( + FieldDefinition { + name: "title", + args: [], + of_type: String!, + resolver: None, + directives: [], + description: None, + default_value: None, + }, + {}, + ), + ), + "user": Field( + ( + FieldDefinition { + name: "user", + args: [], + of_type: User, + resolver: Some( + IO( + Http { + req_template: RequestTemplate { + root_url: Mustache( + [ + Literal( + "http://jsonplaceholder.typicode.com/users/", + ), + Expression( + [ + "value", + "userId", + ], + ), + ], + ), + query: [], + method: GET, + headers: [], + body_path: None, + endpoint: Endpoint { + path: "http://jsonplaceholder.typicode.com/users/{{.value.userId}}", + query: [], + method: GET, + input: Obj( + {}, + ), + output: Obj( + {}, + ), + headers: {}, + body: None, + description: None, + encoding: ApplicationJson, + }, + encoding: ApplicationJson, + query_encoder: RepeatedKey, + }, + group_by: None, + dl_id: None, + http_filter: None, + is_list: false, + dedupe: true, + }, + ), + ), + directives: [], + description: None, + default_value: None, + }, + {}, + ), + ), + "userId": Field( + ( + FieldDefinition { + name: "userId", + args: [], + of_type: ID!, + resolver: None, + directives: [], + description: None, + default_value: None, + }, + {}, + ), + ), + }, + ), + "Query": ( + Object( + ObjectTypeDefinition { + name: "Query", + fields: [ + FieldDefinition { + name: "posts", + args: [], + of_type: [Post], + resolver: Some( + IO( + Http { + req_template: RequestTemplate { + root_url: Mustache( + [ + Literal( + "http://jsonplaceholder.typicode.com/posts", + ), + ], + ), + query: [], + method: GET, + headers: [], + body_path: None, + endpoint: Endpoint { + path: "http://jsonplaceholder.typicode.com/posts", + query: [], + method: GET, + input: Obj( + {}, + ), + output: Obj( + {}, + ), + headers: {}, + body: None, + description: None, + encoding: ApplicationJson, + }, + encoding: ApplicationJson, + query_encoder: RepeatedKey, + }, + group_by: None, + dl_id: None, + http_filter: None, + is_list: true, + dedupe: false, + }, + ), + ), + directives: [], + description: None, + default_value: None, + }, + FieldDefinition { + name: "users", + args: [], + of_type: [User], + resolver: Some( + IO( + Http { + req_template: RequestTemplate { + root_url: Mustache( + [ + Literal( + "http://jsonplaceholder.typicode.com/users", + ), + ], + ), + query: [], + method: GET, + headers: [], + body_path: None, + endpoint: Endpoint { + path: "http://jsonplaceholder.typicode.com/users", + query: [], + method: GET, + input: Obj( + {}, + ), + output: Obj( + {}, + ), + headers: {}, + body: None, + description: None, + encoding: ApplicationJson, + }, + encoding: ApplicationJson, + query_encoder: RepeatedKey, + }, + group_by: None, + dl_id: None, + http_filter: None, + is_list: true, + dedupe: true, + }, + ), + ), + directives: [], + description: None, + default_value: None, + }, + ], + description: None, + implements: {}, + directives: [], + }, + ), + { + "posts": Field( + ( + FieldDefinition { + name: "posts", + args: [], + of_type: [Post], + resolver: Some( + IO( + Http { + req_template: RequestTemplate { + root_url: Mustache( + [ + Literal( + "http://jsonplaceholder.typicode.com/posts", + ), + ], + ), + query: [], + method: GET, + headers: [], + body_path: None, + endpoint: Endpoint { + path: "http://jsonplaceholder.typicode.com/posts", + query: [], + method: GET, + input: Obj( + {}, + ), + output: Obj( + {}, + ), + headers: {}, + body: None, + description: None, + encoding: ApplicationJson, + }, + encoding: ApplicationJson, + query_encoder: RepeatedKey, + }, + group_by: None, + dl_id: None, + http_filter: None, + is_list: true, + dedupe: false, + }, + ), + ), + directives: [], + description: None, + default_value: None, + }, + {}, + ), + ), + "users": Field( + ( + FieldDefinition { + name: "users", + args: [], + of_type: [User], + resolver: Some( + IO( + Http { + req_template: RequestTemplate { + root_url: Mustache( + [ + Literal( + "http://jsonplaceholder.typicode.com/users", + ), + ], + ), + query: [], + method: GET, + headers: [], + body_path: None, + endpoint: Endpoint { + path: "http://jsonplaceholder.typicode.com/users", + query: [], + method: GET, + input: Obj( + {}, + ), + output: Obj( + {}, + ), + headers: {}, + body: None, + description: None, + encoding: ApplicationJson, + }, + encoding: ApplicationJson, + query_encoder: RepeatedKey, + }, + group_by: None, + dl_id: None, + http_filter: None, + is_list: true, + dedupe: true, + }, + ), + ), + directives: [], + description: None, + default_value: None, + }, + {}, + ), + ), + }, + ), + "User": ( + Object( + ObjectTypeDefinition { + name: "User", + fields: [ + FieldDefinition { + name: "comments", + args: [], + of_type: [Comment], + resolver: Some( + IO( + Http { + req_template: RequestTemplate { + root_url: Mustache( + [ + Literal( + "http://jsonplaceholder.typicode.com/users/", + ), + Expression( + [ + "value", + "id", + ], + ), + Literal( + "/comments", + ), + ], + ), + query: [], + method: GET, + headers: [], + body_path: None, + endpoint: Endpoint { + path: "http://jsonplaceholder.typicode.com/users/{{.value.id}}/comments", + query: [], + method: GET, + input: Obj( + {}, + ), + output: Obj( + {}, + ), + headers: {}, + body: None, + description: None, + encoding: ApplicationJson, + }, + encoding: ApplicationJson, + query_encoder: RepeatedKey, + }, + group_by: None, + dl_id: None, + http_filter: None, + is_list: true, + dedupe: true, + }, + ), + ), + directives: [], + description: None, + default_value: None, + }, + FieldDefinition { + name: "email", + args: [], + of_type: String!, + resolver: None, + directives: [], + description: None, + default_value: None, + }, + FieldDefinition { + name: "id", + args: [], + of_type: ID!, + resolver: None, + directives: [], + description: None, + default_value: None, + }, + FieldDefinition { + name: "name", + args: [], + of_type: String!, + resolver: None, + directives: [], + description: None, + default_value: None, + }, + FieldDefinition { + name: "phone", + args: [], + of_type: String, + resolver: None, + directives: [], + description: None, + default_value: None, + }, + FieldDefinition { + name: "username", + args: [], + of_type: String!, + resolver: None, + directives: [], + description: None, + default_value: None, + }, + ], + description: None, + implements: {}, + directives: [], + }, + ), + { + "comments": Field( + ( + FieldDefinition { + name: "comments", + args: [], + of_type: [Comment], + resolver: Some( + IO( + Http { + req_template: RequestTemplate { + root_url: Mustache( + [ + Literal( + "http://jsonplaceholder.typicode.com/users/", + ), + Expression( + [ + "value", + "id", + ], + ), + Literal( + "/comments", + ), + ], + ), + query: [], + method: GET, + headers: [], + body_path: None, + endpoint: Endpoint { + path: "http://jsonplaceholder.typicode.com/users/{{.value.id}}/comments", + query: [], + method: GET, + input: Obj( + {}, + ), + output: Obj( + {}, + ), + headers: {}, + body: None, + description: None, + encoding: ApplicationJson, + }, + encoding: ApplicationJson, + query_encoder: RepeatedKey, + }, + group_by: None, + dl_id: None, + http_filter: None, + is_list: true, + dedupe: true, + }, + ), + ), + directives: [], + description: None, + default_value: None, + }, + {}, + ), + ), + "email": Field( + ( + FieldDefinition { + name: "email", + args: [], + of_type: String!, + resolver: None, + directives: [], + description: None, + default_value: None, + }, + {}, + ), + ), + "id": Field( + ( + FieldDefinition { + name: "id", + args: [], + of_type: ID!, + resolver: None, + directives: [], + description: None, + default_value: None, + }, + {}, + ), + ), + "name": Field( + ( + FieldDefinition { + name: "name", + args: [], + of_type: String!, + resolver: None, + directives: [], + description: None, + default_value: None, + }, + {}, + ), + ), + "phone": Field( + ( + FieldDefinition { + name: "phone", + args: [], + of_type: String, + resolver: None, + directives: [], + description: None, + default_value: None, + }, + {}, + ), + ), + "username": Field( + ( + FieldDefinition { + name: "username", + args: [], + of_type: String!, + resolver: None, + directives: [], + description: None, + default_value: None, + }, + {}, + ), + ), + }, + ), + }, + schema: SchemaDefinition { + query: "Query", + mutation: None, + directives: [ + Directive { + name: "server", + arguments: { + "hostname": String("0.0.0.0"), + "port": Number(8000), + }, + }, + ], + }, + }, + is_introspection_query: false, + is_dedupe: true, + is_const: false, + is_protected: false, + min_cache_ttl: None, + selection: [ + Field { + id: 0, + name: "users", + output_name: "users", + ir: "Some(..)", + type_of: [User], + type_condition: Some( + "Query", + ), + selection: [ + Field { + id: 1, + name: "id", + output_name: "id", + type_of: ID!, + type_condition: Some( + "User", + ), + directives: [], + }, + Field { + id: 2, + name: "comments", + output_name: "comments", + ir: "Some(..)", + type_of: [Comment], + type_condition: Some( + "User", + ), + selection: [ + Field { + id: 3, + name: "body", + output_name: "body", + type_of: String!, + type_condition: Some( + "Comment", + ), + directives: [], + }, + ], + directives: [ + Directive { + name: "defer", + arguments: [ + ( + "label", + String( + "comment-defer", + ), + ), + ], + }, + ], + }, + ], + directives: [], + }, + ], + before: None, +} diff --git a/src/core/jit/transform/mod.rs b/src/core/jit/transform/mod.rs index 71928ddbfd..b626c5abda 100644 --- a/src/core/jit/transform/mod.rs +++ b/src/core/jit/transform/mod.rs @@ -4,6 +4,7 @@ mod check_const; mod check_dedupe; mod check_protected; mod input_resolver; +mod wrap_defer; mod skip; pub use auth_planner::*; @@ -12,4 +13,5 @@ pub use check_const::*; pub use check_dedupe::*; pub use check_protected::*; pub use input_resolver::*; +pub use wrap_defer::*; pub use skip::*; diff --git a/src/core/jit/transform/wrap_defer.rs b/src/core/jit/transform/wrap_defer.rs new file mode 100644 index 0000000000..4f24ea01fc --- /dev/null +++ b/src/core/jit/transform/wrap_defer.rs @@ -0,0 +1,47 @@ +use std::{convert::Infallible, marker::PhantomData}; + +use tailcall_valid::Valid; + +use crate::core::{ + ir::model::IR, + jit::{Field, OperationPlan}, + Transform, +}; + +pub struct WrapDefer(PhantomData); + +impl WrapDefer { + pub fn new() -> Self { + Self(PhantomData) + } +} + +/// goes through selection and finds out IR's that needs to be deferred. +#[inline] +fn detect_and_wrap(field: &mut Field) { + for selection in field.selection.iter_mut() { + if let Some(ir) = std::mem::take(&mut selection.ir) { + let ir = if selection + .directives + .iter() + .find(|d| d.name == "defer") + .is_some() + && field.ir.is_some() + { + IR::Deferred(Box::new(ir)) + } else { + ir + }; + selection.ir = Some(ir); + } + } +} + +impl Transform for WrapDefer { + type Value = OperationPlan; + type Error = Infallible; + fn transform(&self, mut plan: Self::Value) -> Valid { + plan.selection.iter_mut().for_each(detect_and_wrap); + Valid::succeed(plan) + } +} From 7b58f3b25f7e623c17504f56fcfa6c6d0d009dab Mon Sep 17 00:00:00 2001 From: laststylebender Date: Tue, 26 Nov 2024 17:17:52 +0530 Subject: [PATCH 2/9] - fix ocurrences of deferred --- src/core/ir/eval.rs | 1 + src/core/ir/model.rs | 1 + src/core/jit/transform/auth_planner.rs | 3 +++ src/core/jit/transform/check_cache.rs | 1 + src/core/jit/transform/check_const.rs | 1 + src/core/jit/transform/check_dedupe.rs | 1 + src/core/jit/transform/check_protected.rs | 1 + 7 files changed, 9 insertions(+) diff --git a/src/core/ir/eval.rs b/src/core/ir/eval.rs index 840893c84f..dafc21e938 100644 --- a/src/core/ir/eval.rs +++ b/src/core/ir/eval.rs @@ -146,6 +146,7 @@ impl IR { Ok(ConstValue::object(obj)) } + IR::Deferred(ir) => ir.eval(ctx).await, } }) } diff --git a/src/core/ir/model.rs b/src/core/ir/model.rs index a65339a466..791dea2838 100644 --- a/src/core/ir/model.rs +++ b/src/core/ir/model.rs @@ -175,6 +175,7 @@ impl IR { .collect(), ), IR::Service(sdl) => IR::Service(sdl), + IR::Deferred(ir) => IR::Deferred(ir.modify_box(modifier)), } } } diff --git a/src/core/jit/transform/auth_planner.rs b/src/core/jit/transform/auth_planner.rs index e372c4daf2..48f6d7d1e6 100644 --- a/src/core/jit/transform/auth_planner.rs +++ b/src/core/jit/transform/auth_planner.rs @@ -78,5 +78,8 @@ pub fn update_ir(ir: &mut IR, vec: &mut Vec) { IR::Discriminate(_, ir) => { update_ir(ir, vec); } + IR::Deferred(ir) => { + update_ir(ir, vec); + } } } diff --git a/src/core/jit/transform/check_cache.rs b/src/core/jit/transform/check_cache.rs index 83a7dc202c..c193acfcaf 100644 --- a/src/core/jit/transform/check_cache.rs +++ b/src/core/jit/transform/check_cache.rs @@ -35,6 +35,7 @@ fn check_cache(ir: &IR) -> Option { } ttl } + IR::Deferred(ir) => check_cache(ir), IR::Dynamic(_) | IR::ContextPath(_) | IR::Map(_) | IR::Service(_) => None, } } diff --git a/src/core/jit/transform/check_const.rs b/src/core/jit/transform/check_const.rs index 6ecdd4e599..c8825f71fc 100644 --- a/src/core/jit/transform/check_const.rs +++ b/src/core/jit/transform/check_const.rs @@ -28,6 +28,7 @@ pub fn is_const(ir: &IR) -> bool { IR::Discriminate(_, ir) => is_const(ir), IR::Entity(hash_map) => hash_map.values().all(is_const), IR::Service(_) => true, + IR::Deferred(ir) => is_const(ir), } } diff --git a/src/core/jit/transform/check_dedupe.rs b/src/core/jit/transform/check_dedupe.rs index 296038a6f3..f6018e6551 100644 --- a/src/core/jit/transform/check_dedupe.rs +++ b/src/core/jit/transform/check_dedupe.rs @@ -27,6 +27,7 @@ fn check_dedupe(ir: &IR) -> bool { IR::ContextPath(_) => true, IR::Map(_) => true, IR::Service(_) => true, + IR::Deferred(ir) => check_dedupe(ir) } } diff --git a/src/core/jit/transform/check_protected.rs b/src/core/jit/transform/check_protected.rs index 2b16557e28..93c1d0d8ef 100644 --- a/src/core/jit/transform/check_protected.rs +++ b/src/core/jit/transform/check_protected.rs @@ -28,6 +28,7 @@ pub fn is_protected(ir: &IR) -> bool { IR::Discriminate(_, ir) => is_protected(ir), IR::Entity(hash_map) => hash_map.values().any(is_protected), IR::Service(_) => false, + IR::Deferred(ir) => is_protected(ir), } } From cf2d3785646b8e565e2cb344a21f95131ea47b9b Mon Sep 17 00:00:00 2001 From: laststylebender Date: Tue, 26 Nov 2024 17:43:44 +0530 Subject: [PATCH 3/9] - move independent deferred fields to separate variable --- src/core/jit/model.rs | 11 ++++- src/core/jit/request.rs | 1 + src/core/jit/transform/defer_planner.rs | 52 ++++++++++++++++++++++++ src/core/jit/transform/input_resolver.rs | 9 ++++ src/core/jit/transform/mod.rs | 2 + 5 files changed, 73 insertions(+), 2 deletions(-) create mode 100644 src/core/jit/transform/defer_planner.rs diff --git a/src/core/jit/model.rs b/src/core/jit/model.rs index 135effe755..c9767257b0 100644 --- a/src/core/jit/model.rs +++ b/src/core/jit/model.rs @@ -291,6 +291,7 @@ pub struct OperationPlan { pub min_cache_ttl: Option, pub selection: Vec>, pub before: Option, + pub deferred_fields: Vec>, } impl OperationPlan { @@ -298,12 +299,16 @@ impl OperationPlan { self, map: impl Fn(Input) -> Result, ) -> Result, Error> { - let mut selection = vec![]; - + let mut selection = Vec::with_capacity(self.selection.len()); for n in self.selection { selection.push(n.try_map(&map)?); } + let mut deferred_selection = Vec::with_capacity(self.deferred_fields.len()); + for n in self.deferred_fields { + deferred_selection.push(n.try_map(&map)?); + } + Ok(OperationPlan { selection, root_name: self.root_name, @@ -315,6 +320,7 @@ impl OperationPlan { is_protected: self.is_protected, min_cache_ttl: self.min_cache_ttl, before: self.before, + deferred_fields: deferred_selection, }) } } @@ -342,6 +348,7 @@ impl OperationPlan { is_protected: false, min_cache_ttl: None, before: Default::default(), + deferred_fields: Default::default(), } } diff --git a/src/core/jit/request.rs b/src/core/jit/request.rs index 20f2c9f820..d2cbb93f0a 100644 --- a/src/core/jit/request.rs +++ b/src/core/jit/request.rs @@ -51,6 +51,7 @@ impl Request { .pipe(transform::CheckDedupe::new()) .pipe(transform::CheckCache::new()) .pipe(transform::WrapDefer::new()) + .pipe(transform::DeferPlanner::new()) .transform(plan) .to_result() // both transformers are infallible right now diff --git a/src/core/jit/transform/defer_planner.rs b/src/core/jit/transform/defer_planner.rs new file mode 100644 index 0000000000..3d37442f5a --- /dev/null +++ b/src/core/jit/transform/defer_planner.rs @@ -0,0 +1,52 @@ +use std::{convert::Infallible, marker::PhantomData}; + +use tailcall_valid::Valid; + +use crate::core::{ + ir::model::IR, + jit::{Field, OperationPlan}, + Transform, +}; + +pub struct DeferPlanner(PhantomData); + +impl DeferPlanner { + pub fn new() -> Self { + Self(PhantomData) + } +} + +// collect the fields that has IR type of Deferred and return back. +fn move_deferred_fields(field: &mut Field) -> Vec> { + let mut deferred_fields = Vec::new(); + for selection in field.selection.iter_mut() { + match selection.ir { + Some(IR::Deferred(_)) => { + deferred_fields.push(selection.clone()); + } + _ => {} + } + deferred_fields.extend(move_deferred_fields(selection)); + } + + field.selection.retain(|f| { + f.ir.as_ref() + .map_or(true, |ir| !matches!(ir, IR::Deferred(_))) + }); + + deferred_fields +} + +impl Transform for DeferPlanner { + type Value = OperationPlan; + type Error = Infallible; + + fn transform(&self, mut plan: Self::Value) -> Valid { + let mut deferred_fields = Vec::new(); + for field in plan.selection.iter_mut() { + deferred_fields.extend(move_deferred_fields(field)); + } + plan.deferred_fields = deferred_fields; + Valid::succeed(plan) + } +} diff --git a/src/core/jit/transform/input_resolver.rs b/src/core/jit/transform/input_resolver.rs index 920bcfdae1..4354a9f77c 100644 --- a/src/core/jit/transform/input_resolver.rs +++ b/src/core/jit/transform/input_resolver.rs @@ -68,6 +68,14 @@ where .map(|field| Self::resolve_field(&index, field?)) .collect::, _>>()?; + let deferred_fields = self + .plan + .deferred_fields + .into_iter() + .map(|field| field.try_map(&|value| value.resolve(variables))) + .map(|field| Self::resolve_field(&index, field?)) + .collect::, _>>()?; + Ok(OperationPlan { root_name: self.plan.root_name.to_string(), operation_type: self.plan.operation_type, @@ -79,6 +87,7 @@ where min_cache_ttl: self.plan.min_cache_ttl, selection, before: self.plan.before, + deferred_fields: deferred_fields, }) } diff --git a/src/core/jit/transform/mod.rs b/src/core/jit/transform/mod.rs index b626c5abda..ae0531858a 100644 --- a/src/core/jit/transform/mod.rs +++ b/src/core/jit/transform/mod.rs @@ -6,6 +6,7 @@ mod check_protected; mod input_resolver; mod wrap_defer; mod skip; +mod defer_planner; pub use auth_planner::*; pub use check_cache::*; @@ -14,4 +15,5 @@ pub use check_dedupe::*; pub use check_protected::*; pub use input_resolver::*; pub use wrap_defer::*; +pub use defer_planner::*; pub use skip::*; From 3e2874995febb3b75663970212f43b10e5f3ec2a Mon Sep 17 00:00:00 2001 From: laststylebender Date: Tue, 26 Nov 2024 17:51:44 +0530 Subject: [PATCH 4/9] - add path to deferred IR. --- src/core/ir/eval.rs | 2 +- src/core/ir/model.rs | 9 ++- ...ilcall__core__jit__model__test__defer.snap | 64 ++++++++++--------- src/core/jit/transform/auth_planner.rs | 2 +- src/core/jit/transform/check_cache.rs | 2 +- src/core/jit/transform/check_const.rs | 2 +- src/core/jit/transform/check_dedupe.rs | 2 +- src/core/jit/transform/check_protected.rs | 2 +- src/core/jit/transform/defer_planner.rs | 4 +- src/core/jit/transform/wrap_defer.rs | 13 +++- 10 files changed, 58 insertions(+), 44 deletions(-) diff --git a/src/core/ir/eval.rs b/src/core/ir/eval.rs index dafc21e938..886b212002 100644 --- a/src/core/ir/eval.rs +++ b/src/core/ir/eval.rs @@ -146,7 +146,7 @@ impl IR { Ok(ConstValue::object(obj)) } - IR::Deferred(ir) => ir.eval(ctx).await, + IR::Deferred { ir, .. } => ir.eval(ctx).await, } }) } diff --git a/src/core/ir/model.rs b/src/core/ir/model.rs index 791dea2838..3658d1cbe1 100644 --- a/src/core/ir/model.rs +++ b/src/core/ir/model.rs @@ -30,7 +30,10 @@ pub enum IR { Entity(HashMap), /// Apollo Federation _service resolver Service(String), - Deferred(Box), + Deferred { + ir: Box, + path: Vec, + }, } #[derive(Clone, Debug)] @@ -175,7 +178,9 @@ impl IR { .collect(), ), IR::Service(sdl) => IR::Service(sdl), - IR::Deferred(ir) => IR::Deferred(ir.modify_box(modifier)), + IR::Deferred { ir, path } => { + IR::Deferred { ir: Box::new(ir.modify(modifier)), path } + } } } } diff --git a/src/core/jit/snapshots/tailcall__core__jit__model__test__defer.snap b/src/core/jit/snapshots/tailcall__core__jit__model__test__defer.snap index 706a69d001..b8d8e61966 100644 --- a/src/core/jit/snapshots/tailcall__core__jit__model__test__defer.snap +++ b/src/core/jit/snapshots/tailcall__core__jit__model__test__defer.snap @@ -868,44 +868,46 @@ OperationPlan { ), directives: [], }, + ], + directives: [], + }, + ], + before: None, + deferred_fields: [ + Field { + id: 2, + name: "comments", + output_name: "comments", + ir: "Some(..)", + type_of: [Comment], + type_condition: Some( + "User", + ), + selection: [ Field { - id: 2, - name: "comments", - output_name: "comments", - ir: "Some(..)", - type_of: [Comment], + id: 3, + name: "body", + output_name: "body", + type_of: String!, type_condition: Some( - "User", + "Comment", ), - selection: [ - Field { - id: 3, - name: "body", - output_name: "body", - type_of: String!, - type_condition: Some( - "Comment", + directives: [], + }, + ], + directives: [ + Directive { + name: "defer", + arguments: [ + ( + "label", + String( + "comment-defer", ), - directives: [], - }, - ], - directives: [ - Directive { - name: "defer", - arguments: [ - ( - "label", - String( - "comment-defer", - ), - ), - ], - }, + ), ], }, ], - directives: [], }, ], - before: None, } diff --git a/src/core/jit/transform/auth_planner.rs b/src/core/jit/transform/auth_planner.rs index 48f6d7d1e6..47083cfbe4 100644 --- a/src/core/jit/transform/auth_planner.rs +++ b/src/core/jit/transform/auth_planner.rs @@ -78,7 +78,7 @@ pub fn update_ir(ir: &mut IR, vec: &mut Vec) { IR::Discriminate(_, ir) => { update_ir(ir, vec); } - IR::Deferred(ir) => { + IR::Deferred { ir, .. } => { update_ir(ir, vec); } } diff --git a/src/core/jit/transform/check_cache.rs b/src/core/jit/transform/check_cache.rs index c193acfcaf..8e49547221 100644 --- a/src/core/jit/transform/check_cache.rs +++ b/src/core/jit/transform/check_cache.rs @@ -35,7 +35,7 @@ fn check_cache(ir: &IR) -> Option { } ttl } - IR::Deferred(ir) => check_cache(ir), + IR::Deferred{ir, ..} => check_cache(ir), IR::Dynamic(_) | IR::ContextPath(_) | IR::Map(_) | IR::Service(_) => None, } } diff --git a/src/core/jit/transform/check_const.rs b/src/core/jit/transform/check_const.rs index c8825f71fc..156eced8e1 100644 --- a/src/core/jit/transform/check_const.rs +++ b/src/core/jit/transform/check_const.rs @@ -28,7 +28,7 @@ pub fn is_const(ir: &IR) -> bool { IR::Discriminate(_, ir) => is_const(ir), IR::Entity(hash_map) => hash_map.values().all(is_const), IR::Service(_) => true, - IR::Deferred(ir) => is_const(ir), + IR::Deferred{ir, ..} => is_const(ir), } } diff --git a/src/core/jit/transform/check_dedupe.rs b/src/core/jit/transform/check_dedupe.rs index f6018e6551..5f78201a12 100644 --- a/src/core/jit/transform/check_dedupe.rs +++ b/src/core/jit/transform/check_dedupe.rs @@ -27,7 +27,7 @@ fn check_dedupe(ir: &IR) -> bool { IR::ContextPath(_) => true, IR::Map(_) => true, IR::Service(_) => true, - IR::Deferred(ir) => check_dedupe(ir) + IR::Deferred{ir, ..} => check_dedupe(ir) } } diff --git a/src/core/jit/transform/check_protected.rs b/src/core/jit/transform/check_protected.rs index 93c1d0d8ef..3973048880 100644 --- a/src/core/jit/transform/check_protected.rs +++ b/src/core/jit/transform/check_protected.rs @@ -28,7 +28,7 @@ pub fn is_protected(ir: &IR) -> bool { IR::Discriminate(_, ir) => is_protected(ir), IR::Entity(hash_map) => hash_map.values().any(is_protected), IR::Service(_) => false, - IR::Deferred(ir) => is_protected(ir), + IR::Deferred{ir, ..} => is_protected(ir), } } diff --git a/src/core/jit/transform/defer_planner.rs b/src/core/jit/transform/defer_planner.rs index 3d37442f5a..383d27a44c 100644 --- a/src/core/jit/transform/defer_planner.rs +++ b/src/core/jit/transform/defer_planner.rs @@ -21,7 +21,7 @@ fn move_deferred_fields(field: &mut Field) -> Vec> { let mut deferred_fields = Vec::new(); for selection in field.selection.iter_mut() { match selection.ir { - Some(IR::Deferred(_)) => { + Some(IR::Deferred { .. }) => { deferred_fields.push(selection.clone()); } _ => {} @@ -31,7 +31,7 @@ fn move_deferred_fields(field: &mut Field) -> Vec> { field.selection.retain(|f| { f.ir.as_ref() - .map_or(true, |ir| !matches!(ir, IR::Deferred(_))) + .map_or(true, |ir| !matches!(ir, IR::Deferred { .. })) }); deferred_fields diff --git a/src/core/jit/transform/wrap_defer.rs b/src/core/jit/transform/wrap_defer.rs index 4f24ea01fc..45f3f4e876 100644 --- a/src/core/jit/transform/wrap_defer.rs +++ b/src/core/jit/transform/wrap_defer.rs @@ -18,7 +18,8 @@ impl WrapDefer { /// goes through selection and finds out IR's that needs to be deferred. #[inline] -fn detect_and_wrap(field: &mut Field) { +fn detect_and_wrap(field: &mut Field, path: &mut Vec) { + path.push(field.output_name.clone()); for selection in field.selection.iter_mut() { if let Some(ir) = std::mem::take(&mut selection.ir) { let ir = if selection @@ -28,20 +29,26 @@ fn detect_and_wrap(field: &mut Field) { .is_some() && field.ir.is_some() { - IR::Deferred(Box::new(ir)) + IR::Deferred { ir: Box::new(ir), path: vec![] } } else { ir }; selection.ir = Some(ir); } + + detect_and_wrap(selection, path); } + + path.pop(); } impl Transform for WrapDefer { type Value = OperationPlan; type Error = Infallible; fn transform(&self, mut plan: Self::Value) -> Valid { - plan.selection.iter_mut().for_each(detect_and_wrap); + plan.selection + .iter_mut() + .for_each(|f| detect_and_wrap(f, &mut vec![])); Valid::succeed(plan) } } From 61e9f37dfebc3217ac53a3679041aeaad35439d6 Mon Sep 17 00:00:00 2001 From: laststylebender Date: Tue, 26 Nov 2024 18:01:48 +0530 Subject: [PATCH 5/9] - add Id to Deferred IR --- src/core/ir/model.rs | 13 +++++- src/core/jit/exec_const.rs | 3 ++ src/core/jit/response.rs | 4 ++ src/core/jit/transform/wrap_defer.rs | 61 +++++++++++++++------------- 4 files changed, 51 insertions(+), 30 deletions(-) diff --git a/src/core/ir/model.rs b/src/core/ir/model.rs index 3658d1cbe1..832b01cb00 100644 --- a/src/core/ir/model.rs +++ b/src/core/ir/model.rs @@ -13,6 +13,14 @@ use crate::core::graphql::{self}; use crate::core::http::HttpFilter; use crate::core::{grpc, http}; +#[derive(Clone, Debug)] +pub struct IrId(usize); +impl IrId { + pub fn new(id: usize) -> Self { + Self(id) + } +} + #[derive(Clone, Debug, Display)] pub enum IR { Dynamic(DynamicValue), @@ -31,6 +39,7 @@ pub enum IR { /// Apollo Federation _service resolver Service(String), Deferred { + id: IrId, ir: Box, path: Vec, }, @@ -178,8 +187,8 @@ impl IR { .collect(), ), IR::Service(sdl) => IR::Service(sdl), - IR::Deferred { ir, path } => { - IR::Deferred { ir: Box::new(ir.modify(modifier)), path } + IR::Deferred { ir, path, id } => { + IR::Deferred { ir: Box::new(ir.modify(modifier)), path, id } } } } diff --git a/src/core/jit/exec_const.rs b/src/core/jit/exec_const.rs index 1606f05631..11b0a0f650 100644 --- a/src/core/jit/exec_const.rs +++ b/src/core/jit/exec_const.rs @@ -98,6 +98,9 @@ impl ConstValueExecutor { let resp: Response = exe.execute(&synth).await; + + // add the pending to response. + if is_introspection_query { let async_req = async_graphql::Request::from(request).only_introspection(); let async_resp = app_ctx.execute(async_req).await; diff --git a/src/core/jit/response.rs b/src/core/jit/response.rs index aabe67dd65..12e414a82f 100644 --- a/src/core/jit/response.rs +++ b/src/core/jit/response.rs @@ -20,6 +20,9 @@ pub struct Response { #[serde(skip)] pub cache_control: CacheControl, + + #[serde(skip_serializing_if = "Vec::is_empty")] + pub pending: Vec } impl Default for Response { @@ -29,6 +32,7 @@ impl Default for Response { errors: Default::default(), extensions: Default::default(), cache_control: Default::default(), + pending: Default::default(), } } } diff --git a/src/core/jit/transform/wrap_defer.rs b/src/core/jit/transform/wrap_defer.rs index 45f3f4e876..e562f255e4 100644 --- a/src/core/jit/transform/wrap_defer.rs +++ b/src/core/jit/transform/wrap_defer.rs @@ -3,52 +3,57 @@ use std::{convert::Infallible, marker::PhantomData}; use tailcall_valid::Valid; use crate::core::{ - ir::model::IR, - jit::{Field, OperationPlan}, - Transform, + counter::{Count, Counter}, ir::model::{IrId, IR}, jit::{Field, OperationPlan}, Transform }; -pub struct WrapDefer(PhantomData); +pub struct WrapDefer{ + _marker: PhantomData, + defer_id: Counter, +} impl WrapDefer { pub fn new() -> Self { - Self(PhantomData) + Self { + _marker: PhantomData, + defer_id: Counter::new(0), + } } -} - -/// goes through selection and finds out IR's that needs to be deferred. -#[inline] -fn detect_and_wrap(field: &mut Field, path: &mut Vec) { - path.push(field.output_name.clone()); - for selection in field.selection.iter_mut() { - if let Some(ir) = std::mem::take(&mut selection.ir) { - let ir = if selection - .directives - .iter() - .find(|d| d.name == "defer") - .is_some() - && field.ir.is_some() - { - IR::Deferred { ir: Box::new(ir), path: vec![] } - } else { - ir - }; - selection.ir = Some(ir); + /// goes through selection and finds out IR's that needs to be deferred. + #[inline] + fn detect_and_wrap(&self,field: &mut Field, path: &mut Vec) { + path.push(field.output_name.clone()); + for selection in field.selection.iter_mut() { + if let Some(ir) = std::mem::take(&mut selection.ir) { + let ir = if selection + .directives + .iter() + .find(|d| d.name == "defer") + .is_some() + && field.ir.is_some() + { + IR::Deferred { ir: Box::new(ir), path: vec![], id: IrId::new(self.defer_id.next()) } + } else { + ir + }; + selection.ir = Some(ir); + } + + self.detect_and_wrap(selection, path); } - detect_and_wrap(selection, path); + path.pop(); } - path.pop(); } + impl Transform for WrapDefer { type Value = OperationPlan; type Error = Infallible; fn transform(&self, mut plan: Self::Value) -> Valid { plan.selection .iter_mut() - .for_each(|f| detect_and_wrap(f, &mut vec![])); + .for_each(|f| self.detect_and_wrap(f, &mut vec![])); Valid::succeed(plan) } } From a34d149b9a2374b9797daf59625b5965b70e19a7 Mon Sep 17 00:00:00 2001 From: laststylebender Date: Tue, 26 Nov 2024 18:20:35 +0530 Subject: [PATCH 6/9] - fix bug --- src/core/jit/transform/defer_planner.rs | 20 ++++++++++-- src/core/jit/transform/wrap_defer.rs | 41 ++++++++++++++++--------- 2 files changed, 45 insertions(+), 16 deletions(-) diff --git a/src/core/jit/transform/defer_planner.rs b/src/core/jit/transform/defer_planner.rs index 383d27a44c..a1253803fc 100644 --- a/src/core/jit/transform/defer_planner.rs +++ b/src/core/jit/transform/defer_planner.rs @@ -1,4 +1,4 @@ -use std::{convert::Infallible, marker::PhantomData}; +use std::{convert::Infallible, fmt::Debug, marker::PhantomData}; use tailcall_valid::Valid; @@ -37,16 +37,32 @@ fn move_deferred_fields(field: &mut Field) -> Vec> { deferred_fields } -impl Transform for DeferPlanner { +impl Transform for DeferPlanner { type Value = OperationPlan; type Error = Infallible; fn transform(&self, mut plan: Self::Value) -> Valid { let mut deferred_fields = Vec::new(); for field in plan.selection.iter_mut() { + match field.ir { + Some(IR::Deferred { .. }) => { + deferred_fields.push(field.clone()); + } + _ => {} + } + deferred_fields.extend(move_deferred_fields(field)); } + + plan.selection.retain(|f| { + f.ir.as_ref() + .map_or(true, |ir| !matches!(ir, IR::Deferred { .. })) + }); + plan.deferred_fields = deferred_fields; + + println!("{:?}", plan.deferred_fields); + Valid::succeed(plan) } } diff --git a/src/core/jit/transform/wrap_defer.rs b/src/core/jit/transform/wrap_defer.rs index e562f255e4..6028ccc745 100644 --- a/src/core/jit/transform/wrap_defer.rs +++ b/src/core/jit/transform/wrap_defer.rs @@ -3,24 +3,24 @@ use std::{convert::Infallible, marker::PhantomData}; use tailcall_valid::Valid; use crate::core::{ - counter::{Count, Counter}, ir::model::{IrId, IR}, jit::{Field, OperationPlan}, Transform + counter::{Count, Counter}, + ir::model::{IrId, IR}, + jit::{Field, OperationPlan}, + Transform, }; -pub struct WrapDefer{ +pub struct WrapDefer { _marker: PhantomData, defer_id: Counter, } impl WrapDefer { pub fn new() -> Self { - Self { - _marker: PhantomData, - defer_id: Counter::new(0), - } + Self { _marker: PhantomData, defer_id: Counter::new(0) } } /// goes through selection and finds out IR's that needs to be deferred. #[inline] - fn detect_and_wrap(&self,field: &mut Field, path: &mut Vec) { + fn detect_and_wrap(&self, field: &mut Field, path: &mut Vec) { path.push(field.output_name.clone()); for selection in field.selection.iter_mut() { if let Some(ir) = std::mem::take(&mut selection.ir) { @@ -29,9 +29,12 @@ impl WrapDefer { .iter() .find(|d| d.name == "defer") .is_some() - && field.ir.is_some() { - IR::Deferred { ir: Box::new(ir), path: vec![], id: IrId::new(self.defer_id.next()) } + IR::Deferred { + ir: Box::new(ir), + path: vec![], + id: IrId::new(self.defer_id.next()), + } } else { ir }; @@ -43,17 +46,27 @@ impl WrapDefer { path.pop(); } - } - impl Transform for WrapDefer { type Value = OperationPlan; type Error = Infallible; fn transform(&self, mut plan: Self::Value) -> Valid { - plan.selection - .iter_mut() - .for_each(|f| self.detect_and_wrap(f, &mut vec![])); + plan.selection.iter_mut().for_each(|f| { + if let Some(ir) = std::mem::take(&mut f.ir) { + let ir = if f.directives.iter().find(|d| d.name == "defer").is_some() { + IR::Deferred { + ir: Box::new(ir), + path: vec![], + id: IrId::new(self.defer_id.next()), + } + } else { + ir + }; + f.ir = Some(ir); + } + self.detect_and_wrap(f, &mut vec![]) + }); Valid::succeed(plan) } } From efa0b1b1a95ef8e23ce0272c328aceed0a05ed9f Mon Sep 17 00:00:00 2001 From: laststylebender Date: Tue, 26 Nov 2024 18:52:49 +0530 Subject: [PATCH 7/9] - constrait: only independent ir's can be deferred. --- src/core/app_context.rs | 2 ++ src/core/blueprint/operators/http.rs | 8 ++++++++ src/core/ir/model.rs | 12 ++++++++++++ src/core/jit/transform/wrap_defer.rs | 15 +++++++++++++++ 4 files changed, 37 insertions(+) diff --git a/src/core/app_context.rs b/src/core/app_context.rs index 03036584c6..c0dd7f146f 100644 --- a/src/core/app_context.rs +++ b/src/core/app_context.rs @@ -55,6 +55,7 @@ impl AppContext { http_filter, is_list, dedupe, + is_dependent, .. } => { let is_list = *is_list; @@ -73,6 +74,7 @@ impl AppContext { http_filter: http_filter.clone(), is_list, dedupe, + is_dependent: *is_dependent, })); http_data_loaders.push(data_loader); diff --git a/src/core/blueprint/operators/http.rs b/src/core/blueprint/operators/http.rs index 6518388845..534268ab49 100644 --- a/src/core/blueprint/operators/http.rs +++ b/src/core/blueprint/operators/http.rs @@ -63,6 +63,12 @@ pub fn compile_http( .or(config_module.upstream.on_request.clone()) .map(|on_request| HttpFilter { on_request }); + // check if the resolver is independent or not. + let is_dependent = http.query.iter().any(|q| q.value.contains("{{.value") || q.value.contains("{{value")) || http + .body + .as_ref() + .map_or(false, |b| b.contains("{{.value") || b.contains("{{value")); + let io = if !http.batch_key.is_empty() && http.method == Method::GET { // Find a query parameter that contains a reference to the {{.value}} key let key = http.query.iter().find_map(|q| { @@ -77,6 +83,7 @@ pub fn compile_http( http_filter, is_list, dedupe, + is_dependent, }) } else { IR::IO(IO::Http { @@ -86,6 +93,7 @@ pub fn compile_http( http_filter, is_list, dedupe, + is_dependent, }) }; (io, &http.select) diff --git a/src/core/ir/model.rs b/src/core/ir/model.rs index 832b01cb00..90546e3a49 100644 --- a/src/core/ir/model.rs +++ b/src/core/ir/model.rs @@ -14,6 +14,7 @@ use crate::core::http::HttpFilter; use crate::core::{grpc, http}; #[derive(Clone, Debug)] +#[allow(dead_code)] pub struct IrId(usize); impl IrId { pub fn new(id: usize) -> Self { @@ -61,6 +62,7 @@ pub enum IO { http_filter: Option, is_list: bool, dedupe: bool, + is_dependent: bool, }, GraphQL { req_template: graphql::RequestTemplate, @@ -81,6 +83,16 @@ pub enum IO { } impl IO { + // TODO: fix is_dependent for GraphQL and Grpc. + pub fn is_dependent(&self) -> bool { + match self { + IO::Http { is_dependent, .. } => *is_dependent, + IO::GraphQL { .. } => true, + IO::Grpc { .. } => true, + IO::Js { .. } => false, + } + } + pub fn dedupe(&self) -> bool { match self { IO::Http { dedupe, .. } => *dedupe, diff --git a/src/core/jit/transform/wrap_defer.rs b/src/core/jit/transform/wrap_defer.rs index 6028ccc745..b6d1639802 100644 --- a/src/core/jit/transform/wrap_defer.rs +++ b/src/core/jit/transform/wrap_defer.rs @@ -14,6 +14,20 @@ pub struct WrapDefer { defer_id: Counter, } +fn check_dependent_irs(ir: &IR) -> bool { + match ir { + IR::IO(io) => io.is_dependent(), + IR::Cache(cache) => cache.io.is_dependent(), + IR::Deferred { .. } | IR::Service(_) | IR::ContextPath(_) | IR::Dynamic(_) => false, + IR::Path(ir, _) => check_dependent_irs(ir), + IR::Map(map) => check_dependent_irs(&map.input), + IR::Pipe(l, r) => check_dependent_irs(l) || check_dependent_irs(r), + IR::Discriminate(_, ir) => check_dependent_irs(ir), + IR::Entity(map) => map.values().any(check_dependent_irs), + IR::Protect(_, ir) => check_dependent_irs(ir), + } +} + impl WrapDefer { pub fn new() -> Self { Self { _marker: PhantomData, defer_id: Counter::new(0) } @@ -29,6 +43,7 @@ impl WrapDefer { .iter() .find(|d| d.name == "defer") .is_some() + && !check_dependent_irs(&ir) { IR::Deferred { ir: Box::new(ir), From 120018509efa5d3513b5d82a5bab9a5b6d836f03 Mon Sep 17 00:00:00 2001 From: laststylebender Date: Tue, 26 Nov 2024 19:01:34 +0530 Subject: [PATCH 8/9] - add check_dependent_irs check --- src/core/jit/transform/wrap_defer.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/core/jit/transform/wrap_defer.rs b/src/core/jit/transform/wrap_defer.rs index b6d1639802..0d995e8030 100644 --- a/src/core/jit/transform/wrap_defer.rs +++ b/src/core/jit/transform/wrap_defer.rs @@ -69,7 +69,9 @@ impl Transform for WrapDefer { fn transform(&self, mut plan: Self::Value) -> Valid { plan.selection.iter_mut().for_each(|f| { if let Some(ir) = std::mem::take(&mut f.ir) { - let ir = if f.directives.iter().find(|d| d.name == "defer").is_some() { + let ir = if f.directives.iter().find(|d| d.name == "defer").is_some() + && !check_dependent_irs(&ir) + { IR::Deferred { ir: Box::new(ir), path: vec![], From 0ede5c037785ebb5cc8499c3854d739ce6e4156f Mon Sep 17 00:00:00 2001 From: laststylebender Date: Tue, 26 Nov 2024 21:21:23 +0530 Subject: [PATCH 9/9] - use the path. --- src/core/jit/transform/wrap_defer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/jit/transform/wrap_defer.rs b/src/core/jit/transform/wrap_defer.rs index 0d995e8030..5a6fa911f3 100644 --- a/src/core/jit/transform/wrap_defer.rs +++ b/src/core/jit/transform/wrap_defer.rs @@ -47,7 +47,7 @@ impl WrapDefer { { IR::Deferred { ir: Box::new(ir), - path: vec![], + path: path.clone(), id: IrId::new(self.defer_id.next()), } } else {