1use crate::physical::common::hash_row;
4use crate::physical::types::{OperatorResult, PhysicalOperatorExec};
5use akar_common::types::Value;
6use akar_common::vector::{DataChunk, ValueVector};
7
8pub struct PhysicalEmptyResult;
10
11impl PhysicalOperatorExec for PhysicalEmptyResult {
12 fn operator_type(&self) -> &str {
13 "empty_result"
14 }
15
16 fn execute(&self, _input: Vec<DataChunk>) -> OperatorResult {
17 Ok(vec![])
18 }
19}
20
21pub struct PhysicalMultiplicityReducer {
23 pub key_columns: Vec<usize>,
24}
25
26impl PhysicalOperatorExec for PhysicalMultiplicityReducer {
27 fn operator_type(&self) -> &str {
28 "multiplicity_reducer"
29 }
30
31 fn execute(&self, input: Vec<DataChunk>) -> OperatorResult {
32 if input.is_empty() {
33 return Ok(input);
34 }
35
36 let mut result = Vec::new();
37 let mut buckets: std::collections::HashMap<u64, Vec<usize>> = std::collections::HashMap::new();
43 let mut kept_keys: Vec<Vec<Value>> = Vec::new();
44
45 for chunk in input {
46 let mut filter_mask = vec![false; chunk.size];
47 for i in 0..chunk.size {
48 let mut row_keys = Vec::with_capacity(self.key_columns.len());
49 for &col_idx in &self.key_columns {
50 let val = chunk.get_value(col_idx, i).unwrap_or(Value::Null);
51 row_keys.push(val);
52 }
53
54 let hash = hash_row(&row_keys);
55 let is_dup = buckets
56 .get(&hash)
57 .is_some_and(|bucket| bucket.iter().any(|&k| kept_keys[k] == row_keys));
58 if !is_dup {
59 match buckets.get_mut(&hash) {
60 Some(bucket) => bucket.push(kept_keys.len()),
61 None => {
62 buckets.insert(hash, vec![kept_keys.len()]);
63 }
64 }
65 kept_keys.push(row_keys);
66 filter_mask[i] = true;
67 }
68 }
69
70 let filtered_size = filter_mask.iter().filter(|&&b| b).count();
71 if filtered_size > 0 {
72 let mut new_fields = Vec::new();
73 let mut new_field_types = Vec::new();
74 for (col_idx, _field) in chunk.fields.iter().enumerate() {
75 let phys_type = chunk.field_types[col_idx];
76 let mut new_field = ValueVector::new(phys_type, filtered_size);
77 let mut current = 0;
78 for (i, &keep) in filter_mask.iter().enumerate() {
79 if keep {
80 if let Some(val) = chunk.get_value(col_idx, i) {
81 let _ = new_field.set_value(current, &val);
82 }
83 current += 1;
84 }
85 }
86 new_fields.push(akar_common::arrow_vector::ArrowVector::from_legacy(&new_field).array);
87 new_field_types.push(phys_type);
88 }
89 result.push(DataChunk {
90 fields: new_fields,
91 field_types: new_field_types,
92 size: filtered_size,
93 field_names: chunk.field_names.clone(),
94 sel_vector: None,
95 });
96 }
97 }
98 Ok(result)
99 }
100}
101
102pub struct PhysicalSkip {
104 pub skip_count: usize,
105}
106
107impl PhysicalOperatorExec for PhysicalSkip {
108 fn operator_type(&self) -> &str {
109 "skip"
110 }
111
112 fn execute(&self, input: Vec<DataChunk>) -> OperatorResult {
113 let mut remaining_skip = self.skip_count;
114 let mut output = Vec::new();
115
116 for chunk in input {
117 if remaining_skip == 0 {
118 output.push(chunk);
119 continue;
120 }
121
122 if chunk.size <= remaining_skip {
123 remaining_skip -= chunk.size;
124 continue;
125 }
126
127 let keep_size = chunk.size - remaining_skip;
129 let mut sliced_fields = Vec::new();
130 let mut sliced_types = Vec::new();
131
132 for (col_idx, _field) in chunk.fields.iter().enumerate() {
133 let phys_type = chunk.field_types[col_idx];
134 let mut new_field = ValueVector::new(phys_type, keep_size);
135 for i in 0..keep_size {
136 let val = chunk
137 .get_value(col_idx, remaining_skip + i)
138 .unwrap_or(akar_common::types::Value::Null);
139 if new_field.set_value(i, &val).is_err() {
142 new_field.set_null(i, true);
143 }
144 }
145 sliced_fields.push(akar_common::arrow_vector::ArrowVector::from_legacy(&new_field).array);
146 sliced_types.push(phys_type);
147 }
148
149 output.push(DataChunk {
150 fields: sliced_fields,
151 field_types: sliced_types,
152 size: keep_size,
153 field_names: chunk.field_names.clone(),
154 sel_vector: None,
155 });
156 remaining_skip = 0;
157 }
158
159 Ok(output)
160 }
161}
162
163pub struct PhysicalUnionAllScan;
165
166impl PhysicalOperatorExec for PhysicalUnionAllScan {
167 fn operator_type(&self) -> &str {
168 "union_all_scan"
169 }
170
171 fn execute(&self, input: Vec<DataChunk>) -> OperatorResult {
172 Ok(input)
173 }
174}
175
176pub struct PhysicalInsert {
178 pub table_name: String,
179 pub table_id: u64,
180 pub columns: Vec<String>,
181 pub values: Vec<Vec<akar_common::types::Value>>,
182 pub table_catalog: std::sync::Arc<akar_storage::table::TableCatalog>,
183 pub txn_id: Option<u64>,
185 pub undo_sink: Option<std::sync::Arc<std::sync::Mutex<Vec<akar_transaction::UndoRecord>>>>,
187 pub wal_sink: Option<akar_storage::wal::WalSink>,
189}
190
191impl PhysicalOperatorExec for PhysicalInsert {
192 fn operator_type(&self) -> &str {
193 "insert"
194 }
195
196 fn execute(&self, _input: Vec<DataChunk>) -> OperatorResult {
197 let mut inserted = 0;
198
199 if let Some(mut rel_tbl) = self.table_catalog.get_rel_table_by_name_mut(&self.table_name) {
201 let mut rels_to_insert = Vec::new();
203 for row_values in &self.values {
204 if row_values.len() >= 2 {
205 let src = if let akar_common::types::Value::Int64(v) = row_values[0] {
207 v as u64
208 } else {
209 0
210 };
211 let dst = if let akar_common::types::Value::Int64(v) = row_values[1] {
212 v as u64
213 } else {
214 0
215 };
216 let props = if row_values.len() > 2 {
217 row_values[2..].to_vec()
218 } else {
219 vec![]
220 };
221 rels_to_insert.push((src, dst, props));
222 }
223 }
224 if !rels_to_insert.is_empty() {
225 let start = rel_tbl.edges.len();
226 if let Ok(count) = rel_tbl.insert_rels_batch(&rels_to_insert) {
227 inserted += count;
228 for (src, dst, props) in &rels_to_insert {
229 akar_storage::wal::log_rel_insert_record(&self.wal_sink, self.table_id, *src, *dst, props);
230 }
231 if let Some(sink) = self.undo_sink.as_ref()
232 && let Ok(mut u) = sink.lock()
233 {
234 for idx in start..start + count as usize {
235 u.push(akar_transaction::UndoRecord::insert(self.table_id, idx as u64));
236 }
237 }
238 }
239 }
240 } else if let Some(mut node_tbl) = self.table_catalog.get_node_table_by_name_mut(&self.table_name) {
241 for row_values in &self.values {
243 if let Ok(row_id) = node_tbl.insert_row_with_txn(row_values.clone(), self.txn_id) {
244 inserted += 1;
245 akar_storage::wal::log_insert_record(&self.wal_sink, self.table_id, row_values);
246 if let Some(sink) = self.undo_sink.as_ref()
247 && let Ok(mut u) = sink.lock()
248 {
249 u.push(akar_transaction::UndoRecord::insert(self.table_id, row_id));
250 }
251 }
252 }
253 } else {
254 return Err(format!("Table '{}' not found for INSERT", self.table_name).into());
255 }
256
257 let mut v = ValueVector::new(akar_common::types::PhysicalTypeID::Int64, 1);
258 v.resize(1);
259 v.set_i64(0, inserted as i64);
260 let arr = akar_common::arrow_vector::ArrowVector::from_legacy(&v).array;
261 Ok(vec![DataChunk::new(
262 vec![arr],
263 vec![akar_common::types::PhysicalTypeID::Int64],
264 )])
265 }
266}
267
268pub struct PhysicalExtensionClause {
270 pub action: akar_parser::ast::ExtensionAction,
271 pub extension_name: String,
272}
273
274impl PhysicalOperatorExec for PhysicalExtensionClause {
275 fn operator_type(&self) -> &str {
276 "extension_clause"
277 }
278
279 fn execute(&self, _input: Vec<DataChunk>) -> OperatorResult {
280 let msg = match self.action {
281 akar_parser::ast::ExtensionAction::Install => {
282 format!("Extension '{}' installed successfully.", self.extension_name)
283 }
284 akar_parser::ast::ExtensionAction::Load => {
285 if self.extension_name.to_lowercase() == "httpfs" {
287 format!(
288 "Extension '{}' loaded (HTTP/S3 virtual file system).",
289 self.extension_name
290 )
291 } else if self.extension_name.to_lowercase() == "fts" {
292 format!("Extension '{}' loaded (Full Text Search).", self.extension_name)
293 } else {
294 format!("Extension '{}' loaded.", self.extension_name)
295 }
296 }
297 akar_parser::ast::ExtensionAction::Uninstall => {
298 format!("Extension '{}' uninstalled.", self.extension_name)
299 }
300 };
301
302 tracing::info!("{}", msg);
303
304 let mut field = ValueVector::new(akar_common::types::PhysicalTypeID::String, 1);
305 let _ = field.set_value(0, &akar_common::types::Value::String(msg));
306 let arr = akar_common::arrow_vector::ArrowVector::from_legacy(&field).array;
307
308 Ok(vec![DataChunk {
309 fields: vec![arr],
310 field_types: vec![akar_common::types::PhysicalTypeID::String],
311 size: 1,
312 field_names: vec!["message".into()],
313 sel_vector: None,
314 }])
315 }
316}
317
318#[cfg(test)]
319mod tests {
320 use super::*;
321 use akar_common::types::PhysicalTypeID;
322
323 fn make_chunk(cols: &[Vec<Value>], names: &[&str]) -> DataChunk {
324 let mut fields = Vec::with_capacity(cols.len());
325 let mut types = Vec::with_capacity(cols.len());
326 for col in cols {
327 let first = col
328 .iter()
329 .find(|v| !matches!(v, Value::Null))
330 .unwrap_or(&Value::Int64(0));
331 let ptype = match first {
332 Value::Int64(_) => PhysicalTypeID::Int64,
333 Value::Double(_) => PhysicalTypeID::Double,
334 Value::Float(_) => PhysicalTypeID::Float,
335 Value::Bool(_) => PhysicalTypeID::Bool,
336 Value::String(_) => PhysicalTypeID::String,
337 _ => PhysicalTypeID::Int64,
338 };
339 let mut v = ValueVector::new(ptype, col.len().max(1));
340 for (i, val) in col.iter().enumerate() {
341 let _ = v.set_value(i, val);
342 }
343 v.resize(col.len());
344 fields.push(akar_common::arrow_vector::ArrowVector::from_legacy(&v).array);
345 types.push(ptype);
346 }
347 let mut chunk = DataChunk::new(fields, types);
348 chunk.field_names = names.iter().map(|s| s.to_string()).collect();
349 chunk
350 }
351
352 fn reducer(key_columns: Vec<usize>) -> PhysicalMultiplicityReducer {
353 PhysicalMultiplicityReducer { key_columns }
354 }
355
356 #[test]
357 fn test_multiplicity_reducer_empty_input_returns_empty() {
358 let out = reducer(vec![0]).execute(Vec::new()).unwrap();
359 assert!(out.is_empty());
360 }
361
362 #[test]
363 fn test_multiplicity_reducer_dedup_across_chunks() {
364 let c1 = make_chunk(&[vec![Value::Int64(1), Value::Int64(2)]], &["x"]);
367 let c2 = make_chunk(&[vec![Value::Int64(2), Value::Int64(3)]], &["x"]);
368 let out = reducer(vec![0]).execute(vec![c1, c2]).unwrap();
369 assert_eq!(out.len(), 2, "one output chunk per non-empty filtered chunk");
370 assert_eq!(out[0].size, 2);
371 assert_eq!(out[1].size, 1, "row 2 is a duplicate of c1's row 2");
372 assert_eq!(out[0].get_value(0, 0), Some(Value::Int64(1)));
373 assert_eq!(out[0].get_value(0, 1), Some(Value::Int64(2)));
374 assert_eq!(out[1].get_value(0, 0), Some(Value::Int64(3)));
375 }
376
377 #[test]
378 fn test_multiplicity_reducer_key_columns_subset() {
379 let chunk = make_chunk(
382 &[
383 vec![Value::Int64(1), Value::Int64(1)],
384 vec![Value::String("a".into()), Value::String("b".into())],
385 ],
386 &["id", "tag"],
387 );
388 let out = reducer(vec![0]).execute(vec![chunk]).unwrap();
389 assert_eq!(out[0].size, 1);
390 assert_eq!(out[0].get_value(0, 0), Some(Value::Int64(1)));
391 assert_eq!(out[0].get_value(1, 0), Some(Value::String("a".into())));
392 }
393
394 #[test]
395 fn test_multiplicity_reducer_multicolumn_key() {
396 let chunk = make_chunk(
399 &[
400 vec![Value::Int64(1), Value::Int64(1), Value::Int64(1)],
401 vec![
402 Value::String("a".into()),
403 Value::String("b".into()),
404 Value::String("a".into()),
405 ],
406 ],
407 &["id", "tag"],
408 );
409 let out = reducer(vec![0, 1]).execute(vec![chunk]).unwrap();
410 assert_eq!(out[0].size, 2, "(1,a) and (1,b) kept; (1,a) repeated dropped");
411 assert_eq!(out[0].get_value(0, 0), Some(Value::Int64(1)));
412 assert_eq!(out[0].get_value(1, 0), Some(Value::String("a".into())));
413 assert_eq!(out[0].get_value(1, 1), Some(Value::String("b".into())));
414 }
415
416 #[test]
417 fn test_multiplicity_reducer_nan_not_deduped() {
418 let chunk = make_chunk(&[vec![Value::Double(f64::NAN), Value::Double(f64::NAN)]], &["x"]);
421 let out = reducer(vec![0]).execute(vec![chunk]).unwrap();
422 assert_eq!(out[0].size, 2);
423 }
424}