reifydb_engine/vm/volcano/join/
common.rs1use std::sync::Arc;
5
6use postcard::to_stdvec;
7use reifydb_core::value::column::{ColumnWithName, buffer::ColumnBuffer, columns::Columns};
8use reifydb_transaction::transaction::Transaction;
9use reifydb_value::{
10 fragment::Fragment,
11 util::hash::{Hash128, xxh3_128},
12 value::Value,
13};
14
15use crate::{
16 Result,
17 expression::{compile::CompiledExpr, context::EvalContext},
18 vm::volcano::query::{QueryContext, QueryNode},
19};
20
21pub(crate) fn load_and_merge_all<'a>(
22 node: &mut Box<dyn QueryNode>,
23 rx: &mut Transaction<'a>,
24 ctx: &mut QueryContext,
25) -> Result<Columns> {
26 let mut result: Option<Columns> = None;
27
28 while let Some(columns) = node.next(rx, ctx)? {
29 if let Some(mut acc) = result.take() {
30 acc.append_columns(columns)?;
31 result = Some(acc);
32 } else {
33 result = Some(columns);
34 }
35 }
36 let result = result.unwrap_or_else(Columns::empty);
37 Ok(result)
38}
39
40pub struct ResolvedColumnNames {
41 pub qualified_names: Vec<String>,
42}
43
44pub fn resolve_column_names(
45 left_columns: &Columns,
46 right_columns: &Columns,
47 alias: &Option<Fragment>,
48 excluded_right_indices: Option<&[usize]>,
49) -> ResolvedColumnNames {
50 let mut qualified_names = Vec::new();
51
52 for col in left_columns.iter() {
53 qualified_names.push(col.name().text().to_string());
54 }
55
56 for (idx, col) in right_columns.iter().enumerate() {
57 if let Some(excluded) = excluded_right_indices
58 && excluded.contains(&idx)
59 {
60 continue;
61 }
62
63 let col_name = col.name().text();
64
65 let alias_text = alias.as_ref().map(|a| a.text()).unwrap_or("other");
66 let prefixed_name = format!("{}_{}", alias_text, col_name);
67
68 let mut final_name = prefixed_name.clone();
69 if qualified_names.contains(&final_name) {
70 let mut counter = 2;
71 loop {
72 let candidate = format!("{}_{}", prefixed_name, counter);
73 if !qualified_names.contains(&candidate) {
74 final_name = candidate;
75 break;
76 }
77 counter += 1;
78 }
79 }
80
81 qualified_names.push(final_name);
82 }
83
84 ResolvedColumnNames {
85 qualified_names,
86 }
87}
88
89pub fn build_eval_columns(
90 left_columns: &Columns,
91 right_columns: &Columns,
92 left_row: &[Value],
93 right_row: &[Value],
94 alias: &Option<Fragment>,
95) -> Vec<ColumnWithName> {
96 let mut eval_columns = Vec::new();
97
98 for (idx, col) in left_columns.iter().enumerate() {
99 let data = match &left_row[idx] {
100 Value::None {
101 ..
102 } => ColumnBuffer::typed_none(&col.get_type()),
103 value => ColumnBuffer::from(value.clone()),
104 };
105 eval_columns.push(ColumnWithName::new(col.name().clone(), data));
106 }
107
108 for (idx, col) in right_columns.iter().enumerate() {
109 let data = match &right_row[idx] {
110 Value::None {
111 ..
112 } => ColumnBuffer::typed_none(&col.get_type()),
113 value => ColumnBuffer::from(value.clone()),
114 };
115 if let Some(alias) = alias {
116 let aliased_name = Fragment::internal(format!("{}.{}", alias.text(), col.name().text()));
117 eval_columns.push(ColumnWithName {
118 name: aliased_name,
119 data,
120 });
121 } else {
122 eval_columns.push(ColumnWithName::new(col.name().clone(), data));
123 }
124 }
125
126 eval_columns
127}
128
129pub struct JoinContext {
130 pub context: Option<Arc<QueryContext>>,
131 pub compiled: Vec<CompiledExpr>,
132}
133
134impl Default for JoinContext {
135 fn default() -> Self {
136 Self::new()
137 }
138}
139
140impl JoinContext {
141 pub fn new() -> Self {
142 Self {
143 context: None,
144 compiled: vec![],
145 }
146 }
147
148 pub fn set(&mut self, ctx: &QueryContext) {
149 self.context = Some(Arc::new(ctx.clone()));
150 }
151
152 pub fn get(&self) -> &Arc<QueryContext> {
153 self.context.as_ref().expect("Join context not initialized")
154 }
155
156 pub fn is_initialized(&self) -> bool {
157 self.context.is_some()
158 }
159}
160
161pub(crate) fn compute_join_hash(
162 columns: &Columns,
163 col_indices: &[usize],
164 row_idx: usize,
165 buf: &mut Vec<u8>,
166) -> Option<Hash128> {
167 buf.clear();
168 for &idx in col_indices {
169 let value = columns[idx].get_value(row_idx);
170 if matches!(value, Value::None { .. }) {
171 return None;
172 }
173 let bytes = to_stdvec(&value).ok()?;
174 buf.extend_from_slice(&bytes);
175 }
176 Some(xxh3_128(buf))
177}
178
179pub(crate) fn keys_equal_by_index(
180 left: &Columns,
181 left_row: usize,
182 left_indices: &[usize],
183 right: &Columns,
184 right_row: usize,
185 right_indices: &[usize],
186) -> bool {
187 for (&li, &ri) in left_indices.iter().zip(right_indices.iter()) {
188 let lv = left[li].get_value(left_row);
189 let rv = right[ri].get_value(right_row);
190 if lv != rv {
191 return false;
192 }
193 }
194 true
195}
196
197pub(crate) fn eval_join_condition(
198 compiled: &[CompiledExpr],
199 left_columns: &Columns,
200 right_columns: &Columns,
201 left_row: &[Value],
202 right_row: &[Value],
203 alias: &Option<Fragment>,
204 ctx: &QueryContext,
205) -> bool {
206 if compiled.is_empty() {
207 return true;
208 }
209 let eval_columns = build_eval_columns(left_columns, right_columns, left_row, right_row, alias);
210 let session = EvalContext::from_query(ctx);
211 let exec_ctx = session.with_eval_join(Columns::new(eval_columns));
212 compiled.iter().all(|compiled_expr| {
213 let col = compiled_expr.execute(&exec_ctx).unwrap();
214 matches!(col.data().get_value(0), Value::Boolean(true))
215 })
216}