diff --git a/interactive_engine/executor/ir/core/src/plan/physical.rs b/interactive_engine/executor/ir/core/src/plan/physical.rs index 2ddc705ad3f5..f8fdef18ed03 100644 --- a/interactive_engine/executor/ir/core/src/plan/physical.rs +++ b/interactive_engine/executor/ir/core/src/plan/physical.rs @@ -3050,4 +3050,89 @@ mod test { assert_eq!(builder, expected_builder); } + + #[test] + fn path_expand_project_as_physical() { + let source_opr = pb::Scan { + scan_opt: 0, + alias: None, + params: Some(query_params(vec!["person".into()], vec![])), + idx_predicate: None, + meta_data: None, + }; + + let edge_expand = pb::EdgeExpand { + v_tag: None, + direction: 0, + params: Some(query_params(vec!["knows".into()], vec![])), + expand_opt: 0, // vertex + alias: None, + meta_data: None, + }; + + let path_opr = pb::PathExpand { + base: Some(edge_expand.clone().into()), + start_tag: None, + alias: None, + hop_range: Some(pb::Range { lower: 1, upper: 4 }), + path_opt: 0, // ARBITRARY + result_opt: 1, // ALL_V + condition: None, + }; + + let project_opr = pb::Project { + mappings: vec![ExprAlias { + expr: Some(str_to_expr_pb("@.name".to_string()).unwrap()), + alias: None, + }], + is_append: true, + meta_data: vec![], + }; + + let mut logical_plan = LogicalPlan::with_node(Node::new(0, source_opr.clone().into())); + logical_plan + .append_operator_as_node(path_opr.clone().into(), vec![0]) + .unwrap(); // node 1 + logical_plan + .append_operator_as_node(project_opr.clone().into(), vec![1]) + .unwrap(); // node 2 + + // Case without partition + let mut builder = PlanBuilder::default(); + let mut plan_meta = logical_plan.get_meta().clone(); + logical_plan + .add_job_builder(&mut builder, &mut plan_meta) + .unwrap(); + + let mut expected_builder = PlanBuilder::default(); + expected_builder.add_scan_source(source_opr.clone()); + expected_builder.path_expand(path_opr.clone()); + expected_builder.project(project_opr.clone()); + + assert_eq!(builder, expected_builder); + + // Case with partition + let mut builder = PlanBuilder::default(); + let mut plan_meta = logical_plan.get_meta().clone().with_partition(); + logical_plan + .add_job_builder(&mut builder, &mut plan_meta) + .unwrap(); + + // translate `PathExpand(out("knows"))` to `auxilia("name") + PathExpand() with ExpandBase of out("knows")+auxilia("name")` + let mut path_expand = path_opr.clone(); + path_expand.base.as_mut().unwrap().get_v = + Some(build_auxilia_with_tag_alias_columns(None, None, vec!["name".into()])); + + let mut expected_builder = PlanBuilder::default(); + expected_builder.add_scan_source(source_opr); + expected_builder.shuffle(None); + // post process for path expand: 1. cache properties of path start vertex; 2. + expected_builder.get_v(build_auxilia_with_tag_alias_columns(None, None, vec!["name".into()])); + expected_builder.path_expand(path_expand); + // postprocess for project + expected_builder.shuffle(None); + expected_builder.get_v(build_auxilia_with_tag_alias_columns(None, None, vec![])); + expected_builder.project(project_opr); + assert_eq!(builder, expected_builder); + } } diff --git a/interactive_engine/executor/ir/integrated/tests/pathxd_test.rs b/interactive_engine/executor/ir/integrated/tests/pathxd_test.rs index 8580ae8cb667..60cf06645a97 100644 --- a/interactive_engine/executor/ir/integrated/tests/pathxd_test.rs +++ b/interactive_engine/executor/ir/integrated/tests/pathxd_test.rs @@ -19,6 +19,7 @@ mod common; #[cfg(test)] mod test { + use dyn_type::{object, Object}; use graph_proxy::apis::{Element, GraphElement, ID}; use ir_common::expr_parse::str_to_expr_pb; use ir_common::generated::algebra as pb; @@ -873,4 +874,85 @@ mod test { fn path_expand_exactly_whole_v_e_w2_test() { path_expand_exactly_whole_v_e_query(2) } + + // g.V().hasLabel("person").both("2..3", "knows").values("name") + fn init_path_expand_project_request(result_opt: i32) -> JobRequest { + let source_opr = pb::Scan { + scan_opt: 0, + alias: None, + params: Some(query_params(vec![PERSON_LABEL.into()], vec![], None)), + idx_predicate: None, + meta_data: None, + }; + + let edge_expand = pb::EdgeExpand { + v_tag: None, + direction: 2, + params: Some(query_params(vec![KNOWS_LABEL.into()], vec![], None)), + expand_opt: 0, + alias: None, + meta_data: None, + }; + + let path_expand_opr = pb::PathExpand { + base: Some(edge_expand.into()), + start_tag: None, + alias: None, + hop_range: Some(pb::Range { lower: 2, upper: 3 }), + path_opt: 0, // Arbitrary + result_opt, + condition: None, + }; + + let project_opr = pb::Project { + mappings: vec![pb::project::ExprAlias { + expr: Some(str_to_expr_pb("@.name".to_string()).unwrap()), + alias: None, + }], + is_append: true, + meta_data: vec![], + }; + + let mut job_builder = JobBuilder::default(); + job_builder.add_scan_source(source_opr); + job_builder.shuffle(None); + job_builder.path_expand(path_expand_opr); + job_builder.project(project_opr); + job_builder.sink(default_sink_pb()); + + job_builder.build().unwrap() + } + + #[test] + fn path_expand_allv_project_test() { + initialize(); + let request = init_path_expand_project_request(1); // all v + let mut results = submit_query(request, 2); + let mut result_collection: Vec = vec![]; + let mut expected_result_paths: Vec = vec![ + Object::Vector(vec![object!("marko"), object!("vadas"), object!("marko")]), + Object::Vector(vec![object!("marko"), object!("josh"), object!("marko")]), + Object::Vector(vec![object!("vadas"), object!("marko"), object!("vadas")]), + Object::Vector(vec![object!("vadas"), object!("marko"), object!("josh")]), + Object::Vector(vec![object!("josh"), object!("marko"), object!("vadas")]), + Object::Vector(vec![object!("josh"), object!("marko"), object!("josh")]), + ]; + + while let Some(result) = results.next() { + match result { + Ok(res) => { + let entry = parse_result(res).unwrap(); + if let Some(properties) = entry.get(None).unwrap().as_object() { + result_collection.push(properties.clone()); + } + } + Err(e) => { + panic!("err result {:?}", e); + } + } + } + expected_result_paths.sort(); + result_collection.sort(); + assert_eq!(result_collection, expected_result_paths); + } }