1use crate::physical::common::store_value_in_vector;
2use crate::physical::scan_filter::PhysicalScan;
3use crate::physical::types::{OperatorResult, PhysicalOperatorExec};
4use crate::physical::write_ops::evaluate_expression_for_row;
5use akar_common::error::ProcessorError;
6use akar_common::types::{PhysicalTypeID, Value};
7use akar_common::vector::{DataChunk, ValueVector};
8use akar_storage::table::TableCatalog;
9use std::sync::Arc;
10
11pub struct PhysicalRecursiveExtend {
29 pub source_table_id: u64,
30 pub rel_table_ids: Vec<u64>,
31 pub lower_bound: u64,
32 pub upper_bound: u64,
33 pub direction: akar_common::enums::ExtendDirection,
34 pub semantic: akar_common::enums::PathSemantic,
35 pub table_catalog: Option<Arc<TableCatalog>>,
36 pub weight_property: Option<String>,
39 pub cost_output_name: Option<String>,
41}
42
43impl PhysicalOperatorExec for PhysicalRecursiveExtend {
44 fn operator_type(&self) -> &str {
45 "recursive_extend"
46 }
47
48 fn execute(&self, input: Vec<DataChunk>) -> OperatorResult {
49 use akar_common::enums::ExtendDirection;
50 use akar_common::enums::PathSemantic;
51 use akar_common::types::Value;
52 use akar_common::vector::ValueVector;
53 use std::collections::{HashMap, VecDeque};
54
55 let catalog = self
56 .table_catalog
57 .as_ref()
58 .ok_or_else(|| "No table catalog available for RecursiveExtend".to_string())?;
59
60 let mut fwd_adj: HashMap<u64, Vec<(u64, u64)>> = HashMap::new();
62 let mut rev_adj: HashMap<u64, Vec<(u64, u64)>> = HashMap::new();
63 let mut edge_weights: HashMap<u64, f64> = HashMap::new();
65 let is_weighted = self.weight_property.is_some();
67 let mut weight_col_idx: HashMap<u64, Option<usize>> = HashMap::new();
69
70 for &rel_table_id in &self.rel_table_ids {
71 if let Some(rel_table) = catalog.get_rel_table(rel_table_id) {
72 if let Some(ref wp) = self.weight_property {
74 let idx = rel_table.columns.iter().position(|c| c.name == *wp);
75 weight_col_idx.insert(rel_table_id, idx);
76 }
77
78 for (&src, neighbors) in rel_table.fwd_adj.iter() {
79 fwd_adj
80 .entry(src)
81 .or_default()
82 .extend(neighbors.iter().map(|(dst, edge_idx)| (*dst, *edge_idx as u64)));
83 if is_weighted && let Some(col_idx) = weight_col_idx.get(&rel_table_id).and_then(|&c| c) {
85 for &(_dst, edge_idx) in neighbors {
86 if let Some(weight_val) =
87 rel_table.properties.get(col_idx).and_then(|col| col.get(edge_idx))
88 {
89 let w = match weight_val {
90 Value::Int64(i) => *i as f64,
91 Value::Double(d) => *d,
92 Value::Float(f) => *f as f64,
93 Value::Int32(i) => *i as f64,
94 _ => 1.0, };
96 edge_weights.insert(edge_idx as u64, w);
97 }
98 }
99 }
100 }
101 for (&dst, neighbors) in rel_table.rev_adj.iter() {
102 rev_adj
103 .entry(dst)
104 .or_default()
105 .extend(neighbors.iter().map(|(src, edge_idx)| (*src, *edge_idx as u64)));
106 }
107 }
108 }
109
110 let source_offsets: Vec<i64> = if input.is_empty() || input[0].fields.is_empty() {
112 let mut all: Vec<i64> = fwd_adj
113 .keys()
114 .chain(rev_adj.keys())
115 .copied()
116 .map(|k| k as i64)
117 .collect();
118 all.sort();
119 all.dedup();
120 all
121 } else {
122 let field = &input[0].fields[0];
123 let num_rows = input[0].size;
124 let mut offsets = Vec::with_capacity(num_rows);
125 for i in 0..num_rows {
126 if !field.is_null(i) {
127 let offset = if let Some(Value::Int64(val)) = input[0].get_value(0, i) {
128 val
129 } else {
130 0
131 };
132 offsets.push(offset);
133 }
134 }
135 offsets
136 };
137
138 if source_offsets.is_empty() {
139 return Ok(vec![DataChunk::new(vec![], vec![])]);
140 }
141
142 let mut result_src: Vec<i64> = Vec::new();
144 let mut result_dst: Vec<i64> = Vec::new();
145 let mut result_len: Vec<i64> = Vec::new();
146 let mut result_cost: Vec<f64> = Vec::new(); let mut result_path_nodes: Vec<Vec<i64>> = Vec::new();
149 let mut result_path_edges: Vec<Vec<i64>> = Vec::new();
150
151 for &src in &source_offsets {
152 let src_u = src as u64;
153
154 if is_weighted {
155 use std::cmp::Reverse;
157 use std::collections::BinaryHeap;
158
159 const COST_PRECISION: i64 = 1000;
162
163 let cost_to_i64 = |c: f64| -> i64 { (c * COST_PRECISION as f64).round() as i64 };
165
166 let mut parents: HashMap<u64, (u64, u64, u64, f64)> = HashMap::new();
168 let mut pq: BinaryHeap<Reverse<(i64, u64)>> = BinaryHeap::new();
169
170 pq.push(Reverse((cost_to_i64(0.0), src_u)));
171 parents.insert(src_u, (u64::MAX, u64::MAX, 0, 0.0));
172
173 while let Some(Reverse((cur_cost_i64, node))) = pq.pop() {
174 let cur_cost = cur_cost_i64 as f64 / COST_PRECISION as f64;
175 let cur_depth = parents.get(&node).map(|&(_, _, d, _)| d).unwrap_or(0);
176
177 if let Some(&(_, _, _, best_cost)) = parents.get(&node)
179 && cur_cost > best_cost + 1e-9
180 {
181 continue;
182 }
183
184 if cur_depth >= self.upper_bound {
185 continue;
186 }
187
188 let neighbors: Vec<(u64, u64)> = match self.direction {
190 ExtendDirection::Fwd => fwd_adj.get(&node).cloned().unwrap_or_default(),
191 ExtendDirection::Bwd => rev_adj.get(&node).cloned().unwrap_or_default(),
192 ExtendDirection::Both => {
193 let mut nbrs = fwd_adj.get(&node).cloned().unwrap_or_default();
194 if let Some(bwd) = rev_adj.get(&node) {
195 nbrs.extend(bwd.iter().copied());
196 }
197 nbrs
198 }
199 };
200
201 for (nbr, edge_id) in neighbors {
202 let edge_w = edge_weights.get(&edge_id).copied().unwrap_or(1.0);
203 let new_cost = cur_cost + edge_w;
204 let new_depth = cur_depth + 1;
205
206 let should_visit = match parents.get(&nbr) {
207 Some(&(_, _, _, existing_cost)) => new_cost < existing_cost - 1e-9,
208 None => true,
209 };
210
211 if should_visit {
212 parents.insert(nbr, (node, edge_id, new_depth, new_cost));
213 pq.push(Reverse((cost_to_i64(new_cost), nbr)));
214 }
215 }
216 }
217
218 for (&node, &(_parent, _eid, depth, cost)) in &parents {
220 if depth < self.lower_bound || depth > self.upper_bound {
221 continue;
222 }
223 if depth == 0 && self.lower_bound > 0 {
224 continue;
225 }
226
227 result_src.push(src);
228 result_dst.push(node as i64);
229 result_len.push(depth as i64);
230 result_cost.push(cost);
231
232 let mut cur = node;
234 let mut temp_nodes = vec![node as i64];
235 let mut temp_edges = Vec::new();
236
237 while cur != src_u {
238 if let Some(&(parent, eid, _, _)) = parents.get(&cur) {
239 if parent == u64::MAX {
240 break;
241 }
242 temp_edges.push(eid as i64);
243 temp_nodes.push(parent as i64);
244 cur = parent;
245 } else {
246 break;
247 }
248 }
249
250 temp_nodes.reverse();
251 temp_edges.reverse();
252 let mut path_nodes = vec![src];
253 path_nodes.extend(temp_nodes);
254 result_path_nodes.push(path_nodes);
255 result_path_edges.push(temp_edges);
256 }
257 } else {
258 let mut queue = VecDeque::new();
260 let mut parents: HashMap<u64, (u64, u64, u64)> = HashMap::new();
262 queue.push_back((src_u, 0u64));
263 parents.insert(src_u, (u64::MAX, u64::MAX, 0));
264
265 let semantic = self.semantic;
266
267 while let Some((node, depth)) = queue.pop_front() {
268 if depth >= self.upper_bound {
269 continue;
270 }
271
272 let neighbors: Vec<(u64, u64)> = match self.direction {
273 ExtendDirection::Fwd => fwd_adj.get(&node).cloned().unwrap_or_default(),
274 ExtendDirection::Bwd => rev_adj.get(&node).cloned().unwrap_or_default(),
275 ExtendDirection::Both => {
276 let mut nbrs = fwd_adj.get(&node).cloned().unwrap_or_default();
277 if let Some(bwd) = rev_adj.get(&node) {
278 nbrs.extend(bwd.iter().copied());
279 }
280 nbrs
281 }
282 };
283
284 'neighbors: for (nbr, edge_id) in neighbors {
285 if parents.contains_key(&nbr) {
286 match semantic {
287 PathSemantic::Walk | PathSemantic::Acyclic => continue 'neighbors,
288 PathSemantic::Trail => {
289 let mut cur = node;
290 while let Some(&(p, eid, _)) = parents.get(&cur) {
291 if eid == edge_id {
292 continue 'neighbors;
293 }
294 if p == u64::MAX {
295 break;
296 }
297 cur = p;
298 }
299 }
300 }
301 }
302
303 let new_depth = depth + 1;
304 parents.insert(nbr, (node, edge_id, new_depth));
305 queue.push_back((nbr, new_depth));
306 }
307 }
308
309 for (&node, &(_parent_node, _edge_id, depth)) in &parents {
311 if depth < self.lower_bound || depth > self.upper_bound {
312 continue;
313 }
314 if depth == 0 && self.lower_bound > 0 {
315 continue;
316 }
317
318 result_src.push(src);
319 result_dst.push(node as i64);
320 result_len.push(depth as i64);
321
322 let mut cur = node;
324 let mut temp_nodes = vec![node as i64];
325 let mut temp_edges = Vec::new();
326
327 while cur != src_u {
328 if let Some(&(parent, eid, _)) = parents.get(&cur) {
329 if parent == u64::MAX {
330 break;
331 }
332 temp_edges.push(eid as i64);
333 temp_nodes.push(parent as i64);
334 cur = parent;
335 } else {
336 break;
337 }
338 }
339
340 temp_nodes.reverse();
341 temp_edges.reverse();
342 let mut path_nodes = vec![src];
343 path_nodes.extend(temp_nodes);
344 result_path_nodes.push(path_nodes);
345 result_path_edges.push(temp_edges);
346 }
347 }
348 }
349
350 let num_results = result_src.len();
352 if num_results == 0 {
353 return Ok(vec![DataChunk::new(vec![], vec![])]);
354 }
355
356 let mut src_v = ValueVector::new(akar_common::types::PhysicalTypeID::Int64, num_results);
358 let mut dst_v = ValueVector::new(akar_common::types::PhysicalTypeID::Int64, num_results);
359 let mut len_v = ValueVector::new(akar_common::types::PhysicalTypeID::Int64, num_results);
360
361 for i in 0..num_results {
362 let offset = i * 8;
363 src_v.data_mut()[offset..offset + 8].copy_from_slice(&result_src[i].to_le_bytes());
364 src_v.set_null(i, false);
365 dst_v.data_mut()[offset..offset + 8].copy_from_slice(&result_dst[i].to_le_bytes());
366 dst_v.set_null(i, false);
367 len_v.data_mut()[offset..offset + 8].copy_from_slice(&result_len[i].to_le_bytes());
368 len_v.set_null(i, false);
369 }
370 src_v.resize(num_results);
371 dst_v.resize(num_results);
372 len_v.resize(num_results);
373
374 let mut path_nodes_col: Vec<Value> = Vec::with_capacity(num_results);
377 let mut path_edges_col: Vec<Value> = Vec::with_capacity(num_results);
378
379 for i in 0..num_results {
380 let node_vals: Vec<Value> = result_path_nodes[i].iter().map(|&n| Value::Int64(n)).collect();
382 path_nodes_col.push(Value::List(node_vals));
383 let edge_vals: Vec<Value> = result_path_edges[i].iter().map(|&e| Value::Int64(e)).collect();
385 path_edges_col.push(Value::List(edge_vals));
386 }
387
388 let mut path_nodes_v = ValueVector::new(akar_common::types::PhysicalTypeID::List, num_results);
390 let mut path_edges_v = ValueVector::new(akar_common::types::PhysicalTypeID::List, num_results);
391
392 for (i, val) in path_nodes_col.iter().enumerate() {
393 path_nodes_v.set_value(i, val)?;
394 }
395 for (i, val) in path_edges_col.iter().enumerate() {
396 path_edges_v.set_value(i, val)?;
397 }
398
399 let has_cost = is_weighted;
401
402 if has_cost {
403 let mut cost_v = ValueVector::new(akar_common::types::PhysicalTypeID::Double, num_results);
404 for (i, cost) in result_cost.iter().enumerate().take(num_results) {
405 let offset = i * 8;
406 cost_v.data_mut()[offset..offset + 8].copy_from_slice(&cost.to_le_bytes());
407 cost_v.set_null(i, false);
408 }
409 cost_v.resize(num_results);
410
411 Ok(vec![DataChunk {
412 fields: vec![
413 akar_common::arrow_vector::ArrowVector::from_legacy(&src_v).array,
414 akar_common::arrow_vector::ArrowVector::from_legacy(&dst_v).array,
415 akar_common::arrow_vector::ArrowVector::from_legacy(&len_v).array,
416 akar_common::arrow_vector::ArrowVector::from_legacy(&path_nodes_v).array,
417 akar_common::arrow_vector::ArrowVector::from_legacy(&path_edges_v).array,
418 akar_common::arrow_vector::ArrowVector::from_legacy(&cost_v).array,
419 ],
420 field_types: vec![
421 src_v.physical_type(),
422 dst_v.physical_type(),
423 len_v.physical_type(),
424 path_nodes_v.physical_type(),
425 path_edges_v.physical_type(),
426 cost_v.physical_type(),
427 ],
428 size: num_results,
429 field_names: vec![],
430 sel_vector: None,
431 }])
432 } else {
433 Ok(vec![DataChunk {
434 fields: vec![
435 akar_common::arrow_vector::ArrowVector::from_legacy(&src_v).array,
436 akar_common::arrow_vector::ArrowVector::from_legacy(&dst_v).array,
437 akar_common::arrow_vector::ArrowVector::from_legacy(&len_v).array,
438 akar_common::arrow_vector::ArrowVector::from_legacy(&path_nodes_v).array,
439 akar_common::arrow_vector::ArrowVector::from_legacy(&path_edges_v).array,
440 ],
441 field_types: vec![
442 src_v.physical_type(),
443 dst_v.physical_type(),
444 len_v.physical_type(),
445 path_nodes_v.physical_type(),
446 path_edges_v.physical_type(),
447 ],
448 size: num_results,
449 field_names: vec![],
450 sel_vector: None,
451 }])
452 }
453 }
454}
455
456pub struct PhysicalCreateNode {
457 pub table_name: String,
458 pub table_id: u64,
459 pub out_var_name: String,
460 pub properties: Vec<(String, akar_parser::ast::Expression)>,
461 pub table_catalog: Arc<TableCatalog>,
462}
463
464impl PhysicalCreateNode {
465 pub fn execute(&self, input: Vec<DataChunk>) -> Result<Vec<DataChunk>, ProcessorError> {
466 if input.is_empty() {
467 return Ok(input);
468 }
469
470 let mut table = self
471 .table_catalog
472 .get_node_table_by_name_mut(&self.table_name)
473 .ok_or_else(|| format!("Node table {} not found", self.table_name))?;
474
475 let mut output = Vec::with_capacity(input.len());
477
478 for mut chunk in input {
479 let mut node_ids = ValueVector::new(akar_common::types::PhysicalTypeID::Int64, chunk.size);
480
481 for i in 0..chunk.size {
482 let mut values = vec![akar_common::types::Value::Null; table.columns.len()];
483 for (prop_name, prop_expr) in &self.properties {
484 if let Some(col_idx) = table.columns.iter().position(|c| c.name == *prop_name) {
485 values[col_idx] = evaluate_expression_for_row(prop_expr, &chunk, i);
486 }
487 }
488
489 let row_offset = table.insert_row(values)?;
490 node_ids.data_mut()[i * 8..(i + 1) * 8].copy_from_slice(&(row_offset as i64).to_le_bytes());
491 node_ids.set_null(i, false);
492 }
493 node_ids.resize(chunk.size);
494
495 chunk
496 .fields
497 .push(akar_common::arrow_vector::ArrowVector::from_legacy(&node_ids).array);
498 chunk.field_types.push(akar_common::types::PhysicalTypeID::List);
499 chunk.field_names.push(self.out_var_name.clone());
500 output.push(chunk);
501 }
502
503 Ok(output)
504 }
505}
506
507pub struct PhysicalCreateRel {
508 pub table_name: String,
509 pub table_id: u64,
510 pub src_node_name: String,
511 pub dst_node_name: String,
512 pub properties: Vec<(String, akar_parser::ast::Expression)>,
513 pub table_catalog: Arc<TableCatalog>,
514}
515
516impl PhysicalCreateRel {
517 pub fn execute(&self, input: Vec<DataChunk>) -> Result<Vec<DataChunk>, ProcessorError> {
518 if input.is_empty() {
519 return Ok(input);
520 }
521
522 let mut table = self
523 .table_catalog
524 .get_rel_table_by_name_mut(&self.table_name)
525 .ok_or_else(|| format!("Rel table {} not found", self.table_name))?;
526
527 let mut output = Vec::with_capacity(input.len());
528
529 for chunk in input {
530 let src_name_id = format!("{}.{}", self.src_node_name, "_id");
531 let src_name_pk = format!("{}.{}", self.src_node_name, "id");
532 let src_idx = chunk
533 .field_names
534 .iter()
535 .position(|name| name == &src_name_id)
536 .or_else(|| chunk.field_names.iter().position(|name| name == &self.src_node_name))
537 .or_else(|| chunk.field_names.iter().position(|name| name == &src_name_pk))
538 .ok_or_else(|| format!("Source node variable {} not found", self.src_node_name))?;
539
540 let dst_name_id = format!("{}.{}", self.dst_node_name, "_id");
541 let dst_name_pk = format!("{}.{}", self.dst_node_name, "id");
542 let dst_idx = chunk
543 .field_names
544 .iter()
545 .position(|name| name == &dst_name_id)
546 .or_else(|| chunk.field_names.iter().position(|name| name == &self.dst_node_name))
547 .or_else(|| chunk.field_names.iter().position(|name| name == &dst_name_pk))
548 .ok_or_else(|| format!("Destination node variable {} not found", self.dst_node_name))?;
549
550 let src_vec = &chunk.fields[src_idx];
551 let dst_vec = &chunk.fields[dst_idx];
552
553 let mut inserted = 0;
554 for i in 0..chunk.size {
555 if src_vec.is_null(i) || dst_vec.is_null(i) {
556 continue; }
558
559 let mut src_bytes = [0u8; 8];
560 src_bytes.copy_from_slice(&src_vec.to_data().buffers()[0].as_slice()[i * 8..(i + 1) * 8]);
561 let src_id = i64::from_le_bytes(src_bytes) as u64;
562
563 let mut dst_bytes = [0u8; 8];
564 dst_bytes.copy_from_slice(&dst_vec.to_data().buffers()[0].as_slice()[i * 8..(i + 1) * 8]);
565 let dst_id = i64::from_le_bytes(dst_bytes) as u64;
566
567 let mut values = vec![akar_common::types::Value::Null; table.columns.len()];
568 for (prop_name, prop_expr) in &self.properties {
569 if let Some(col_idx) = table.columns.iter().position(|c| c.name == *prop_name) {
570 values[col_idx] = evaluate_expression_for_row(prop_expr, &chunk, i);
571 }
572 }
573
574 table.insert_rel(src_id, dst_id, values)?;
575 inserted += 1;
576 }
577 tracing::debug!(
578 "PhysicalCreateRel inserted {} relationships from chunk of size {}",
579 inserted,
580 chunk.size
581 );
582
583 output.push(chunk);
584 }
585
586 Ok(output)
587 }
588}
589
590pub struct PhysicalExtend {
599 pub rel_table_name: String,
601 pub rel_table_id: u64,
603 pub bound_node_var: String,
605 pub direction: akar_parser::ast::EdgeDirection,
607 pub dst_node_var: String,
609 pub dst_table_name: String,
611 pub dst_table_id: u64,
613 pub table_catalog: Arc<TableCatalog>,
615}
616
617impl PhysicalExtend {
618 pub fn execute(&self, input: Vec<DataChunk>) -> Result<Vec<DataChunk>, ProcessorError> {
619 if input.is_empty() || input.iter().all(|c| c.size == 0) {
620 return Ok(input);
621 }
622
623 let (fwd_adj, rev_adj, rel_props, rel_cols) = {
625 let rel_table = self
626 .table_catalog
627 .get_rel_table_by_name(&self.rel_table_name)
628 .ok_or_else(|| format!("Rel table {} not found", self.rel_table_name))?;
629 let fwd = rel_table.fwd_adj.clone();
630 let rev = rel_table.rev_adj.clone();
631 let props = rel_table.properties.clone();
632 let cols = rel_table.columns.clone();
633 (fwd, rev, props, cols)
634 };
635
636 let (dest_data, dest_cols, dest_pk_col) = {
638 let dest_table = self
639 .table_catalog
640 .get_node_table_by_name(&self.dst_table_name)
641 .ok_or_else(|| format!("Node table {} not found", self.dst_table_name))?;
642 let data = dest_table.to_column_major_data();
643 let cols = dest_table.columns.clone();
644 let pk = dest_table.primary_key_column;
645 (data, cols, pk)
646 };
647
648 let pk_to_row: std::collections::HashMap<u64, usize> = if dest_pk_col < dest_data.len() {
650 dest_data[dest_pk_col]
651 .iter()
652 .enumerate()
653 .filter_map(|(row, val)| {
654 if let Value::Int64(id) = val {
655 Some((*id as u64, row))
656 } else {
657 None
658 }
659 })
660 .collect()
661 } else {
662 std::collections::HashMap::new()
663 };
664
665 let mut output = Vec::with_capacity(input.len());
666
667 for chunk in input {
668 let bound_name_id = format!("{}.{}", self.bound_node_var, "_id");
670 let bound_name_pk = format!("{}.{}", self.bound_node_var, "id");
671 let bound_idx = chunk
672 .field_names
673 .iter()
674 .position(|name| name == &bound_name_id)
675 .or_else(|| chunk.field_names.iter().position(|name| name == &self.bound_node_var))
676 .or_else(|| chunk.field_names.iter().position(|name| name == &bound_name_pk))
677 .ok_or_else(|| {
678 format!(
679 "Bound node variable {} not found in Extend input. Available fields: {:?}",
680 self.bound_node_var, chunk.field_names
681 )
682 })?;
683
684 let mut total_rows = 0;
686 let mut row_mappings: Vec<(usize, u64, usize)> = Vec::new(); for i in 0..chunk.size {
689 if chunk.fields[bound_idx].is_null(i) {
690 continue;
691 }
692 let src_id = if let Some(akar_common::types::Value::Int64(val)) = chunk.get_value(bound_idx, i) {
693 val as u64
694 } else {
695 continue;
696 };
697
698 let edges: Vec<(u64, usize)> = match self.direction {
699 akar_parser::ast::EdgeDirection::LeftToRight => fwd_adj.get(&src_id).cloned().unwrap_or_default(),
700 akar_parser::ast::EdgeDirection::RightToLeft => rev_adj.get(&src_id).cloned().unwrap_or_default(),
701 akar_parser::ast::EdgeDirection::Both => {
702 let mut all = fwd_adj.get(&src_id).cloned().unwrap_or_default();
703 if let Some(rev) = rev_adj.get(&src_id) {
704 all.extend(rev.iter().cloned());
705 }
706 all
707 }
708 };
709
710 for &(dst_offset, edge_idx) in &edges {
711 if !pk_to_row.contains_key(&dst_offset)
712 && dst_offset as usize >= dest_data.first().map(|c| c.len()).unwrap_or(0)
713 {
714 continue;
715 }
716 total_rows += 1;
717 row_mappings.push((i, dst_offset, edge_idx));
718 }
719 }
720
721 if total_rows == 0 {
722 output.push(DataChunk::new(vec![], vec![]));
723 continue;
724 }
725
726 let num_input_fields = chunk.fields.len();
729 let num_rel_cols = rel_cols.len();
730 let num_dest_cols = dest_cols.len();
731 let num_out_cols = num_input_fields + num_rel_cols + num_dest_cols;
732
733 let mut out_data: Vec<Vec<Value>> = vec![Vec::with_capacity(total_rows); num_out_cols];
735
736 for &(input_row, dst_offset, edge_idx) in &row_mappings {
737 for col in 0..num_input_fields {
739 let val = chunk.get_value(col, input_row).unwrap_or(Value::Null);
740 out_data[col].push(val);
741 }
742 for col in 0..num_rel_cols {
744 let val = rel_props
745 .get(col)
746 .and_then(|c| c.get(edge_idx))
747 .cloned()
748 .unwrap_or(Value::Null);
749 out_data[num_input_fields + col].push(val);
750 }
751 let dest_row = pk_to_row.get(&dst_offset).copied();
753 for col in 0..num_dest_cols {
754 let val = dest_row
755 .and_then(|r| dest_data.get(col).and_then(|c| c.get(r)))
756 .cloned()
757 .unwrap_or_else(|| {
758 dest_data
759 .get(col)
760 .and_then(|c| c.get(dst_offset as usize))
761 .cloned()
762 .unwrap_or(Value::Null)
763 });
764 out_data[num_input_fields + num_rel_cols + col].push(val);
765 }
766 }
767
768 let mut fields = Vec::with_capacity(num_out_cols);
770 let mut field_names = Vec::with_capacity(num_out_cols);
771
772 for col in 0..num_input_fields {
774 let phys_type = chunk.field_types[col];
775 let mut v = ValueVector::new(phys_type, total_rows);
776 v.resize(total_rows);
777 for row in 0..total_rows {
778 store_value_in_vector(&mut v, row, &out_data[col][row])?;
779 }
780 fields.push(v);
781 if col < chunk.field_names.len() {
782 field_names.push(chunk.field_names[col].clone());
783 } else {
784 field_names.push(format!("field_{}", col));
785 }
786 }
787
788 for col in 0..num_rel_cols {
790 let phys_type = if col < rel_cols.len() {
791 PhysicalScan::logical_to_physical(&rel_cols[col].logical_type)
792 } else {
793 PhysicalTypeID::Int64
794 };
795 let mut v = ValueVector::new(phys_type, total_rows);
796 v.resize(total_rows);
797 for row in 0..total_rows {
798 store_value_in_vector(&mut v, row, &out_data[num_input_fields + col][row])?;
799 }
800 fields.push(v);
801 let rel_prefix = &self.rel_table_name;
802 let col_name = rel_cols.get(col).map(|c| c.name.as_str()).unwrap_or("");
803 field_names.push(format!("{}.{}", rel_prefix, col_name));
804 }
805
806 for col in 0..num_dest_cols {
808 let phys_type = if col < dest_cols.len() {
809 PhysicalScan::logical_to_physical(&dest_cols[col].logical_type)
810 } else {
811 PhysicalTypeID::Int64
812 };
813 let mut v = ValueVector::new(phys_type, total_rows);
814 v.resize(total_rows);
815 for row in 0..total_rows {
816 store_value_in_vector(&mut v, row, &out_data[num_input_fields + num_rel_cols + col][row])?;
817 }
818 fields.push(v);
819 let prefix = &self.dst_node_var;
820 let col_name = dest_cols.get(col).map(|c| c.name.as_str()).unwrap_or("");
821 field_names.push(format!("{}.{}", prefix, col_name));
822 }
823
824 let arrow_fields = fields
825 .iter()
826 .map(|v| akar_common::arrow_vector::ArrowVector::from_legacy(v).array)
827 .collect::<Vec<_>>();
828 let arrow_field_types = fields.iter().map(|v| v.physical_type()).collect::<Vec<_>>();
829 output.push(DataChunk {
830 fields: arrow_fields,
831 field_types: arrow_field_types,
832 size: total_rows,
833 field_names,
834 sel_vector: None,
835 });
836 }
837
838 Ok(output)
839 }
840}