akar_processor/processor/mapper/
map_join.rs1use super::ExecutionContext;
2use crate::physical_operator::*;
3use akar_common::error::ProcessorError;
4use akar_common::types::{PhysicalTypeID, Value};
5use akar_common::vector::DataChunk;
6use akar_parser::ast::Expression;
7use akar_planner::logical_operator::LogicalOperator;
8
9use crate::processor::join_helpers::{JoinKeyBinding, derive_join_bindings};
10use crate::processor::union_helpers::{flatten_union_child, merge_optional_chunks};
11
12pub fn map_and_execute_join(
13 op: &LogicalOperator,
14 current_input: Vec<DataChunk>,
15 ctx: &mut ExecutionContext,
16) -> Result<Vec<DataChunk>, ProcessorError> {
17 match op {
18 LogicalOperator::HashJoin(h) => {
19 let left_ops = flatten_union_child(&h.build_side);
20 let right_ops = flatten_union_child(&h.probe_side);
21
22 let build_chunks = ctx.execute_children(&left_ops)?;
23 let probe_chunks = ctx.execute_children(&right_ops)?;
24
25 let (build_orig, probe_orig) = (field_count(&build_chunks), field_count(&probe_chunks));
26 let (build_chunks, build_cols, build_appended, probe_chunks, probe_cols, probe_appended) =
27 prepare_join_sides(&h.join_keys, build_chunks, probe_chunks)?;
28
29 let join = PhysicalHashJoin::new(build_cols, probe_cols);
30 let result = join.execute_binary(&build_chunks, &probe_chunks)?;
31
32 Ok(strip_join_synthetic_columns(
33 result,
34 build_orig,
35 build_appended,
36 probe_orig,
37 probe_appended,
38 true,
39 ))
40 }
41 LogicalOperator::SemiJoin(s) => {
42 let left_ops = flatten_union_child(&s.left);
43 let right_ops = flatten_union_child(&s.right);
44
45 let build_chunks = ctx.execute_children(&left_ops)?;
46 let probe_chunks = ctx.execute_children(&right_ops)?;
47
48 let (_build_orig, probe_orig) = (field_count(&build_chunks), field_count(&probe_chunks));
49 let (build_chunks, build_cols, _build_appended, probe_chunks, probe_cols, probe_appended) =
50 prepare_join_sides(&s.join_keys, build_chunks, probe_chunks)?;
51
52 let semi = PhysicalSemiJoin {
53 build_columns: build_cols,
54 probe_columns: probe_cols,
55 };
56 let result = semi.execute_binary(&build_chunks, &probe_chunks)?;
57 Ok(strip_join_synthetic_columns(
58 result,
59 0,
60 0,
61 probe_orig,
62 probe_appended,
63 false,
64 ))
65 }
66 LogicalOperator::AntiJoin(a) => {
67 let left_ops = flatten_union_child(&a.left);
68 let right_ops = flatten_union_child(&a.right);
69
70 let build_chunks = ctx.execute_children(&left_ops)?;
71 let probe_chunks = ctx.execute_children(&right_ops)?;
72
73 let (_build_orig, probe_orig) = (field_count(&build_chunks), field_count(&probe_chunks));
74 let (build_chunks, build_cols, _build_appended, probe_chunks, probe_cols, probe_appended) =
75 prepare_join_sides(&a.join_keys, build_chunks, probe_chunks)?;
76
77 let anti = PhysicalAntiJoin {
78 build_columns: build_cols,
79 probe_columns: probe_cols,
80 };
81 let result = anti.execute_binary(&build_chunks, &probe_chunks)?;
82 Ok(strip_join_synthetic_columns(
83 result,
84 0,
85 0,
86 probe_orig,
87 probe_appended,
88 false,
89 ))
90 }
91 LogicalOperator::Intersect(ic) => {
92 let build_sides = collect_union_sides(&ic.left);
96 let probe_ops = flatten_union_child(&ic.right);
97
98 let build_chunk_sides: Vec<Vec<DataChunk>> = build_sides
99 .iter()
100 .map(|ops| ctx.execute_children(ops))
101 .collect::<Result<_, _>>()?;
102 let probe_chunks = ctx.execute_children(&probe_ops)?;
103
104 let (probe_key_col, build_key_col) =
105 resolve_intersect_key_cols(&ic.build_key_exprs, &probe_chunks, &build_chunk_sides);
106
107 let intersect = PhysicalIntersect {
108 num_build_sides: build_chunk_sides.len() as u32,
109 probe_key_col,
110 build_key_col,
111 };
112 let result = intersect.execute_sides(&build_chunk_sides, &probe_chunks)?;
113 Ok(result)
114 }
115 LogicalOperator::CrossProduct(cp) => {
116 let left_ops = flatten_union_child(&cp.left);
117 let right_ops = flatten_union_child(&cp.right);
118
119 let left_chunks = ctx.execute_children(&left_ops)?;
120 let right_chunks = ctx.execute_children(&right_ops)?;
121
122 let cross = PhysicalCrossProduct;
123 let result = cross.execute_binary(&left_chunks, &right_chunks)?;
124
125 Ok(result)
126 }
127 LogicalOperator::OptionalMatch(om) => {
128 let left_ops = flatten_union_child(&om.left);
130 let left_result = ctx.execute_children(&left_ops)?;
131
132 let right_ops = flatten_union_child(&om.right);
134 let right_result = ctx.execute_children(&right_ops)?;
135
136 let merged = merge_optional_chunks(left_result, right_result)?;
138 Ok(merged)
139 }
140 LogicalOperator::RecursiveExtend(re) => {
141 let scan = PhysicalRecursiveExtend {
142 source_table_id: re.source_table_id,
143 rel_table_ids: re.rel_table_ids.clone(),
144 lower_bound: re.lower_bound,
145 upper_bound: re.upper_bound,
146 direction: re.direction,
147 semantic: re.semantic,
148 table_catalog: ctx.table_catalog.clone(),
149 weight_property: re.weight_property.clone(),
150 cost_output_name: re.cost_output_name.clone(),
151 };
152 let result = scan.execute(current_input)?;
153 Ok(result)
154 }
155 _ => Err(format!("Not a join operator: {:?}", op).into()),
156 }
157}
158
159fn field_count(chunks: &[DataChunk]) -> usize {
160 chunks.first().map(|c| c.fields.len()).unwrap_or(0)
161}
162
163fn split_bindings(bindings: &[JoinKeyBinding]) -> (Vec<u32>, Vec<Option<String>>, Vec<u32>, Vec<Option<String>>) {
165 let mut build_cols = Vec::new();
166 let mut build_keys = Vec::new();
167 let mut probe_cols = Vec::new();
168 let mut probe_keys = Vec::new();
169 for b in bindings {
170 build_cols.push(b.build_col);
171 build_keys.push(b.build_map_key.clone());
172 probe_cols.push(b.probe_col);
173 probe_keys.push(b.probe_map_key.clone());
174 }
175 (build_cols, build_keys, probe_cols, probe_keys)
176}
177
178fn prepare_join_sides(
182 join_keys: &[Expression],
183 build_chunks: Vec<DataChunk>,
184 probe_chunks: Vec<DataChunk>,
185) -> Result<(Vec<DataChunk>, Vec<u32>, usize, Vec<DataChunk>, Vec<u32>, usize), ProcessorError> {
186 let bindings = derive_join_bindings(join_keys, &build_chunks, &probe_chunks);
187 let (build_cols, build_keys, probe_cols, probe_keys) = split_bindings(&bindings);
188 let (build_chunks, build_cols, build_appended) = materialize_map_keys(&build_chunks, &build_cols, &build_keys)?;
189 let (probe_chunks, probe_cols, probe_appended) = materialize_map_keys(&probe_chunks, &probe_cols, &probe_keys)?;
190 Ok((
191 build_chunks,
192 build_cols,
193 build_appended,
194 probe_chunks,
195 probe_cols,
196 probe_appended,
197 ))
198}
199
200fn materialize_map_keys(
205 chunks: &[DataChunk],
206 cols: &[u32],
207 keys: &[Option<String>],
208) -> Result<(Vec<DataChunk>, Vec<u32>, usize), ProcessorError> {
209 let mut out: Vec<DataChunk> = chunks.to_vec();
210 let mut new_cols: Vec<u32> = cols.to_vec();
211 let base_count = out.first().map(|c| c.fields.len()).unwrap_or(0);
212 let mut appended = 0usize;
213 let mut done: std::collections::HashMap<(u32, String), u32> = std::collections::HashMap::new();
216
217 for (i, key) in keys.iter().enumerate() {
218 let Some(key_name) = key else { continue };
219 let col = cols[i];
220 if let Some(&idx) = done.get(&(col, key_name.clone())) {
221 new_cols[i] = idx;
222 continue;
223 }
224 let new_idx = (base_count + appended) as u32;
225 let mut ok = false;
226 for chunk in out.iter_mut() {
227 if col as usize >= chunk.fields.len() {
228 continue;
229 }
230 let extracted: Vec<Value> = (0..chunk.size)
231 .map(|row| {
232 crate::expression_evaluator::map_property_value(
233 &chunk.get_value(col as usize, row).unwrap_or(Value::Null),
234 key_name,
235 )
236 })
237 .collect();
238 let t = extracted
239 .iter()
240 .find(|v| !matches!(v, Value::Null))
241 .map(|v| v.physical_type())
242 .unwrap_or(PhysicalTypeID::Int64);
243 let arr = crate::expression_evaluator::build_arrow_from_values(&extracted, t, chunk.size)
244 .map_err(|e| e.to_string())?;
245 chunk.fields.push(arr.array);
246 chunk.field_types.push(arr.physical_type);
247 chunk.field_names.push(format!("__join_extract_{}_{}", col, key_name));
248 ok = true;
249 }
250 if ok {
251 appended += 1;
252 }
253 done.insert((col, key_name.clone()), new_idx);
254 new_cols[i] = new_idx;
255 }
256 Ok((out, new_cols, appended))
257}
258
259fn strip_join_synthetic_columns(
265 result: Vec<DataChunk>,
266 build_orig: usize,
267 build_appended: usize,
268 probe_orig: usize,
269 probe_appended: usize,
270 output_has_build: bool,
271) -> Vec<DataChunk> {
272 let mut to_remove: Vec<usize> = Vec::new();
273 if output_has_build {
274 to_remove.extend(build_orig..build_orig + build_appended);
275 let probe_base = build_orig + build_appended + probe_orig;
276 to_remove.extend(probe_base..probe_base + probe_appended);
277 } else {
278 to_remove.extend(probe_orig..probe_orig + probe_appended);
279 }
280 if to_remove.is_empty() {
281 return result;
282 }
283 result
284 .into_iter()
285 .map(|mut chunk| {
286 for &idx in to_remove.iter().rev() {
287 if idx < chunk.fields.len() {
288 chunk.fields.remove(idx);
289 if idx < chunk.field_types.len() {
290 chunk.field_types.remove(idx);
291 }
292 if idx < chunk.field_names.len() {
293 chunk.field_names.remove(idx);
294 }
295 }
296 }
297 chunk
298 })
299 .collect()
300}
301
302fn collect_union_sides(op: &LogicalOperator) -> Vec<Vec<LogicalOperator>> {
305 match op {
306 LogicalOperator::Union(u) => {
307 let mut sides = collect_union_sides(&u.left);
308 sides.extend(collect_union_sides(&u.right));
309 sides
310 }
311 other => vec![flatten_union_child(other)],
312 }
313}
314
315fn resolve_intersect_key_cols(
320 build_key_exprs: &[Expression],
321 probe_chunks: &[DataChunk],
322 build_sides: &[Vec<DataChunk>],
323) -> (u32, u32) {
324 let var = build_key_exprs.first().and_then(|e| match e {
325 Expression::Variable(v) => Some(v.clone()),
326 Expression::PropertyAccess(obj, _) => {
327 if let Expression::Variable(v) = &**obj {
328 Some(v.clone())
329 } else {
330 None
331 }
332 }
333 _ => None,
334 });
335
336 let probe_names: Vec<&str> = probe_chunks
337 .first()
338 .map(|c| c.field_names.iter().map(|s| s.as_str()).collect())
339 .unwrap_or_default();
340 let build_names: Vec<&str> = build_sides
341 .first()
342 .and_then(|s| s.first())
343 .map(|c| c.field_names.iter().map(|s| s.as_str()).collect())
344 .unwrap_or_default();
345
346 let mut probe_col = 0u32;
347 let mut build_col = 0u32;
348 if let Some(var) = var {
349 let candidates = [format!("{var}._id"), format!("{var}.id"), var];
350 for c in &candidates {
351 if let Some(idx) = probe_names.iter().position(|n| n == c) {
352 probe_col = idx as u32;
353 break;
354 }
355 }
356 for c in &candidates {
357 if let Some(idx) = build_names.iter().position(|n| n == c) {
358 build_col = idx as u32;
359 break;
360 }
361 }
362 }
363 (probe_col, build_col)
364}