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}
39
40impl PhysicalOperatorExec for PhysicalSet {
41 fn operator_type(&self) -> &str {
42 "set"
43 }
44
45 fn execute(&self, input: Vec<DataChunk>) -> OperatorResult {
46 let mut rows_to_update: Vec<u64> = Vec::new();
50 let mut source_rows: Vec<(usize, usize)> = Vec::new();
54
55 for (ci, chunk) in input.iter().enumerate() {
56 let row_id_col = row_id_column_index(chunk);
57 for row in 0..chunk.size {
58 if !chunk.fields.is_empty()
59 && let Some(Value::Int64(val)) = chunk.get_value(row_id_col.unwrap_or(0), row)
60 {
61 rows_to_update.push(val as u64);
62 source_rows.push((ci, row));
63 }
64 }
65 }
66
67 if rows_to_update.is_empty() {
68 return Ok(vec![count_chunk(0)]);
69 }
70
71 let snapshot = self.build_snapshot_chunk(&rows_to_update, &input, &source_rows)?;
73
74 let mut all_values: Vec<Vec<Value>> = Vec::with_capacity(self.items.len());
79 for item in &self.items {
80 all_values.push(self.evaluate_item(item, &snapshot)?);
81 }
82
83 let mut updated = 0u64;
85 if self.is_node {
86 if let Some(mut table) = self.table_catalog.get_node_table_by_name_mut(&self.table_name) {
87 for (item_idx, item) in self.items.iter().enumerate() {
88 let col_idx = table
92 .columns
93 .iter()
94 .position(|c| c.name == item.column_name)
95 .unwrap_or(item.column_idx);
96 for (i, row_idx) in rows_to_update.iter().enumerate() {
97 if let Some(sink) = self.undo_sink.as_ref()
99 && let Ok(mut u) = sink.lock()
100 {
101 let old_data = table.cell_undo_bytes(*row_idx, col_idx);
102 u.push(UndoRecord::update(self.table_id, *row_idx, col_idx as u32, old_data));
103 }
104 if table
105 .update_cell(*row_idx, col_idx, all_values[item_idx][i].clone())
106 .is_ok()
107 {
108 updated += 1;
109 }
110 }
111 }
112 } else {
113 return Err(format!("Node table '{}' not found for SET", self.table_name).into());
114 }
115 } else {
116 if let Some(mut table) = self.table_catalog.get_rel_table_by_name_mut(&self.table_name) {
117 for (item_idx, item) in self.items.iter().enumerate() {
118 let col_idx = table
119 .columns
120 .iter()
121 .position(|c| c.name == item.column_name)
122 .unwrap_or(item.column_idx);
123 for (i, edge_idx) in rows_to_update.iter().enumerate() {
124 if let Some(sink) = self.undo_sink.as_ref()
125 && let Ok(mut u) = sink.lock()
126 {
127 let old_data = table.edge_cell_undo_bytes(*edge_idx as usize, col_idx);
128 u.push(UndoRecord::update(self.table_id, *edge_idx, col_idx as u32, old_data));
129 }
130 if table
131 .update_cell(*edge_idx as usize, col_idx, all_values[item_idx][i].clone())
132 .is_ok()
133 {
134 updated += 1;
135 }
136 }
137 }
138 } else {
139 return Err(format!("Rel table '{}' not found for SET", self.table_name).into());
140 }
141 }
142
143 tracing::info!("SET: updated {updated} rows in '{}'", self.table_name);
144
145 let mut output = self.build_output_chunk(&rows_to_update, &input, &source_rows)?;
150 let mut count_v = ValueVector::new(PhysicalTypeID::Int64, rows_to_update.len());
151 count_v.resize(rows_to_update.len());
152 for i in 0..rows_to_update.len() {
153 count_v.set_i64(i, updated as i64);
154 }
155 output
156 .fields
157 .insert(0, akar_common::arrow_vector::ArrowVector::from_legacy(&count_v).array);
158 output.field_types.insert(0, PhysicalTypeID::Int64);
159 output.field_names.insert(0, String::new());
160 Ok(vec![output])
161 }
162}
163
164fn count_chunk(count: u64) -> DataChunk {
165 let mut v = ValueVector::new(PhysicalTypeID::Int64, 1);
166 v.resize(1);
167 v.set_i64(0, count as i64);
168 let arr = akar_common::arrow_vector::ArrowVector::from_legacy(&v).array;
169 DataChunk::new(vec![arr], vec![PhysicalTypeID::Int64])
170}
171
172impl PhysicalSet {
173 fn build_snapshot_chunk(
178 &self,
179 rows: &[u64],
180 input: &[DataChunk],
181 source_rows: &[(usize, usize)],
182 ) -> Result<DataChunk, String> {
183 let mut snapshot = if self.is_node {
184 let table = self
185 .table_catalog
186 .get_node_table_by_name(&self.table_name)
187 .ok_or_else(|| format!("Node table '{}' not found for SET", self.table_name))?;
188 build_old_row_chunk(&table.columns, rows, &|row, col| {
189 table.get_value(row as usize, col).cloned()
190 })?
191 } else {
192 let table = self
193 .table_catalog
194 .get_rel_table_by_name(&self.table_name)
195 .ok_or_else(|| format!("Rel table '{}' not found for SET", self.table_name))?;
196 build_old_row_chunk(&table.columns, rows, &|row, col| {
197 table.get_edge_properties(row as usize).get(col).cloned()
198 })?
199 };
200 append_pipeline_columns(&mut snapshot, input, source_rows)?;
201 Ok(snapshot)
202 }
203}
204
205pub(crate) fn append_pipeline_columns(
210 snapshot: &mut DataChunk,
211 input: &[DataChunk],
212 source_rows: &[(usize, usize)],
213) -> Result<(), String> {
214 let n = source_rows.len();
215 for (ci, chunk) in input.iter().enumerate() {
216 let mut appended: Vec<(usize, String)> = Vec::new();
217 for (col_idx, name) in chunk.field_names.iter().enumerate() {
218 if name == "_id" || name.ends_with("._id") {
219 continue;
220 }
221 if snapshot.field_names.iter().any(|existing| existing == name) {
222 continue;
223 }
224 if let Some((_, base)) = name.rsplit_once('.') {
230 if snapshot.field_names.iter().any(|existing| existing == base) {
231 continue;
232 }
233 }
234 appended.push((col_idx, name.clone()));
235 }
236 if appended.is_empty() {
237 continue;
238 }
239
240 for (col_idx, name) in appended {
241 let mut col_values: Vec<Value> = vec![Value::Null; n];
243 for (i, &(cci, rowi)) in source_rows.iter().enumerate() {
244 if cci == ci {
245 col_values[i] = chunk.get_value(col_idx, rowi).unwrap_or(Value::Null);
246 }
247 }
248 let phys_type = col_values
251 .iter()
252 .find(|v| !matches!(v, Value::Null))
253 .map(|v| v.physical_type())
254 .unwrap_or(chunk.field_types[col_idx]);
255 let arr = crate::expression_evaluator::build_arrow_from_values(&col_values, phys_type, n)
256 .map_err(|e| e.to_string())?;
257 snapshot.fields.push(arr.array);
258 snapshot.field_types.push(arr.physical_type);
259 snapshot.field_names.push(name);
260 }
261 }
262 Ok(())
263}
264
265impl PhysicalSet {
266 fn build_output_chunk(
273 &self,
274 rows: &[u64],
275 input: &[DataChunk],
276 source_rows: &[(usize, usize)],
277 ) -> Result<DataChunk, String> {
278 let mut chunk = if self.is_node {
279 let table = self
280 .table_catalog
281 .get_node_table_by_name(&self.table_name)
282 .ok_or_else(|| format!("Node table '{}' not found for SET", self.table_name))?;
283 build_old_row_chunk(&table.columns, rows, &|row, col| {
284 table.get_value(row as usize, col).cloned()
285 })?
286 } else {
287 let table = self
288 .table_catalog
289 .get_rel_table_by_name(&self.table_name)
290 .ok_or_else(|| format!("Rel table '{}' not found for SET", self.table_name))?;
291 build_old_row_chunk(&table.columns, rows, &|row, col| {
292 table.get_edge_properties(row as usize).get(col).cloned()
293 })?
294 };
295 append_pipeline_columns(&mut chunk, input, source_rows)?;
296 let mut v = ValueVector::new(PhysicalTypeID::Int64, rows.len());
299 v.resize(rows.len());
300 for (i, r) in rows.iter().enumerate() {
301 v.set_i64(i, *r as i64);
302 }
303 chunk
304 .fields
305 .push(akar_common::arrow_vector::ArrowVector::from_legacy(&v).array);
306 chunk.field_types.push(PhysicalTypeID::Int64);
307 chunk.field_names.push("_id".to_string());
308 Ok(chunk)
309 }
310
311 fn evaluate_item(&self, item: &SetItem, chunk: &DataChunk) -> Result<Vec<Value>, String> {
314 if let Some(registry) = self.function_registry.as_ref() {
315 let evaluator = ExpressionEvaluator::new(registry.clone());
316 let vec = evaluator
320 .evaluate_to_arrow(&item.value, chunk)
321 .map_err(|e| e.to_string())?;
322 Ok((0..chunk.size)
323 .map(|i| vec.get_value(i).unwrap_or(Value::Null))
324 .collect())
325 } else {
326 Ok((0..chunk.size)
327 .map(|i| evaluate_expression_for_row(&item.value, chunk, i))
328 .collect())
329 }
330 }
331}
332
333pub(crate) fn build_old_row_chunk(
342 columns: &[ColumnDefinition],
343 rows: &[u64],
344 get_cell: &dyn Fn(u64, usize) -> Option<Value>,
345) -> Result<DataChunk, String> {
346 let n = rows.len();
347 let mut fields = Vec::with_capacity(columns.len());
348 let mut field_types = Vec::with_capacity(columns.len());
349 let mut field_names = Vec::with_capacity(columns.len());
350 for (col_idx, col) in columns.iter().enumerate() {
351 let phys_type = physical_type_from_logical(col.logical_type);
352 let col_values: Vec<Value> = rows
353 .iter()
354 .map(|row| get_cell(*row, col_idx).unwrap_or(Value::Null))
355 .collect();
356 let arr = crate::expression_evaluator::build_arrow_from_values(&col_values, phys_type, n)
357 .map_err(|e| e.to_string())?;
358 fields.push(arr.array);
359 field_types.push(phys_type);
360 field_names.push(col.name.clone());
361 }
362 Ok(DataChunk::new(fields, field_types).with_names(field_names))
363}
364
365pub fn evaluate_expression_for_row(
367 expr: &akar_parser::ast::Expression,
368 chunk: &DataChunk,
369 row: usize,
370) -> akar_common::types::Value {
371 match expr {
372 akar_parser::ast::Expression::Constant(c) => match c {
373 akar_parser::ast::Constant::Null => akar_common::types::Value::Null,
374 akar_parser::ast::Constant::Bool(b) => akar_common::types::Value::Bool(*b),
375 akar_parser::ast::Constant::Integer(i) => akar_common::types::Value::Int64(*i),
376 akar_parser::ast::Constant::Float(f) => akar_common::types::Value::Double(*f),
377 akar_parser::ast::Constant::String(s) => akar_common::types::Value::String(s.clone()),
378 },
379 akar_parser::ast::Expression::Variable(name) => chunk
380 .field_names
381 .iter()
382 .position(|n| n == name)
383 .and_then(|i| chunk.get_value(i, row))
384 .unwrap_or(akar_common::types::Value::Null),
385 akar_parser::ast::Expression::PropertyAccess(obj, prop) => {
386 let qualified = match obj.as_ref() {
387 akar_parser::ast::Expression::Variable(var) => format!("{var}.{prop}"),
388 _ => prop.clone(),
389 };
390 if let Some(i) = chunk.field_names.iter().position(|n| *n == qualified) {
391 chunk.get_value(i, row).unwrap_or(akar_common::types::Value::Null)
392 } else if let akar_parser::ast::Expression::Variable(var) = obj.as_ref()
393 && let Some(i) = chunk.field_names.iter().position(|n| n == var)
394 {
395 let obj_val = chunk.get_value(i, row).unwrap_or(akar_common::types::Value::Null);
399 crate::expression_evaluator::map_property_value(&obj_val, prop)
400 } else if let Some(i) = chunk.field_names.iter().position(|n| *n == *prop) {
401 chunk.get_value(i, row).unwrap_or(akar_common::types::Value::Null)
402 } else {
403 akar_common::types::Value::Null
404 }
405 }
406 akar_parser::ast::Expression::UnaryOp(op, inner) => {
407 let v = evaluate_expression_for_row(inner, chunk, row);
408 match op {
409 akar_parser::ast::UnaryOp::Negate => match v {
410 akar_common::types::Value::Int64(n) => akar_common::types::Value::Int64(-n),
411 akar_common::types::Value::Double(n) => akar_common::types::Value::Double(-n),
412 _ => akar_common::types::Value::Null,
413 },
414 _ => akar_common::types::Value::Null,
415 }
416 }
417 akar_parser::ast::Expression::BinaryOp(op, left, right) => {
418 let l = evaluate_expression_for_row(left, chunk, row);
419 let r = evaluate_expression_for_row(right, chunk, row);
420 binary_value_op(op, &l, &r)
421 }
422 akar_parser::ast::Expression::List(items) => {
423 let vals = items
424 .iter()
425 .map(|i| evaluate_expression_for_row(i, chunk, row))
426 .collect();
427 akar_common::types::Value::List(vals)
428 }
429 akar_parser::ast::Expression::Map(items) => {
430 let entries = items
431 .iter()
432 .map(|(k, v)| {
433 (
434 akar_common::types::Value::String(k.clone()),
435 evaluate_expression_for_row(v, chunk, row),
436 )
437 })
438 .collect();
439 akar_common::types::Value::Map(entries)
440 }
441 _ => akar_common::types::Value::Null,
442 }
443}
444
445fn as_f64(v: &Value) -> Option<f64> {
446 match v {
447 Value::Int64(n) => Some(*n as f64),
448 Value::Int32(n) => Some(*n as f64),
449 Value::Int128(n) => Some(*n as f64),
450 Value::Double(n) => Some(*n),
451 Value::Float(n) => Some(*n as f64),
452 _ => None,
453 }
454}
455
456fn binary_value_op(op: &BinaryOp, l: &Value, r: &Value) -> Value {
460 match op {
461 BinaryOp::Add => match (l, r) {
462 (Value::String(a), Value::String(b)) => Value::String(format!("{a}{b}")),
463 _ => match (as_f64(l), as_f64(r)) {
464 (Some(a), Some(b)) => {
465 if matches!(l, Value::Int64(_)) && matches!(r, Value::Int64(_)) {
466 Value::Int64(a as i64 + b as i64)
467 } else {
468 Value::Double(a + b)
469 }
470 }
471 _ => Value::Null,
472 },
473 },
474 BinaryOp::Subtract => 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 BinaryOp::Multiply => match (as_f64(l), as_f64(r)) {
485 (Some(a), Some(b)) => {
486 if matches!(l, Value::Int64(_)) && matches!(r, Value::Int64(_)) {
487 Value::Int64(a as i64 * b as i64)
488 } else {
489 Value::Double(a * b)
490 }
491 }
492 _ => Value::Null,
493 },
494 BinaryOp::Divide => match (as_f64(l), as_f64(r)) {
495 (Some(a), Some(b)) if b != 0.0 => {
496 if matches!(l, Value::Int64(_)) && matches!(r, Value::Int64(_)) {
497 Value::Int64(a as i64 / b as i64)
498 } else {
499 Value::Double(a / b)
500 }
501 }
502 _ => Value::Null,
503 },
504 BinaryOp::Modulo => match (l, r) {
505 (Value::Int64(a), Value::Int64(b)) if *b != 0 => Value::Int64(a % b),
506 _ => Value::Null,
507 },
508 BinaryOp::Equal => Value::Bool(l == r),
509 BinaryOp::NotEqual => Value::Bool(l != r),
510 BinaryOp::LessThan => match (as_f64(l), as_f64(r)) {
511 (Some(a), Some(b)) => Value::Bool(a < b),
512 _ => Value::Null,
513 },
514 BinaryOp::LessThanOrEqual => match (as_f64(l), as_f64(r)) {
515 (Some(a), Some(b)) => Value::Bool(a <= b),
516 _ => Value::Null,
517 },
518 BinaryOp::GreaterThan => match (as_f64(l), as_f64(r)) {
519 (Some(a), Some(b)) => Value::Bool(a > b),
520 _ => Value::Null,
521 },
522 BinaryOp::GreaterThanOrEqual => match (as_f64(l), as_f64(r)) {
523 (Some(a), Some(b)) => Value::Bool(a >= b),
524 _ => Value::Null,
525 },
526 BinaryOp::And => match (l, r) {
527 (Value::Bool(a), Value::Bool(b)) => Value::Bool(*a && *b),
528 _ => Value::Null,
529 },
530 BinaryOp::Or => match (l, r) {
531 (Value::Bool(a), Value::Bool(b)) => Value::Bool(*a || *b),
532 _ => Value::Null,
533 },
534 _ => Value::Null,
535 }
536}
537
538pub fn evaluate_constant_expr(expr: &Expression, registry: &FunctionRegistry) -> Value {
544 match expr {
545 Expression::Constant(c) => ast_constant_to_value(c),
546 Expression::List(items) => Value::List(items.iter().map(|i| evaluate_constant_expr(i, registry)).collect()),
547 Expression::Map(items) => Value::Map(
548 items
549 .iter()
550 .map(|(k, v)| (Value::String(k.clone()), evaluate_constant_expr(v, registry)))
551 .collect(),
552 ),
553 Expression::FunctionCall(name, args) => {
554 let arg_values: Vec<Value> = args.iter().map(|a| evaluate_constant_expr(a, registry)).collect();
555 if arg_values.iter().any(|v| matches!(v, Value::Null)) {
556 return Value::Null;
557 }
558 let func = match registry.get_scalar(name).cloned() {
559 Some(f) => f,
560 None => return Value::Null,
561 };
562 evaluate_scalar(&func, &arg_values).unwrap_or(Value::Null)
563 }
564 _ => Value::Null,
565 }
566}