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_transaction::UndoRecord;
15use std::sync::{Arc, Mutex};
16
17pub struct PhysicalSet {
26 pub table_name: String,
27 pub table_id: u64,
28 pub is_node: bool,
29 pub items: Vec<SetItem>,
30 pub table_catalog: Arc<TableCatalog>,
31 pub txn_id: Option<u64>,
33 pub undo_sink: Option<Arc<Mutex<Vec<UndoRecord>>>>,
35 pub function_registry: Option<Arc<Mutex<FunctionRegistry>>>,
38 pub emit_count: bool,
42}
43
44impl PhysicalOperatorExec for PhysicalSet {
45 fn operator_type(&self) -> &str {
46 "set"
47 }
48
49 fn execute(&self, input: Vec<DataChunk>) -> OperatorResult {
50 let mut rows_to_update: Vec<u64> = Vec::new();
54 let mut source_rows: Vec<(usize, usize)> = Vec::new();
58
59 for (ci, chunk) in input.iter().enumerate() {
60 let row_id_col = row_id_column_index(chunk);
61 for row in 0..chunk.size {
62 if !chunk.fields.is_empty()
63 && let Some(Value::Int64(val)) = chunk.get_value(row_id_col.unwrap_or(0), row)
64 {
65 rows_to_update.push(val as u64);
66 source_rows.push((ci, row));
67 }
68 }
69 }
70
71 if rows_to_update.is_empty() {
72 if self.emit_count {
77 return Ok(vec![count_chunk(0)]);
78 }
79 return Ok(Vec::new());
80 }
81
82 let snapshot = self.build_snapshot_chunk(&rows_to_update, &input, &source_rows)?;
84
85 let mut all_values: Vec<Vec<Value>> = Vec::with_capacity(self.items.len());
90 for item in &self.items {
91 all_values.push(self.evaluate_item(item, &snapshot)?);
92 }
93
94 let mut updated = 0u64;
96 if self.is_node {
97 if let Some(mut table) = self.table_catalog.get_node_table_by_name_mut(&self.table_name) {
98 for (item_idx, item) in self.items.iter().enumerate() {
99 let col_idx = table
103 .columns
104 .iter()
105 .position(|c| c.name == item.column_name)
106 .unwrap_or(item.column_idx);
107 for (i, row_idx) in rows_to_update.iter().enumerate() {
108 if let Some(sink) = self.undo_sink.as_ref()
110 && let Ok(mut u) = sink.lock()
111 {
112 let old_data = table.cell_undo_bytes(*row_idx, col_idx);
113 u.push(UndoRecord::update(self.table_id, *row_idx, col_idx as u32, old_data));
114 }
115 if table
116 .update_cell(*row_idx, col_idx, all_values[item_idx][i].clone())
117 .is_ok()
118 {
119 updated += 1;
120 }
121 }
122 }
123 } else {
124 return Err(format!("Node table '{}' not found for SET", self.table_name).into());
125 }
126 } else {
127 if let Some(mut table) = self.table_catalog.get_rel_table_by_name_mut(&self.table_name) {
128 for (item_idx, item) in self.items.iter().enumerate() {
129 let col_idx = table
130 .columns
131 .iter()
132 .position(|c| c.name == item.column_name)
133 .unwrap_or(item.column_idx);
134 for (i, edge_idx) in rows_to_update.iter().enumerate() {
135 if let Some(sink) = self.undo_sink.as_ref()
136 && let Ok(mut u) = sink.lock()
137 {
138 let old_data = table.edge_cell_undo_bytes(*edge_idx as usize, col_idx);
139 u.push(UndoRecord::update(self.table_id, *edge_idx, col_idx as u32, old_data));
140 }
141 if table
142 .update_cell(*edge_idx as usize, col_idx, all_values[item_idx][i].clone())
143 .is_ok()
144 {
145 updated += 1;
146 }
147 }
148 }
149 } else {
150 return Err(format!("Rel table '{}' not found for SET", self.table_name).into());
151 }
152 }
153
154 tracing::info!("SET: updated {updated} rows in '{}'", self.table_name);
155
156 let mut output = self.build_output_chunk(&rows_to_update, &input, &source_rows)?;
161 let mut count_v = ValueVector::new(PhysicalTypeID::Int64, rows_to_update.len());
162 count_v.resize(rows_to_update.len());
163 for i in 0..rows_to_update.len() {
164 count_v.set_i64(i, updated as i64);
165 }
166 output
167 .fields
168 .insert(0, akar_common::arrow_vector::ArrowVector::from_legacy(&count_v).array);
169 output.field_types.insert(0, PhysicalTypeID::Int64);
170 output.field_names.insert(0, String::new());
171 Ok(vec![output])
172 }
173}
174
175fn count_chunk(count: u64) -> DataChunk {
176 let mut v = ValueVector::new(PhysicalTypeID::Int64, 1);
177 v.resize(1);
178 v.set_i64(0, count as i64);
179 let arr = akar_common::arrow_vector::ArrowVector::from_legacy(&v).array;
180 DataChunk::new(vec![arr], vec![PhysicalTypeID::Int64])
181}
182
183impl PhysicalSet {
184 fn build_snapshot_chunk(
189 &self,
190 rows: &[u64],
191 input: &[DataChunk],
192 source_rows: &[(usize, usize)],
193 ) -> Result<DataChunk, String> {
194 let mut snapshot = if self.is_node {
195 let table = self
196 .table_catalog
197 .get_node_table_by_name(&self.table_name)
198 .ok_or_else(|| format!("Node table '{}' not found for SET", self.table_name))?;
199 build_old_row_chunk(&table.columns, rows, &|row, col| {
200 table.get_value(row as usize, col).cloned()
201 })?
202 } else {
203 let table = self
204 .table_catalog
205 .get_rel_table_by_name(&self.table_name)
206 .ok_or_else(|| format!("Rel table '{}' not found for SET", self.table_name))?;
207 build_old_row_chunk(&table.columns, rows, &|row, col| {
208 table.get_edge_properties(row as usize).get(col).cloned()
209 })?
210 };
211 append_pipeline_columns(&mut snapshot, input, source_rows)?;
212 Ok(snapshot)
213 }
214}
215
216pub(crate) fn append_pipeline_columns(
221 snapshot: &mut DataChunk,
222 input: &[DataChunk],
223 source_rows: &[(usize, usize)],
224) -> Result<(), String> {
225 let n = source_rows.len();
226 for (ci, chunk) in input.iter().enumerate() {
227 let mut appended: Vec<(usize, String)> = Vec::new();
228 for (col_idx, name) in chunk.field_names.iter().enumerate() {
229 if name == "_id" || name.ends_with("._id") {
230 continue;
231 }
232 if snapshot.field_names.iter().any(|existing| existing == name) {
233 continue;
234 }
235 if let Some((_, base)) = name.rsplit_once('.') {
241 if snapshot.field_names.iter().any(|existing| existing == base) {
242 continue;
243 }
244 }
245 appended.push((col_idx, name.clone()));
246 }
247 if appended.is_empty() {
248 continue;
249 }
250
251 for (col_idx, name) in appended {
252 let mut col_values: Vec<Value> = vec![Value::Null; n];
254 for (i, &(cci, rowi)) in source_rows.iter().enumerate() {
255 if cci == ci {
256 col_values[i] = chunk.get_value(col_idx, rowi).unwrap_or(Value::Null);
257 }
258 }
259 let phys_type = col_values
262 .iter()
263 .find(|v| !matches!(v, Value::Null))
264 .map(|v| v.physical_type())
265 .unwrap_or(chunk.field_types[col_idx]);
266 let arr = crate::expression_evaluator::build_arrow_from_values(&col_values, phys_type, n)
267 .map_err(|e| e.to_string())?;
268 snapshot.fields.push(arr.array);
269 snapshot.field_types.push(arr.physical_type);
270 snapshot.field_names.push(name);
271 }
272 }
273 Ok(())
274}
275
276impl PhysicalSet {
277 fn build_output_chunk(
284 &self,
285 rows: &[u64],
286 input: &[DataChunk],
287 source_rows: &[(usize, usize)],
288 ) -> Result<DataChunk, String> {
289 let mut chunk = if self.is_node {
290 let table = self
291 .table_catalog
292 .get_node_table_by_name(&self.table_name)
293 .ok_or_else(|| format!("Node table '{}' not found for SET", self.table_name))?;
294 build_old_row_chunk(&table.columns, rows, &|row, col| {
295 table.get_value(row as usize, col).cloned()
296 })?
297 } else {
298 let table = self
299 .table_catalog
300 .get_rel_table_by_name(&self.table_name)
301 .ok_or_else(|| format!("Rel table '{}' not found for SET", self.table_name))?;
302 build_old_row_chunk(&table.columns, rows, &|row, col| {
303 table.get_edge_properties(row as usize).get(col).cloned()
304 })?
305 };
306 append_pipeline_columns(&mut chunk, input, source_rows)?;
307 let mut v = ValueVector::new(PhysicalTypeID::Int64, rows.len());
310 v.resize(rows.len());
311 for (i, r) in rows.iter().enumerate() {
312 v.set_i64(i, *r as i64);
313 }
314 chunk
315 .fields
316 .push(akar_common::arrow_vector::ArrowVector::from_legacy(&v).array);
317 chunk.field_types.push(PhysicalTypeID::Int64);
318 chunk.field_names.push("_id".to_string());
319 Ok(chunk)
320 }
321
322 fn evaluate_item(&self, item: &SetItem, chunk: &DataChunk) -> Result<Vec<Value>, String> {
325 if let Some(registry) = self.function_registry.as_ref() {
326 let evaluator = ExpressionEvaluator::new(registry.clone());
327 let vec = evaluator
331 .evaluate_to_arrow(&item.value, chunk)
332 .map_err(|e| e.to_string())?;
333 Ok((0..chunk.size)
334 .map(|i| vec.get_value(i).unwrap_or(Value::Null))
335 .collect())
336 } else {
337 Ok((0..chunk.size)
338 .map(|i| evaluate_expression_for_row(&item.value, chunk, i))
339 .collect())
340 }
341 }
342}
343
344pub(crate) fn build_old_row_chunk(
353 columns: &[ColumnDefinition],
354 rows: &[u64],
355 get_cell: &dyn Fn(u64, usize) -> Option<Value>,
356) -> Result<DataChunk, String> {
357 let n = rows.len();
358 let mut fields = Vec::with_capacity(columns.len());
359 let mut field_types = Vec::with_capacity(columns.len());
360 let mut field_names = Vec::with_capacity(columns.len());
361 for (col_idx, col) in columns.iter().enumerate() {
362 let phys_type = physical_type_from_logical(col.logical_type);
363 let col_values: Vec<Value> = rows
364 .iter()
365 .map(|row| get_cell(*row, col_idx).unwrap_or(Value::Null))
366 .collect();
367 let arr = crate::expression_evaluator::build_arrow_from_values(&col_values, phys_type, n)
368 .map_err(|e| e.to_string())?;
369 fields.push(arr.array);
370 field_types.push(phys_type);
371 field_names.push(col.name.clone());
372 }
373 Ok(DataChunk::new(fields, field_types).with_names(field_names))
374}
375
376pub fn evaluate_expression_for_row(
378 expr: &akar_parser::ast::Expression,
379 chunk: &DataChunk,
380 row: usize,
381) -> akar_common::types::Value {
382 match expr {
383 akar_parser::ast::Expression::Constant(c) => match c {
384 akar_parser::ast::Constant::Null => akar_common::types::Value::Null,
385 akar_parser::ast::Constant::Bool(b) => akar_common::types::Value::Bool(*b),
386 akar_parser::ast::Constant::Integer(i) => akar_common::types::Value::Int64(*i),
387 akar_parser::ast::Constant::Float(f) => akar_common::types::Value::Double(*f),
388 akar_parser::ast::Constant::String(s) => akar_common::types::Value::String(s.clone()),
389 },
390 akar_parser::ast::Expression::Variable(name) => chunk
391 .field_names
392 .iter()
393 .position(|n| n == name)
394 .and_then(|i| chunk.get_value(i, row))
395 .unwrap_or(akar_common::types::Value::Null),
396 akar_parser::ast::Expression::PropertyAccess(obj, prop) => {
397 let qualified = match obj.as_ref() {
398 akar_parser::ast::Expression::Variable(var) => format!("{var}.{prop}"),
399 _ => prop.clone(),
400 };
401 if let Some(i) = chunk.field_names.iter().position(|n| *n == qualified) {
402 chunk.get_value(i, row).unwrap_or(akar_common::types::Value::Null)
403 } else if let akar_parser::ast::Expression::Variable(var) = obj.as_ref()
404 && let Some(i) = chunk.field_names.iter().position(|n| n == var)
405 {
406 let obj_val = chunk.get_value(i, row).unwrap_or(akar_common::types::Value::Null);
410 crate::expression_evaluator::map_property_value(&obj_val, prop)
411 } else if let Some(i) = chunk.field_names.iter().position(|n| *n == *prop) {
412 chunk.get_value(i, row).unwrap_or(akar_common::types::Value::Null)
413 } else {
414 akar_common::types::Value::Null
415 }
416 }
417 akar_parser::ast::Expression::UnaryOp(op, inner) => {
418 let v = evaluate_expression_for_row(inner, chunk, row);
419 match op {
420 akar_parser::ast::UnaryOp::Negate => match v {
421 akar_common::types::Value::Int64(n) => akar_common::types::Value::Int64(-n),
422 akar_common::types::Value::Double(n) => akar_common::types::Value::Double(-n),
423 _ => akar_common::types::Value::Null,
424 },
425 _ => akar_common::types::Value::Null,
426 }
427 }
428 akar_parser::ast::Expression::BinaryOp(op, left, right) => {
429 let l = evaluate_expression_for_row(left, chunk, row);
430 let r = evaluate_expression_for_row(right, chunk, row);
431 binary_value_op(op, &l, &r)
432 }
433 akar_parser::ast::Expression::List(items) => {
434 let vals = items
435 .iter()
436 .map(|i| evaluate_expression_for_row(i, chunk, row))
437 .collect();
438 akar_common::types::Value::List(vals)
439 }
440 akar_parser::ast::Expression::Map(items) => {
441 let entries = items
442 .iter()
443 .map(|(k, v)| {
444 (
445 akar_common::types::Value::String(k.clone()),
446 evaluate_expression_for_row(v, chunk, row),
447 )
448 })
449 .collect();
450 akar_common::types::Value::Map(entries)
451 }
452 _ => akar_common::types::Value::Null,
453 }
454}
455
456fn as_f64(v: &Value) -> Option<f64> {
457 match v {
458 Value::Int64(n) => Some(*n as f64),
459 Value::Int32(n) => Some(*n as f64),
460 Value::Int128(n) => Some(*n as f64),
461 Value::Double(n) => Some(*n),
462 Value::Float(n) => Some(*n as f64),
463 _ => None,
464 }
465}
466
467fn binary_value_op(op: &BinaryOp, l: &Value, r: &Value) -> Value {
471 match op {
472 BinaryOp::Add => match (l, r) {
473 (Value::String(a), Value::String(b)) => Value::String(format!("{a}{b}")),
474 _ => match (as_f64(l), as_f64(r)) {
475 (Some(a), Some(b)) => {
476 if matches!(l, Value::Int64(_)) && matches!(r, Value::Int64(_)) {
477 Value::Int64(a as i64 + b as i64)
478 } else {
479 Value::Double(a + b)
480 }
481 }
482 _ => Value::Null,
483 },
484 },
485 BinaryOp::Subtract => match (as_f64(l), as_f64(r)) {
486 (Some(a), Some(b)) => {
487 if matches!(l, Value::Int64(_)) && matches!(r, Value::Int64(_)) {
488 Value::Int64(a as i64 - b as i64)
489 } else {
490 Value::Double(a - b)
491 }
492 }
493 _ => Value::Null,
494 },
495 BinaryOp::Multiply => match (as_f64(l), as_f64(r)) {
496 (Some(a), Some(b)) => {
497 if matches!(l, Value::Int64(_)) && matches!(r, Value::Int64(_)) {
498 Value::Int64(a as i64 * b as i64)
499 } else {
500 Value::Double(a * b)
501 }
502 }
503 _ => Value::Null,
504 },
505 BinaryOp::Divide => match (as_f64(l), as_f64(r)) {
506 (Some(a), Some(b)) if b != 0.0 => {
507 if matches!(l, Value::Int64(_)) && matches!(r, Value::Int64(_)) {
508 Value::Int64(a as i64 / b as i64)
509 } else {
510 Value::Double(a / b)
511 }
512 }
513 _ => Value::Null,
514 },
515 BinaryOp::Modulo => match (l, r) {
516 (Value::Int64(a), Value::Int64(b)) if *b != 0 => Value::Int64(a % b),
517 _ => Value::Null,
518 },
519 BinaryOp::Equal => Value::Bool(l == r),
520 BinaryOp::NotEqual => Value::Bool(l != r),
521 BinaryOp::LessThan => match (as_f64(l), as_f64(r)) {
522 (Some(a), Some(b)) => Value::Bool(a < b),
523 _ => Value::Null,
524 },
525 BinaryOp::LessThanOrEqual => match (as_f64(l), as_f64(r)) {
526 (Some(a), Some(b)) => Value::Bool(a <= b),
527 _ => Value::Null,
528 },
529 BinaryOp::GreaterThan => match (as_f64(l), as_f64(r)) {
530 (Some(a), Some(b)) => Value::Bool(a > b),
531 _ => Value::Null,
532 },
533 BinaryOp::GreaterThanOrEqual => match (as_f64(l), as_f64(r)) {
534 (Some(a), Some(b)) => Value::Bool(a >= b),
535 _ => Value::Null,
536 },
537 BinaryOp::And => match (l, r) {
538 (Value::Bool(a), Value::Bool(b)) => Value::Bool(*a && *b),
539 _ => Value::Null,
540 },
541 BinaryOp::Or => match (l, r) {
542 (Value::Bool(a), Value::Bool(b)) => Value::Bool(*a || *b),
543 _ => Value::Null,
544 },
545 _ => Value::Null,
546 }
547}
548
549pub fn evaluate_constant_expr(expr: &Expression, registry: &FunctionRegistry) -> Value {
555 match expr {
556 Expression::Constant(c) => ast_constant_to_value(c),
557 Expression::List(items) => Value::List(items.iter().map(|i| evaluate_constant_expr(i, registry)).collect()),
558 Expression::Map(items) => Value::Map(
559 items
560 .iter()
561 .map(|(k, v)| (Value::String(k.clone()), evaluate_constant_expr(v, registry)))
562 .collect(),
563 ),
564 Expression::FunctionCall(name, args) => {
565 let arg_values: Vec<Value> = args.iter().map(|a| evaluate_constant_expr(a, registry)).collect();
566 if arg_values.iter().any(|v| matches!(v, Value::Null)) {
567 return Value::Null;
568 }
569 let func = match registry.get_scalar(name).cloned() {
570 Some(f) => f,
571 None => return Value::Null,
572 };
573 evaluate_scalar(&func, &arg_values).unwrap_or(Value::Null)
574 }
575 _ => Value::Null,
576 }
577}