akar_processor/physical/write_ops/
packedextend.rs1use crate::physical::types::{OperatorResult, PhysicalOperatorExec};
3use akar_common::types::Value;
4use akar_common::vector::{DataChunk, ValueVector};
5use akar_storage::table::TableCatalog;
6use std::sync::Arc;
7
8pub struct PhysicalPackedExtend {
17 pub rel_table_name: String,
18 pub rel_table_id: u64,
19 pub bound_node_var: String,
20 pub direction: akar_parser::ast::EdgeDirection,
21 pub dst_node_var: String,
22 pub table_catalog: Arc<TableCatalog>,
23}
24
25impl PhysicalOperatorExec for PhysicalPackedExtend {
26 fn operator_type(&self) -> &str {
27 "packed_extend"
28 }
29
30 fn execute(&self, input: Vec<DataChunk>) -> OperatorResult {
31 if input.is_empty() || input.iter().all(|c| c.size == 0) {
32 return Ok(input);
33 }
34
35 let rel_table = self
36 .table_catalog
37 .get_rel_table_by_name(&self.rel_table_name)
38 .ok_or_else(|| format!("Rel table {} not found", self.rel_table_name))?;
39
40 let mut output_chunks = Vec::new();
41
42 for chunk in input {
43 if chunk.size == 0 {
44 continue;
45 }
46
47 let bound_idx = chunk
49 .field_names
50 .iter()
51 .position(|name| name == &self.bound_node_var)
52 .unwrap_or(0);
53
54 let mut per_src_neighbors: Vec<Vec<u64>> = Vec::with_capacity(chunk.size);
56 let mut total_output_rows: usize = 0;
57
58 for i in 0..chunk.size {
59 if chunk.fields[bound_idx].is_null(i) {
60 per_src_neighbors.push(Vec::new());
61 continue;
62 }
63
64 let src_id = match chunk.get_value(bound_idx, i) {
65 Some(Value::Int64(id)) => id as u64,
66 Some(Value::UInt64(id)) => id,
67 _ => {
68 per_src_neighbors.push(Vec::new());
69 continue;
70 }
71 };
72
73 let neighbors = self.fetch_neighbors(&rel_table, src_id);
74 total_output_rows += neighbors.len();
75 per_src_neighbors.push(neighbors);
76 }
77
78 if total_output_rows == 0 {
79 continue;
80 }
81
82 let num_input_cols = chunk.fields.len();
84 let mut out_fields: Vec<arrow::array::ArrayRef> = Vec::with_capacity(num_input_cols + 1);
85 let mut out_field_types: Vec<akar_common::types::PhysicalTypeID> = Vec::with_capacity(num_input_cols + 1);
86
87 for col_idx in 0..num_input_cols {
89 let phys_type = chunk.field_types[col_idx];
90 let mut v = ValueVector::new(phys_type, total_output_rows);
91 v.resize(total_output_rows);
92
93 let mut out_pos = 0;
94 for (src_row, neighbors) in per_src_neighbors.iter().enumerate() {
95 if neighbors.is_empty() {
96 continue;
97 }
98 let is_null = chunk.fields[col_idx].is_null(src_row);
100 let val = chunk.get_value(col_idx, src_row);
101 for _ in neighbors {
102 if is_null {
103 v.set_null(out_pos, true);
104 } else if let Some(ref val) = val {
105 crate::physical::common::store_value_in_vector(&mut v, out_pos, val)?;
106 }
107 out_pos += 1;
108 }
109 }
110 out_fields.push(akar_common::arrow_vector::ArrowVector::from_legacy(&v).array);
111 out_field_types.push(phys_type);
112 }
113
114 let mut dst_field = ValueVector::new(akar_common::types::PhysicalTypeID::Int64, total_output_rows);
116 dst_field.resize(total_output_rows);
117
118 let mut out_pos = 0;
119 for neighbors in &per_src_neighbors {
120 for &dst_id in neighbors {
121 crate::physical::common::store_value_in_vector(
122 &mut dst_field,
123 out_pos,
124 &Value::Int64(dst_id as i64),
125 )?;
126 out_pos += 1;
127 }
128 }
129 out_fields.push(akar_common::arrow_vector::ArrowVector::from_legacy(&dst_field).array);
130 out_field_types.push(akar_common::types::PhysicalTypeID::Int64);
131
132 let mut new_names = chunk.field_names.clone();
133 new_names.push(self.dst_node_var.clone());
134
135 output_chunks.push(DataChunk {
136 fields: out_fields,
137 field_types: out_field_types,
138 size: total_output_rows,
139 field_names: new_names,
140 sel_vector: None,
141 });
142 }
143
144 if output_chunks.is_empty() {
145 Ok(vec![DataChunk {
146 fields: vec![],
147 field_types: vec![],
148 size: 0,
149 field_names: vec![],
150 sel_vector: None,
151 }])
152 } else {
153 Ok(output_chunks)
154 }
155 }
156}
157
158impl PhysicalPackedExtend {
159 fn fetch_neighbors(&self, rel_table: &akar_storage::table::RelTable, src_id: u64) -> Vec<u64> {
160 if let Some(csr) = &rel_table.csr_index {
161 let is_fwd = matches!(self.direction, akar_parser::ast::EdgeDirection::LeftToRight);
162 csr.get_neighbors(src_id, is_fwd).unwrap_or_default()
163 } else {
164 match self.direction {
165 akar_parser::ast::EdgeDirection::LeftToRight => rel_table
166 .get_outgoing_edges(src_id)
167 .into_iter()
168 .map(|(dst, _)| dst)
169 .collect(),
170 akar_parser::ast::EdgeDirection::RightToLeft => rel_table
171 .get_incoming_edges(src_id)
172 .into_iter()
173 .map(|(dst, _)| dst)
174 .collect(),
175 akar_parser::ast::EdgeDirection::Both => {
176 let mut n: Vec<u64> = rel_table
177 .get_outgoing_edges(src_id)
178 .into_iter()
179 .map(|(dst, _)| dst)
180 .collect();
181 n.extend(rel_table.get_incoming_edges(src_id).into_iter().map(|(dst, _)| dst));
182 n
183 }
184 }
185 }
186 }
187}