akar_processor/processor/mapper/
map_join.rs1use super::ExecutionContext;
2use crate::physical_operator::*;
3use akar_common::error::ProcessorError;
4use akar_common::vector::DataChunk;
5use akar_parser::ast::Expression;
6use akar_planner::logical_operator::LogicalOperator;
7
8use crate::processor::join_helpers::derive_join_column_indices;
9use crate::processor::union_helpers::{flatten_union_child, merge_optional_chunks};
10
11pub fn map_and_execute_join(
12 op: &LogicalOperator,
13 current_input: Vec<DataChunk>,
14 ctx: &mut ExecutionContext,
15) -> Result<Vec<DataChunk>, ProcessorError> {
16 match op {
17 LogicalOperator::HashJoin(h) => {
18 let left_ops = flatten_union_child(&h.build_side);
19 let right_ops = flatten_union_child(&h.probe_side);
20
21 let build_chunks = ctx.execute_children(&left_ops)?;
22 let probe_chunks = ctx.execute_children(&right_ops)?;
23
24 let (build_cols, probe_cols) = derive_join_column_indices(&h.join_keys, &build_chunks, &probe_chunks);
25 let join = PhysicalHashJoin::new(build_cols, probe_cols);
26 let result = join.execute_binary(&build_chunks, &probe_chunks)?;
27 Ok(result)
28 }
29 LogicalOperator::SemiJoin(s) => {
30 let left_ops = flatten_union_child(&s.left);
31 let right_ops = flatten_union_child(&s.right);
32
33 let build_chunks = ctx.execute_children(&left_ops)?;
34 let probe_chunks = ctx.execute_children(&right_ops)?;
35
36 let (build_cols, probe_cols) = derive_join_column_indices(&s.join_keys, &build_chunks, &probe_chunks);
37 let semi = PhysicalSemiJoin {
38 build_columns: build_cols,
39 probe_columns: probe_cols,
40 };
41 let result = semi.execute_binary(&build_chunks, &probe_chunks)?;
42 Ok(result)
43 }
44 LogicalOperator::AntiJoin(a) => {
45 let left_ops = flatten_union_child(&a.left);
46 let right_ops = flatten_union_child(&a.right);
47
48 let build_chunks = ctx.execute_children(&left_ops)?;
49 let probe_chunks = ctx.execute_children(&right_ops)?;
50
51 let (build_cols, probe_cols) = derive_join_column_indices(&a.join_keys, &build_chunks, &probe_chunks);
52 let anti = PhysicalAntiJoin {
53 build_columns: build_cols,
54 probe_columns: probe_cols,
55 };
56 let result = anti.execute_binary(&build_chunks, &probe_chunks)?;
57 Ok(result)
58 }
59 LogicalOperator::Intersect(ic) => {
60 let build_sides = collect_union_sides(&ic.left);
64 let probe_ops = flatten_union_child(&ic.right);
65
66 let build_chunk_sides: Vec<Vec<DataChunk>> = build_sides
67 .iter()
68 .map(|ops| ctx.execute_children(ops))
69 .collect::<Result<_, _>>()?;
70 let probe_chunks = ctx.execute_children(&probe_ops)?;
71
72 let (probe_key_col, build_key_col) =
73 resolve_intersect_key_cols(&ic.build_key_exprs, &probe_chunks, &build_chunk_sides);
74
75 let intersect = PhysicalIntersect {
76 num_build_sides: build_chunk_sides.len() as u32,
77 probe_key_col,
78 build_key_col,
79 };
80 let result = intersect.execute_sides(&build_chunk_sides, &probe_chunks)?;
81 Ok(result)
82 }
83 LogicalOperator::CrossProduct(cp) => {
84 let left_ops = flatten_union_child(&cp.left);
85 let right_ops = flatten_union_child(&cp.right);
86 let build_chunks = ctx.execute_children(&left_ops)?;
87 let probe_chunks = ctx.execute_children(&right_ops)?;
88 let cross = PhysicalCrossProduct;
89 let result = cross.execute_binary(&build_chunks, &probe_chunks)?;
90 Ok(result)
91 }
92 LogicalOperator::OptionalMatch(om) => {
93 let left_ops = flatten_union_child(&om.left);
95 let left_result = ctx.execute_children(&left_ops)?;
96
97 let right_ops = flatten_union_child(&om.right);
99 let right_result = ctx.execute_children(&right_ops)?;
100
101 let merged = merge_optional_chunks(left_result, right_result)?;
103 Ok(merged)
104 }
105 LogicalOperator::RecursiveExtend(re) => {
106 let scan = PhysicalRecursiveExtend {
107 source_table_id: re.source_table_id,
108 rel_table_ids: re.rel_table_ids.clone(),
109 lower_bound: re.lower_bound,
110 upper_bound: re.upper_bound,
111 direction: re.direction,
112 semantic: re.semantic,
113 table_catalog: ctx.table_catalog.clone(),
114 weight_property: re.weight_property.clone(),
115 cost_output_name: re.cost_output_name.clone(),
116 };
117 let result = scan.execute(current_input)?;
118 Ok(result)
119 }
120 _ => Err(format!("Not a join operator: {:?}", op).into()),
121 }
122}
123
124fn collect_union_sides(op: &LogicalOperator) -> Vec<Vec<LogicalOperator>> {
127 match op {
128 LogicalOperator::Union(u) => {
129 let mut sides = collect_union_sides(&u.left);
130 sides.extend(collect_union_sides(&u.right));
131 sides
132 }
133 other => vec![flatten_union_child(other)],
134 }
135}
136
137fn resolve_intersect_key_cols(
142 build_key_exprs: &[Expression],
143 probe_chunks: &[DataChunk],
144 build_sides: &[Vec<DataChunk>],
145) -> (u32, u32) {
146 let var = build_key_exprs.first().and_then(|e| match e {
147 Expression::Variable(v) => Some(v.clone()),
148 Expression::PropertyAccess(obj, _) => {
149 if let Expression::Variable(v) = &**obj {
150 Some(v.clone())
151 } else {
152 None
153 }
154 }
155 _ => None,
156 });
157
158 let probe_names: Vec<&str> = probe_chunks
159 .first()
160 .map(|c| c.field_names.iter().map(|s| s.as_str()).collect())
161 .unwrap_or_default();
162 let build_names: Vec<&str> = build_sides
163 .first()
164 .and_then(|s| s.first())
165 .map(|c| c.field_names.iter().map(|s| s.as_str()).collect())
166 .unwrap_or_default();
167
168 let mut probe_col = 0u32;
169 let mut build_col = 0u32;
170 if let Some(var) = var {
171 let candidates = [format!("{var}._id"), format!("{var}.id"), var];
172 for c in &candidates {
173 if let Some(idx) = probe_names.iter().position(|n| n == c) {
174 probe_col = idx as u32;
175 break;
176 }
177 }
178 for c in &candidates {
179 if let Some(idx) = build_names.iter().position(|n| n == c) {
180 build_col = idx as u32;
181 break;
182 }
183 }
184 }
185 (probe_col, build_col)
186}