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