akar_processor/processor/
join_helpers.rs1use akar_common::vector::DataChunk;
2use akar_parser::ast::Expression;
3
4#[derive(Debug, Clone)]
9pub struct JoinKeyBinding {
10 pub build_col: u32,
11 pub probe_col: u32,
12 pub build_map_key: Option<String>,
14 pub probe_map_key: Option<String>,
16}
17
18pub fn derive_join_bindings(
30 join_keys: &[Expression],
31 build_chunks: &[DataChunk],
32 probe_chunks: &[DataChunk],
33) -> Vec<JoinKeyBinding> {
34 let build_names: Vec<&str> = build_chunks
35 .first()
36 .map(|c| c.field_names.iter().map(|s| s.as_str()).collect())
37 .unwrap_or_default();
38 let probe_names: Vec<&str> = probe_chunks
39 .first()
40 .map(|c| c.field_names.iter().map(|s| s.as_str()).collect())
41 .unwrap_or_default();
42
43 let mut bindings = Vec::new();
44 for key in join_keys {
45 if let Expression::BinaryOp(akar_parser::ast::BinaryOp::Equal, left, right) = key {
46 let (lp, lvar, lprop) = split_prop(left);
47 let (rp, rvar, rprop) = split_prop(right);
48
49 let (build_col, build_map_key) = resolve_side(&lp, &lvar, &lprop, &build_names)
50 .or_else(|| resolve_side(&rp, &rvar, &rprop, &build_names))
51 .unwrap_or((0, None));
52 let (probe_col, probe_map_key) = resolve_side(&rp, &rvar, &rprop, &probe_names)
53 .or_else(|| resolve_side(&lp, &lvar, &lprop, &probe_names))
54 .unwrap_or((0, None));
55
56 bindings.push(JoinKeyBinding {
57 build_col: build_col as u32,
58 probe_col: probe_col as u32,
59 build_map_key,
60 probe_map_key,
61 });
62 }
63 }
64
65 if bindings.is_empty() {
66 bindings.push(JoinKeyBinding {
67 build_col: 0,
68 probe_col: 0,
69 build_map_key: None,
70 probe_map_key: None,
71 });
72 }
73 bindings
74}
75
76fn split_prop(expr: &Expression) -> (String, String, String) {
80 match expr {
81 Expression::PropertyAccess(obj, prop) => {
82 if let Expression::Variable(var) = &**obj {
83 (format!("{var}.{prop}"), var.clone(), prop.clone())
84 } else {
85 (prop.clone(), String::new(), prop.clone())
86 }
87 }
88 Expression::Variable(name) => (name.clone(), name.clone(), name.clone()),
89 _ => (String::new(), String::new(), String::new()),
90 }
91}
92
93fn resolve_side(full: &str, var: &str, key: &str, names: &[&str]) -> Option<(usize, Option<String>)> {
96 if let Some(i) = names.iter().position(|n| *n == full) {
97 return Some((i, None));
98 }
99 if let Some(i) = names.iter().position(|n| *n == key) {
100 return Some((i, None));
101 }
102 if !var.is_empty()
103 && let Some(i) = names.iter().position(|n| *n == var)
104 {
105 return Some((i, Some(key.to_string())));
106 }
107 None
108}