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 out_var_name: cr.out_var_name.clone(),
92 properties: cr.properties.clone(),
93 table_catalog,
94 txn_id: ctx.txn_id,
95 undo_sink: Some(ctx.processor.undo_sink()),
96 wal_sink: Some(ctx.processor.wal_sink()),
97 };
98 let result = create_rel_op.execute(current_input)?;
99 record_insert_writes(cr.table_id, &result, ctx);
101 Ok(result)
102 }
103 LogicalOperator::Extend(ex) => {
104 let table_catalog = ctx
105 .table_catalog
106 .clone()
107 .ok_or_else(|| "No table catalog available for Extend".to_string())?;
108
109 let fts_query = ex.fts_query.as_ref().map(|fq| PhysicalFtsScan {
110 index_name: fq.index_name.clone(),
111 query_string: fq.query_string.clone(),
112 table_name: fq.table_name.clone(),
113 column_name: fq.column_name.clone(),
114 table_catalog: table_catalog.clone(),
115 });
116 let extend_op = PhysicalExtend {
117 rel_table_name: ex.rel_table_name.clone(),
118 rel_table_id: ex.rel_table_id,
119 rel_var: ex.rel_var.clone(),
120 bound_node_var: ex.bound_node_var.clone(),
121 direction: ex.direction.clone(),
122 dst_node_var: ex.dst_node_var.clone(),
123 dst_table_name: ex.dst_table_name.clone(),
124 dst_table_id: ex.dst_table_id,
125 fts_query,
126 table_catalog,
127 };
128 let result = extend_op.execute(current_input)?;
129 record_insert_writes(ex.rel_table_id, &result, ctx);
131 Ok(result)
132 }
133 LogicalOperator::OptionalExtend(oe) => {
134 let table_catalog = ctx
135 .table_catalog
136 .clone()
137 .ok_or_else(|| "No table catalog available for OptionalExtend".to_string())?;
138
139 let input = if oe.children.is_empty() {
140 current_input
141 } else {
142 ctx.execute_children(&oe.children)?
143 };
144
145 let optional_extend_op = PhysicalOptionalExtend {
146 rel_table_name: oe.rel_table_name.clone(),
147 rel_table_id: oe.rel_table_id,
148 rel_var: oe.rel_var.clone(),
149 src_node_var: oe.src_node_var.clone(),
150 dst_node_var: oe.dst_node_var.clone(),
151 direction: oe.direction.clone(),
152 table_catalog,
153 };
154 Ok(optional_extend_op.execute(input)?)
155 }
156 LogicalOperator::Merge(m) => {
157 let table_catalog = ctx
158 .table_catalog
159 .clone()
160 .ok_or_else(|| "No table catalog available for MERGE".to_string())?;
161
162 let mut on_match_ops = Vec::new();
163 for set_item in &m.on_match {
164 on_match_ops.push(PhysicalSet {
165 table_name: set_item.table_name.clone(),
166 table_id: set_item.table_id,
167 is_node: set_item.is_node,
168 items: set_item.items.clone(),
169 table_catalog: table_catalog.clone(),
170 txn_id: ctx.txn_id,
171 undo_sink: Some(ctx.processor.undo_sink()),
172 wal_sink: Some(ctx.processor.wal_sink()),
173 function_registry: ctx.function_registry.clone(),
174 emit_count: false,
175 });
176 }
177
178 let mut on_create_ops = Vec::new();
179 for set_item in &m.on_create {
180 on_create_ops.push(PhysicalSet {
181 table_name: set_item.table_name.clone(),
182 table_id: set_item.table_id,
183 is_node: set_item.is_node,
184 items: set_item.items.clone(),
185 table_catalog: table_catalog.clone(),
186 txn_id: ctx.txn_id,
187 undo_sink: Some(ctx.processor.undo_sink()),
188 wal_sink: Some(ctx.processor.wal_sink()),
189 function_registry: ctx.function_registry.clone(),
190 emit_count: false,
191 });
192 }
193
194 let merge_op = PhysicalMerge {
195 table_name: m.table_name.clone(),
196 table_id: m.table_id,
197 properties: m.properties.clone(),
198 on_match: on_match_ops,
199 on_create: on_create_ops,
200 table_catalog,
201 txn_id: ctx.txn_id,
202 undo_sink: Some(ctx.processor.undo_sink()),
203 wal_sink: Some(ctx.processor.wal_sink()),
204 };
205 let result = merge_op.execute(current_input)?;
206 record_insert_writes(m.table_id, &result, ctx);
208 Ok(result)
209 }
210 LogicalOperator::MergeRel(mr) => {
211 let table_catalog = ctx
212 .table_catalog
213 .clone()
214 .ok_or_else(|| "No table catalog available for MERGE".to_string())?;
215
216 let mut on_match_ops = Vec::new();
217 for set_item in &mr.on_match {
218 on_match_ops.push(PhysicalSet {
219 table_name: set_item.table_name.clone(),
220 table_id: set_item.table_id,
221 is_node: set_item.is_node,
222 items: set_item.items.clone(),
223 table_catalog: table_catalog.clone(),
224 txn_id: ctx.txn_id,
225 undo_sink: Some(ctx.processor.undo_sink()),
226 wal_sink: Some(ctx.processor.wal_sink()),
227 function_registry: ctx.function_registry.clone(),
228 emit_count: false,
229 });
230 }
231
232 let mut on_create_ops = Vec::new();
233 for set_item in &mr.on_create {
234 on_create_ops.push(PhysicalSet {
235 table_name: set_item.table_name.clone(),
236 table_id: set_item.table_id,
237 is_node: set_item.is_node,
238 items: set_item.items.clone(),
239 table_catalog: table_catalog.clone(),
240 txn_id: ctx.txn_id,
241 undo_sink: Some(ctx.processor.undo_sink()),
242 wal_sink: Some(ctx.processor.wal_sink()),
243 function_registry: ctx.function_registry.clone(),
244 emit_count: false,
245 });
246 }
247
248 let merge_rel_op = PhysicalMergeRel {
249 rel_table_name: mr.rel_table_name.clone(),
250 rel_table_id: mr.rel_table_id,
251 edge_var: mr.edge_var.clone(),
252 src_node_var: mr.src_node_var.clone(),
253 dst_node_var: mr.dst_node_var.clone(),
254 direction: akar_parser::ast::EdgeDirection::LeftToRight,
255 properties: mr.properties.clone(),
256 on_match: on_match_ops,
257 on_create: on_create_ops,
258 table_catalog,
259 txn_id: ctx.txn_id,
260 undo_sink: Some(ctx.processor.undo_sink()),
261 wal_sink: Some(ctx.processor.wal_sink()),
262 };
263 let result = merge_rel_op.execute(current_input)?;
264 record_insert_writes(mr.rel_table_id, &result, ctx);
266 Ok(result)
267 }
268 LogicalOperator::CopyFrom(cf) => {
269 let table_catalog = ctx
270 .table_catalog
271 .clone()
272 .ok_or_else(|| "No table catalog available for COPY FROM".to_string())?;
273
274 let columns = if let Some(node_table) = table_catalog.get_node_table_by_name(&cf.table_name) {
276 node_table.columns.clone()
277 } else if let Some(rel_table) = table_catalog.get_rel_table_by_name(&cf.table_name) {
278 rel_table.columns.clone()
279 } else {
280 return Err(format!("Table '{}' not found in storage catalog", cf.table_name).into());
281 };
282
283 let copy_op = PhysicalCopyFrom {
284 table_name: cf.table_name.clone(),
285 table_id: cf.table_id,
286 file_path: cf.file_path.clone(),
287 columns,
288 options: cf.options.clone(),
289 table_catalog,
290 vfs: ctx
291 .vfs
292 .clone()
293 .ok_or_else(|| "VFS not initialized in processor".to_string())?,
294 txn_id: ctx.txn_id,
295 undo_sink: Some(ctx.processor.undo_sink()),
296 wal_sink: Some(ctx.processor.wal_sink()),
297 };
298 let result = copy_op.execute(current_input)?;
299 record_insert_writes(cf.table_id, &result, ctx);
301 Ok(result)
302 }
303 LogicalOperator::BatchInsert(bi) => {
304 let table_catalog = ctx
305 .table_catalog
306 .clone()
307 .ok_or_else(|| "No table catalog available for BATCH INSERT".to_string())?;
308
309 let batch_op = PhysicalBatchInsert {
310 table_name: bi.table_name.clone(),
311 table_id: bi.table_id,
312 rows: bi.rows.clone(),
313 table_catalog,
314 txn_id: ctx.txn_id,
315 undo_sink: Some(ctx.processor.undo_sink()),
316 wal_sink: Some(ctx.processor.wal_sink()),
317 };
318 let result = batch_op.execute(current_input)?;
319 record_insert_writes(bi.table_id, &result, ctx);
321 Ok(result)
322 }
323 LogicalOperator::Insert(i) => {
324 let exec = crate::physical::misc::PhysicalInsert {
325 table_name: i.table_name.clone(),
326 table_id: i.table_id,
327 columns: i.columns.clone(),
328 values: i.values.clone(),
329 table_catalog: ctx.table_catalog.clone().unwrap(),
330 txn_id: ctx.txn_id,
331 undo_sink: Some(ctx.processor.undo_sink()),
332 wal_sink: Some(ctx.processor.wal_sink()),
333 };
334 let result = exec.execute(current_input)?;
335 record_insert_writes(i.table_id, &result, ctx);
337 Ok(result)
338 }
339 _ => Err(format!("Not an update operator: {:?}", op).into()),
340 }
341}
342
343fn record_set_writes(table_id: u64, result: &[DataChunk], ctx: &mut ExecutionContext) {
347 if let Some(chunk) = result.first() {
348 let id_col = chunk
349 .field_names
350 .iter()
351 .position(|n| n == "_id" || n.ends_with("._id"))
352 .unwrap_or(0);
353 for row in 0..chunk.size {
354 if !chunk.fields.is_empty() {
355 if let Some(akar_common::types::Value::Int64(row_idx)) = chunk.get_value(id_col, row) {
356 ctx.written_rows.push((table_id, row_idx as u64));
357 }
358 }
359 }
360 }
361}
362
363fn record_delete_writes(table_id: u64, result: &[DataChunk], ctx: &mut ExecutionContext) {
366 if let Some(chunk) = result.first() {
367 for row in 0..chunk.size {
368 if !chunk.fields.is_empty() {
369 if let Some(akar_common::types::Value::Int64(row_idx)) = chunk.get_value(0, row) {
370 ctx.written_rows.push((table_id, row_idx as u64));
371 }
372 }
373 }
374 }
375}
376
377fn record_insert_writes(table_id: u64, result: &[DataChunk], ctx: &mut ExecutionContext) {
383 if let Some(chunk) = result.first() {
384 if let Some(id_col) = chunk.field_names.iter().position(|n| n == "_id" || n.ends_with("._id")) {
387 for row in 0..chunk.size {
388 if let Some(akar_common::types::Value::Int64(row_id)) = chunk.get_value(id_col, row) {
389 ctx.written_rows.push((table_id, row_id as u64));
390 }
391 }
392 } else if chunk.fields.len() > 1 {
393 for row in 0..chunk.fields[1].len() {
394 if let Some(akar_common::types::Value::Int64(row_id)) = chunk.get_value(1, row) {
395 ctx.written_rows.push((table_id, row_id as u64));
396 }
397 }
398 }
399 }
400}