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