1use super::ExecutionContext;
2use crate::physical_operator::*;
3use akar_common::error::ProcessorError;
4use akar_common::vector::DataChunk;
5use akar_planner::logical_operator::LogicalOperator;
6
7pub fn map_and_execute_update(
8 op: &LogicalOperator,
9 current_input: Vec<DataChunk>,
10 ctx: &mut ExecutionContext,
11) -> Result<Vec<DataChunk>, ProcessorError> {
12 match op {
13 LogicalOperator::Set(sl) => {
14 let table_catalog = ctx
15 .table_catalog
16 .clone()
17 .ok_or_else(|| "No table catalog available for SET".to_string())?;
18
19 let set_op = PhysicalSet {
20 table_name: sl.table_name.clone(),
21 table_id: sl.table_id,
22 is_node: sl.is_node,
23 items: sl.items.clone(),
24 table_catalog,
25 txn_id: ctx.txn_id,
26 undo_sink: Some(ctx.processor.undo_sink()),
27 function_registry: ctx.function_registry.clone(),
28 emit_count: sl.emit_count,
29 };
30 let result = set_op.execute(current_input)?;
31 record_set_writes(sl.table_id, &result, ctx);
33 Ok(result)
34 }
35 LogicalOperator::Delete(dl) => {
36 let table_catalog = ctx
37 .table_catalog
38 .clone()
39 .ok_or_else(|| "No table catalog available for DELETE".to_string())?;
40
41 let delete_op = PhysicalDelete {
42 table_name: dl.table_name.clone(),
43 table_id: dl.table_id,
44 primary_key_column: dl.primary_key_column.clone(),
45 is_node: dl.is_node,
46 detach: dl.detach,
47 row_indices: Vec::new(),
48 table_catalog,
49 txn_id: ctx.txn_id,
50 undo_sink: Some(ctx.processor.undo_sink()),
51 };
52 let result = delete_op.execute(current_input)?;
53 record_delete_writes(dl.table_id, &result, ctx);
55 Ok(result)
56 }
57 LogicalOperator::CreateNode(cn) => {
58 let table_catalog = ctx
59 .table_catalog
60 .clone()
61 .ok_or_else(|| "No table catalog available for CREATE".to_string())?;
62
63 let create_node_op = PhysicalInsertNode {
64 table_name: cn.table_name.clone(),
65 table_id: cn.table_id,
66 out_var_name: cn.out_var_name.clone(),
67 properties: cn.properties.clone(),
68 table_catalog,
69 txn_id: ctx.txn_id,
70 undo_sink: Some(ctx.processor.undo_sink()),
71 };
72 let result = create_node_op.execute(current_input)?;
73 record_insert_writes(cn.table_id, &result, ctx);
75 Ok(result)
76 }
77 LogicalOperator::CreateRel(cr) => {
78 let table_catalog = ctx
79 .table_catalog
80 .clone()
81 .ok_or_else(|| "No table catalog available for CREATE".to_string())?;
82
83 let create_rel_op = PhysicalInsertRel {
84 table_name: cr.table_name.clone(),
85 table_id: cr.table_id,
86 src_node_name: cr.src_node_name.clone(),
87 dst_node_name: cr.dst_node_name.clone(),
88 properties: cr.properties.clone(),
89 table_catalog,
90 txn_id: ctx.txn_id,
91 undo_sink: Some(ctx.processor.undo_sink()),
92 };
93 let result = create_rel_op.execute(current_input)?;
94 record_insert_writes(cr.table_id, &result, ctx);
96 Ok(result)
97 }
98 LogicalOperator::Extend(ex) => {
99 let table_catalog = ctx
100 .table_catalog
101 .clone()
102 .ok_or_else(|| "No table catalog available for Extend".to_string())?;
103
104 let extend_op = PhysicalExtend {
105 rel_table_name: ex.rel_table_name.clone(),
106 rel_table_id: ex.rel_table_id,
107 rel_var: ex.rel_var.clone(),
108 bound_node_var: ex.bound_node_var.clone(),
109 direction: ex.direction.clone(),
110 dst_node_var: ex.dst_node_var.clone(),
111 dst_table_name: ex.dst_table_name.clone(),
112 dst_table_id: ex.dst_table_id,
113 table_catalog,
114 };
115 let result = extend_op.execute(current_input)?;
116 record_insert_writes(ex.rel_table_id, &result, ctx);
118 Ok(result)
119 }
120 LogicalOperator::OptionalExtend(oe) => {
121 let table_catalog = ctx
122 .table_catalog
123 .clone()
124 .ok_or_else(|| "No table catalog available for OptionalExtend".to_string())?;
125
126 let input = if oe.children.is_empty() {
127 current_input
128 } else {
129 ctx.execute_children(&oe.children)?
130 };
131
132 let optional_extend_op = PhysicalOptionalExtend {
133 rel_table_name: oe.rel_table_name.clone(),
134 rel_table_id: oe.rel_table_id,
135 rel_var: oe.rel_var.clone(),
136 src_node_var: oe.src_node_var.clone(),
137 dst_node_var: oe.dst_node_var.clone(),
138 direction: oe.direction.clone(),
139 table_catalog,
140 };
141 Ok(optional_extend_op.execute(input)?)
142 }
143 LogicalOperator::Merge(m) => {
144 let table_catalog = ctx
145 .table_catalog
146 .clone()
147 .ok_or_else(|| "No table catalog available for MERGE".to_string())?;
148
149 let mut on_match_ops = Vec::new();
150 for set_item in &m.on_match {
151 on_match_ops.push(PhysicalSet {
152 table_name: set_item.table_name.clone(),
153 table_id: set_item.table_id,
154 is_node: set_item.is_node,
155 items: set_item.items.clone(),
156 table_catalog: table_catalog.clone(),
157 txn_id: ctx.txn_id,
158 undo_sink: Some(ctx.processor.undo_sink()),
159 function_registry: ctx.function_registry.clone(),
160 emit_count: false,
161 });
162 }
163
164 let mut on_create_ops = Vec::new();
165 for set_item in &m.on_create {
166 on_create_ops.push(PhysicalSet {
167 table_name: set_item.table_name.clone(),
168 table_id: set_item.table_id,
169 is_node: set_item.is_node,
170 items: set_item.items.clone(),
171 table_catalog: table_catalog.clone(),
172 txn_id: ctx.txn_id,
173 undo_sink: Some(ctx.processor.undo_sink()),
174 function_registry: ctx.function_registry.clone(),
175 emit_count: false,
176 });
177 }
178
179 let merge_op = PhysicalMerge {
180 table_name: m.table_name.clone(),
181 table_id: m.table_id,
182 properties: m.properties.clone(),
183 on_match: on_match_ops,
184 on_create: on_create_ops,
185 table_catalog,
186 txn_id: ctx.txn_id,
187 undo_sink: Some(ctx.processor.undo_sink()),
188 };
189 let result = merge_op.execute(current_input)?;
190 record_insert_writes(m.table_id, &result, ctx);
192 Ok(result)
193 }
194 LogicalOperator::MergeRel(mr) => {
195 let table_catalog = ctx
196 .table_catalog
197 .clone()
198 .ok_or_else(|| "No table catalog available for MERGE".to_string())?;
199
200 let mut on_match_ops = Vec::new();
201 for set_item in &mr.on_match {
202 on_match_ops.push(PhysicalSet {
203 table_name: set_item.table_name.clone(),
204 table_id: set_item.table_id,
205 is_node: set_item.is_node,
206 items: set_item.items.clone(),
207 table_catalog: table_catalog.clone(),
208 txn_id: ctx.txn_id,
209 undo_sink: Some(ctx.processor.undo_sink()),
210 function_registry: ctx.function_registry.clone(),
211 emit_count: false,
212 });
213 }
214
215 let mut on_create_ops = Vec::new();
216 for set_item in &mr.on_create {
217 on_create_ops.push(PhysicalSet {
218 table_name: set_item.table_name.clone(),
219 table_id: set_item.table_id,
220 is_node: set_item.is_node,
221 items: set_item.items.clone(),
222 table_catalog: table_catalog.clone(),
223 txn_id: ctx.txn_id,
224 undo_sink: Some(ctx.processor.undo_sink()),
225 function_registry: ctx.function_registry.clone(),
226 emit_count: false,
227 });
228 }
229
230 let merge_rel_op = PhysicalMergeRel {
231 rel_table_name: mr.rel_table_name.clone(),
232 rel_table_id: mr.rel_table_id,
233 edge_var: mr.edge_var.clone(),
234 src_node_var: mr.src_node_var.clone(),
235 dst_node_var: mr.dst_node_var.clone(),
236 direction: akar_parser::ast::EdgeDirection::LeftToRight,
237 properties: mr.properties.clone(),
238 on_match: on_match_ops,
239 on_create: on_create_ops,
240 table_catalog,
241 txn_id: ctx.txn_id,
242 undo_sink: Some(ctx.processor.undo_sink()),
243 };
244 let result = merge_rel_op.execute(current_input)?;
245 record_insert_writes(mr.rel_table_id, &result, ctx);
247 Ok(result)
248 }
249 LogicalOperator::CopyFrom(cf) => {
250 let table_catalog = ctx
251 .table_catalog
252 .clone()
253 .ok_or_else(|| "No table catalog available for COPY FROM".to_string())?;
254
255 let columns = if let Some(node_table) = table_catalog.get_node_table_by_name(&cf.table_name) {
257 node_table.columns.clone()
258 } else if let Some(rel_table) = table_catalog.get_rel_table_by_name(&cf.table_name) {
259 rel_table.columns.clone()
260 } else {
261 return Err(format!("Table '{}' not found in storage catalog", cf.table_name).into());
262 };
263
264 let copy_op = PhysicalCopyFrom {
265 table_name: cf.table_name.clone(),
266 table_id: cf.table_id,
267 file_path: cf.file_path.clone(),
268 columns,
269 options: cf.options.clone(),
270 table_catalog,
271 vfs: ctx
272 .vfs
273 .clone()
274 .ok_or_else(|| "VFS not initialized in processor".to_string())?,
275 txn_id: ctx.txn_id,
276 undo_sink: Some(ctx.processor.undo_sink()),
277 };
278 let result = copy_op.execute(current_input)?;
279 record_insert_writes(cf.table_id, &result, ctx);
281 Ok(result)
282 }
283 LogicalOperator::BatchInsert(bi) => {
284 let table_catalog = ctx
285 .table_catalog
286 .clone()
287 .ok_or_else(|| "No table catalog available for BATCH INSERT".to_string())?;
288
289 let batch_op = PhysicalBatchInsert {
290 table_name: bi.table_name.clone(),
291 table_id: bi.table_id,
292 rows: bi.rows.clone(),
293 table_catalog,
294 txn_id: ctx.txn_id,
295 undo_sink: Some(ctx.processor.undo_sink()),
296 };
297 let result = batch_op.execute(current_input)?;
298 record_insert_writes(bi.table_id, &result, ctx);
300 Ok(result)
301 }
302 LogicalOperator::Insert(i) => {
303 let exec = crate::physical::misc::PhysicalInsert {
304 table_name: i.table_name.clone(),
305 table_id: i.table_id,
306 columns: i.columns.clone(),
307 values: i.values.clone(),
308 table_catalog: ctx.table_catalog.clone().unwrap(),
309 txn_id: ctx.txn_id,
310 undo_sink: Some(ctx.processor.undo_sink()),
311 };
312 let result = exec.execute(current_input)?;
313 record_insert_writes(i.table_id, &result, ctx);
315 Ok(result)
316 }
317 _ => Err(format!("Not an update operator: {:?}", op).into()),
318 }
319}
320
321fn record_set_writes(table_id: u64, result: &[DataChunk], ctx: &mut ExecutionContext) {
325 if let Some(chunk) = result.first() {
326 let id_col = chunk
327 .field_names
328 .iter()
329 .position(|n| n == "_id" || n.ends_with("._id"))
330 .unwrap_or(0);
331 for row in 0..chunk.size {
332 if !chunk.fields.is_empty() {
333 if let Some(akar_common::types::Value::Int64(row_idx)) = chunk.get_value(id_col, row) {
334 ctx.written_rows.push((table_id, row_idx as u64));
335 }
336 }
337 }
338 }
339}
340
341fn record_delete_writes(table_id: u64, result: &[DataChunk], ctx: &mut ExecutionContext) {
344 if let Some(chunk) = result.first() {
345 for row in 0..chunk.size {
346 if !chunk.fields.is_empty() {
347 if let Some(akar_common::types::Value::Int64(row_idx)) = chunk.get_value(0, row) {
348 ctx.written_rows.push((table_id, row_idx as u64));
349 }
350 }
351 }
352 }
353}
354
355fn record_insert_writes(table_id: u64, result: &[DataChunk], ctx: &mut ExecutionContext) {
361 if let Some(chunk) = result.first() {
362 if let Some(id_col) = chunk.field_names.iter().position(|n| n == "_id" || n.ends_with("._id")) {
365 for row in 0..chunk.size {
366 if let Some(akar_common::types::Value::Int64(row_id)) = chunk.get_value(id_col, row) {
367 ctx.written_rows.push((table_id, row_id as u64));
368 }
369 }
370 } else if chunk.fields.len() > 1 {
371 for row in 0..chunk.fields[1].len() {
372 if let Some(akar_common::types::Value::Int64(row_id)) = chunk.get_value(1, row) {
373 ctx.written_rows.push((table_id, row_id as u64));
374 }
375 }
376 }
377 }
378}