1use crate::expression_evaluator::ExpressionEvaluator;
3use crate::physical::types::{OperatorResult, PhysicalOperatorExec};
4use crate::physical::write_ops::delete::{ast_constant_to_value, row_id_column_index};
5use akar_common::arrow_vector::VectorAccess;
6use akar_common::types::Value;
7use akar_common::types::{PhysicalTypeID, physical_type_from_logical};
8use akar_common::vector::{DataChunk, ValueVector};
9use akar_function::registry::FunctionRegistry;
10use akar_function::scalar::evaluate_scalar;
11use akar_parser::ast::{BinaryOp, Expression};
12use akar_planner::logical_operator::SetItem;
13use akar_storage::table::{ColumnDefinition, TableCatalog};
14use akar_storage::wal::{WalSink, log_update_record};
15use akar_transaction::UndoRecord;
16use std::sync::{Arc, Mutex};
17
18pub struct PhysicalSet {
27 pub table_name: String,
28 pub table_id: u64,
29 pub is_node: bool,
30 pub items: Vec<SetItem>,
31 pub table_catalog: Arc<TableCatalog>,
32 pub txn_id: Option<u64>,
34 pub undo_sink: Option<Arc<Mutex<Vec<UndoRecord>>>>,
36 pub function_registry: Option<Arc<Mutex<FunctionRegistry>>>,
39 pub emit_count: bool,
43 pub wal_sink: Option<WalSink>,
45}
46
47impl PhysicalOperatorExec for PhysicalSet {
48 fn operator_type(&self) -> &str {
49 "set"
50 }
51
52 fn execute(&self, input: Vec<DataChunk>) -> OperatorResult {
53 let mut rows_to_update: Vec<u64> = Vec::new();
57 let mut source_rows: Vec<(usize, usize)> = Vec::new();
61
62 for (ci, chunk) in input.iter().enumerate() {
63 let row_id_col = row_id_column_index(chunk);
64 for row in 0..chunk.size {
65 if !chunk.fields.is_empty()
66 && let Some(Value::Int64(val)) = chunk.get_value(row_id_col.unwrap_or(0), row)
67 {
68 rows_to_update.push(val as u64);
69 source_rows.push((ci, row));
70 }
71 }
72 }
73
74 if rows_to_update.is_empty() {
75 if self.emit_count {
80 return Ok(vec![count_chunk(0)]);
81 }
82 return Ok(Vec::new());
83 }
84
85 let snapshot = self.build_snapshot_chunk(&rows_to_update, &input, &source_rows)?;
87
88 let mut all_values: Vec<Vec<Value>> = Vec::with_capacity(self.items.len());
93 for item in &self.items {
94 all_values.push(self.evaluate_item(item, &snapshot)?);
95 }
96
97 let mut updated = 0u64;
99 if self.is_node {
100 if let Some(mut table) = self.table_catalog.get_node_table_by_name_mut(&self.table_name) {
101 for (item_idx, item) in self.items.iter().enumerate() {
102 let col_idx = table
106 .columns
107 .iter()
108 .position(|c| c.name == item.column_name)
109 .unwrap_or(item.column_idx);
110 for (i, row_idx) in rows_to_update.iter().enumerate() {
111 if let Some(sink) = self.undo_sink.as_ref()
113 && let Ok(mut u) = sink.lock()
114 {
115 let old_data = table.cell_undo_bytes(*row_idx, col_idx);
116 u.push(UndoRecord::update(self.table_id, *row_idx, col_idx as u32, old_data));
117 }
118 if table
119 .update_cell(*row_idx, col_idx, all_values[item_idx][i].clone())
120 .is_ok()
121 {
122 updated += 1;
123 log_update_record(
124 &self.wal_sink,
125 self.table_id,
126 *row_idx,
127 col_idx as u32,
128 &all_values[item_idx][i],
129 );
130 }
131 }
132 }
133 } else {
134 return Err(format!("Node table '{}' not found for SET", self.table_name).into());
135 }
136 } else {
137 if let Some(mut table) = self.table_catalog.get_rel_table_by_name_mut(&self.table_name) {
138 for (item_idx, item) in self.items.iter().enumerate() {
139 let col_idx = table
140 .columns
141 .iter()
142 .position(|c| c.name == item.column_name)
143 .unwrap_or(item.column_idx);
144 for (i, edge_idx) in rows_to_update.iter().enumerate() {
145 if let Some(sink) = self.undo_sink.as_ref()
146 && let Ok(mut u) = sink.lock()
147 {
148 let old_data = table.edge_cell_undo_bytes(*edge_idx as usize, col_idx);
149 u.push(UndoRecord::update(self.table_id, *edge_idx, col_idx as u32, old_data));
150 }
151 if table
152 .update_cell(*edge_idx as usize, col_idx, all_values[item_idx][i].clone())
153 .is_ok()
154 {
155 updated += 1;
156 log_update_record(
157 &self.wal_sink,
158 self.table_id,
159 *edge_idx,
160 col_idx as u32,
161 &all_values[item_idx][i],
162 );
163 }
164 }
165 }
166 } else {
167 return Err(format!("Rel table '{}' not found for SET", self.table_name).into());
168 }
169 }
170
171 tracing::info!("SET: updated {updated} rows in '{}'", self.table_name);
172
173 let mut output = self.build_output_chunk(&rows_to_update, &input, &source_rows)?;
178 let mut count_v = ValueVector::new(PhysicalTypeID::Int64, rows_to_update.len());
179 count_v.resize(rows_to_update.len());
180 for i in 0..rows_to_update.len() {
181 count_v.set_i64(i, updated as i64);
182 }
183 output
184 .fields
185 .insert(0, akar_common::arrow_vector::ArrowVector::from_legacy(&count_v).array);
186 output.field_types.insert(0, PhysicalTypeID::Int64);
187 output.field_names.insert(0, String::new());
188 Ok(vec![output])
189 }
190}
191
192fn count_chunk(count: u64) -> DataChunk {
193 let mut v = ValueVector::new(PhysicalTypeID::Int64, 1);
194 v.resize(1);
195 v.set_i64(0, count as i64);
196 let arr = akar_common::arrow_vector::ArrowVector::from_legacy(&v).array;
197 DataChunk::new(vec![arr], vec![PhysicalTypeID::Int64])
198}
199
200impl PhysicalSet {
201 fn build_snapshot_chunk(
206 &self,
207 rows: &[u64],
208 input: &[DataChunk],
209 source_rows: &[(usize, usize)],
210 ) -> Result<DataChunk, String> {
211 let mut snapshot = if self.is_node {
212 let table = self
213 .table_catalog
214 .get_node_table_by_name(&self.table_name)
215 .ok_or_else(|| format!("Node table '{}' not found for SET", self.table_name))?;
216 build_old_row_chunk(&table.columns, rows, &|row, col| {
217 table.get_value(row as usize, col).cloned()
218 })?
219 } else {
220 let table = self
221 .table_catalog
222 .get_rel_table_by_name(&self.table_name)
223 .ok_or_else(|| format!("Rel table '{}' not found for SET", self.table_name))?;
224 build_old_row_chunk(&table.columns, rows, &|row, col| {
225 table.get_edge_properties(row as usize).get(col).cloned()
226 })?
227 };
228 append_pipeline_columns(&mut snapshot, input, source_rows)?;
229 Ok(snapshot)
230 }
231}
232
233pub(crate) fn append_pipeline_columns(
238 snapshot: &mut DataChunk,
239 input: &[DataChunk],
240 source_rows: &[(usize, usize)],
241) -> Result<(), String> {
242 let n = source_rows.len();
243 for (ci, chunk) in input.iter().enumerate() {
244 let mut appended: Vec<(usize, String)> = Vec::new();
245 for (col_idx, name) in chunk.field_names.iter().enumerate() {
246 if name == "_id" || name.ends_with("._id") {
247 continue;
248 }
249 if snapshot.field_names.iter().any(|existing| existing == name) {
250 continue;
251 }
252 if let Some((_, base)) = name.rsplit_once('.') {
258 if snapshot.field_names.iter().any(|existing| existing == base) {
259 continue;
260 }
261 }
262 appended.push((col_idx, name.clone()));
263 }
264 if appended.is_empty() {
265 continue;
266 }
267
268 for (col_idx, name) in appended {
269 let mut col_values: Vec<Value> = vec![Value::Null; n];
271 for (i, &(cci, rowi)) in source_rows.iter().enumerate() {
272 if cci == ci {
273 col_values[i] = chunk.get_value(col_idx, rowi).unwrap_or(Value::Null);
274 }
275 }
276 let phys_type = col_values
279 .iter()
280 .find(|v| !matches!(v, Value::Null))
281 .map(|v| v.physical_type())
282 .unwrap_or(chunk.field_types[col_idx]);
283 let arr = crate::expression_evaluator::build_arrow_from_values(&col_values, phys_type, n)
284 .map_err(|e| e.to_string())?;
285 snapshot.fields.push(arr.array);
286 snapshot.field_types.push(arr.physical_type);
287 snapshot.field_names.push(name);
288 }
289 }
290 Ok(())
291}
292
293impl PhysicalSet {
294 fn build_output_chunk(
301 &self,
302 rows: &[u64],
303 input: &[DataChunk],
304 source_rows: &[(usize, usize)],
305 ) -> Result<DataChunk, String> {
306 let mut chunk = if self.is_node {
307 let table = self
308 .table_catalog
309 .get_node_table_by_name(&self.table_name)
310 .ok_or_else(|| format!("Node table '{}' not found for SET", self.table_name))?;
311 build_old_row_chunk(&table.columns, rows, &|row, col| {
312 table.get_value(row as usize, col).cloned()
313 })?
314 } else {
315 let table = self
316 .table_catalog
317 .get_rel_table_by_name(&self.table_name)
318 .ok_or_else(|| format!("Rel table '{}' not found for SET", self.table_name))?;
319 build_old_row_chunk(&table.columns, rows, &|row, col| {
320 table.get_edge_properties(row as usize).get(col).cloned()
321 })?
322 };
323 append_pipeline_columns(&mut chunk, input, source_rows)?;
324 let mut v = ValueVector::new(PhysicalTypeID::Int64, rows.len());
327 v.resize(rows.len());
328 for (i, r) in rows.iter().enumerate() {
329 v.set_i64(i, *r as i64);
330 }
331 chunk
332 .fields
333 .push(akar_common::arrow_vector::ArrowVector::from_legacy(&v).array);
334 chunk.field_types.push(PhysicalTypeID::Int64);
335 chunk.field_names.push("_id".to_string());
336 Ok(chunk)
337 }
338
339 fn evaluate_item(&self, item: &SetItem, chunk: &DataChunk) -> Result<Vec<Value>, String> {
342 if let Some(registry) = self.function_registry.as_ref() {
343 let evaluator = ExpressionEvaluator::new(registry.clone());
344 let vec = evaluator
348 .evaluate_to_arrow(&item.value, chunk)
349 .map_err(|e| e.to_string())?;
350 Ok((0..chunk.size)
351 .map(|i| vec.get_value(i).unwrap_or(Value::Null))
352 .collect())
353 } else {
354 Ok((0..chunk.size)
355 .map(|i| evaluate_expression_for_row(&item.value, chunk, i))
356 .collect())
357 }
358 }
359}
360
361pub(crate) fn build_old_row_chunk(
370 columns: &[ColumnDefinition],
371 rows: &[u64],
372 get_cell: &dyn Fn(u64, usize) -> Option<Value>,
373) -> Result<DataChunk, String> {
374 let n = rows.len();
375 let mut fields = Vec::with_capacity(columns.len());
376 let mut field_types = Vec::with_capacity(columns.len());
377 let mut field_names = Vec::with_capacity(columns.len());
378 for (col_idx, col) in columns.iter().enumerate() {
379 let phys_type = physical_type_from_logical(col.logical_type);
380 let col_values: Vec<Value> = rows
381 .iter()
382 .map(|row| get_cell(*row, col_idx).unwrap_or(Value::Null))
383 .collect();
384 let arr = crate::expression_evaluator::build_arrow_from_values(&col_values, phys_type, n)
385 .map_err(|e| e.to_string())?;
386 fields.push(arr.array);
387 field_types.push(phys_type);
388 field_names.push(col.name.clone());
389 }
390 Ok(DataChunk::new(fields, field_types).with_names(field_names))
391}
392
393pub fn evaluate_expression_for_row(
395 expr: &akar_parser::ast::Expression,
396 chunk: &DataChunk,
397 row: usize,
398) -> akar_common::types::Value {
399 match expr {
400 akar_parser::ast::Expression::Constant(c) => match c {
401 akar_parser::ast::Constant::Null => akar_common::types::Value::Null,
402 akar_parser::ast::Constant::Bool(b) => akar_common::types::Value::Bool(*b),
403 akar_parser::ast::Constant::Integer(i) => akar_common::types::Value::Int64(*i),
404 akar_parser::ast::Constant::Float(f) => akar_common::types::Value::Double(*f),
405 akar_parser::ast::Constant::String(s) => akar_common::types::Value::String(s.clone()),
406 },
407 akar_parser::ast::Expression::Variable(name) => chunk
408 .field_names
409 .iter()
410 .position(|n| n == name)
411 .and_then(|i| chunk.get_value(i, row))
412 .unwrap_or(akar_common::types::Value::Null),
413 akar_parser::ast::Expression::PropertyAccess(obj, prop) => {
414 let qualified = match obj.as_ref() {
415 akar_parser::ast::Expression::Variable(var) => format!("{var}.{prop}"),
416 _ => prop.clone(),
417 };
418 if let Some(i) = chunk.field_names.iter().position(|n| *n == qualified) {
419 chunk.get_value(i, row).unwrap_or(akar_common::types::Value::Null)
420 } else if let akar_parser::ast::Expression::Variable(var) = obj.as_ref()
421 && let Some(i) = chunk.field_names.iter().position(|n| n == var)
422 {
423 let obj_val = chunk.get_value(i, row).unwrap_or(akar_common::types::Value::Null);
427 crate::expression_evaluator::map_property_value(&obj_val, prop)
428 } else if let Some(i) = chunk.field_names.iter().position(|n| *n == *prop) {
429 chunk.get_value(i, row).unwrap_or(akar_common::types::Value::Null)
430 } else {
431 akar_common::types::Value::Null
432 }
433 }
434 akar_parser::ast::Expression::UnaryOp(op, inner) => {
435 let v = evaluate_expression_for_row(inner, chunk, row);
436 match op {
437 akar_parser::ast::UnaryOp::Negate => match v {
438 akar_common::types::Value::Int64(n) => akar_common::types::Value::Int64(-n),
439 akar_common::types::Value::Double(n) => akar_common::types::Value::Double(-n),
440 _ => akar_common::types::Value::Null,
441 },
442 _ => akar_common::types::Value::Null,
443 }
444 }
445 akar_parser::ast::Expression::BinaryOp(op, left, right) => {
446 let l = evaluate_expression_for_row(left, chunk, row);
447 let r = evaluate_expression_for_row(right, chunk, row);
448 binary_value_op(op, &l, &r)
449 }
450 akar_parser::ast::Expression::List(items) => {
451 let vals = items
452 .iter()
453 .map(|i| evaluate_expression_for_row(i, chunk, row))
454 .collect();
455 akar_common::types::Value::List(vals)
456 }
457 akar_parser::ast::Expression::Map(items) => {
458 let entries = items
459 .iter()
460 .map(|(k, v)| {
461 (
462 akar_common::types::Value::String(k.clone()),
463 evaluate_expression_for_row(v, chunk, row),
464 )
465 })
466 .collect();
467 akar_common::types::Value::Map(entries)
468 }
469 _ => akar_common::types::Value::Null,
470 }
471}
472
473fn as_f64(v: &Value) -> Option<f64> {
474 match v {
475 Value::Int64(n) => Some(*n as f64),
476 Value::Int32(n) => Some(*n as f64),
477 Value::Int128(n) => Some(*n as f64),
478 Value::Double(n) => Some(*n),
479 Value::Float(n) => Some(*n as f64),
480 _ => None,
481 }
482}
483
484fn binary_value_op(op: &BinaryOp, l: &Value, r: &Value) -> Value {
488 match op {
489 BinaryOp::Add => match (l, r) {
490 (Value::String(a), Value::String(b)) => Value::String(format!("{a}{b}")),
491 _ => match (as_f64(l), as_f64(r)) {
492 (Some(a), Some(b)) => {
493 if matches!(l, Value::Int64(_)) && matches!(r, Value::Int64(_)) {
494 Value::Int64(a as i64 + b as i64)
495 } else {
496 Value::Double(a + b)
497 }
498 }
499 _ => Value::Null,
500 },
501 },
502 BinaryOp::Subtract => match (as_f64(l), as_f64(r)) {
503 (Some(a), Some(b)) => {
504 if matches!(l, Value::Int64(_)) && matches!(r, Value::Int64(_)) {
505 Value::Int64(a as i64 - b as i64)
506 } else {
507 Value::Double(a - b)
508 }
509 }
510 _ => Value::Null,
511 },
512 BinaryOp::Multiply => match (as_f64(l), as_f64(r)) {
513 (Some(a), Some(b)) => {
514 if matches!(l, Value::Int64(_)) && matches!(r, Value::Int64(_)) {
515 Value::Int64(a as i64 * b as i64)
516 } else {
517 Value::Double(a * b)
518 }
519 }
520 _ => Value::Null,
521 },
522 BinaryOp::Divide => match (as_f64(l), as_f64(r)) {
523 (Some(a), Some(b)) if b != 0.0 => {
524 if matches!(l, Value::Int64(_)) && matches!(r, Value::Int64(_)) {
525 Value::Int64(a as i64 / b as i64)
526 } else {
527 Value::Double(a / b)
528 }
529 }
530 _ => Value::Null,
531 },
532 BinaryOp::Modulo => match (l, r) {
533 (Value::Int64(a), Value::Int64(b)) if *b != 0 => Value::Int64(a % b),
534 _ => Value::Null,
535 },
536 BinaryOp::Equal => Value::Bool(l == r),
537 BinaryOp::NotEqual => Value::Bool(l != r),
538 BinaryOp::LessThan => match (as_f64(l), as_f64(r)) {
539 (Some(a), Some(b)) => Value::Bool(a < b),
540 _ => Value::Null,
541 },
542 BinaryOp::LessThanOrEqual => match (as_f64(l), as_f64(r)) {
543 (Some(a), Some(b)) => Value::Bool(a <= b),
544 _ => Value::Null,
545 },
546 BinaryOp::GreaterThan => match (as_f64(l), as_f64(r)) {
547 (Some(a), Some(b)) => Value::Bool(a > b),
548 _ => Value::Null,
549 },
550 BinaryOp::GreaterThanOrEqual => match (as_f64(l), as_f64(r)) {
551 (Some(a), Some(b)) => Value::Bool(a >= b),
552 _ => Value::Null,
553 },
554 BinaryOp::And => match (l, r) {
555 (Value::Bool(a), Value::Bool(b)) => Value::Bool(*a && *b),
556 _ => Value::Null,
557 },
558 BinaryOp::Or => match (l, r) {
559 (Value::Bool(a), Value::Bool(b)) => Value::Bool(*a || *b),
560 _ => Value::Null,
561 },
562 _ => Value::Null,
563 }
564}
565
566pub fn evaluate_constant_expr(expr: &Expression, registry: &FunctionRegistry) -> Value {
572 match expr {
573 Expression::Constant(c) => ast_constant_to_value(c),
574 Expression::List(items) => Value::List(items.iter().map(|i| evaluate_constant_expr(i, registry)).collect()),
575 Expression::Map(items) => Value::Map(
576 items
577 .iter()
578 .map(|(k, v)| (Value::String(k.clone()), evaluate_constant_expr(v, registry)))
579 .collect(),
580 ),
581 Expression::FunctionCall(name, args) => {
582 let arg_values: Vec<Value> = args.iter().map(|a| evaluate_constant_expr(a, registry)).collect();
583 if arg_values.iter().any(|v| matches!(v, Value::Null)) {
584 return Value::Null;
585 }
586 let func = match registry.get_scalar(name).cloned() {
587 Some(f) => f,
588 None => return Value::Null,
589 };
590 evaluate_scalar(&func, &arg_values).unwrap_or(Value::Null)
591 }
592 _ => Value::Null,
593 }
594}