akar_processor/physical/write_ops/
merge.rs1use crate::physical::types::{OperatorResult, PhysicalOperatorExec};
4use crate::physical::write_ops::set::PhysicalSet;
5use akar_common::types::{PhysicalTypeID, Value};
6use akar_common::vector::{DataChunk, ValueVector};
7use akar_storage::table::TableCatalog;
8use std::sync::Arc;
9
10pub struct PhysicalMerge {
13 pub table_name: String,
14 pub table_id: u64,
15 pub properties: Vec<(String, akar_parser::ast::Expression)>,
16 pub on_match: Vec<PhysicalSet>,
17 pub on_create: Vec<PhysicalSet>,
18 pub table_catalog: Arc<TableCatalog>,
19}
20
21impl PhysicalOperatorExec for PhysicalMerge {
22 fn operator_type(&self) -> &str {
23 "merge"
24 }
25
26 fn execute(&self, _input: Vec<DataChunk>) -> OperatorResult {
27 let mut _merged_count: u64 = 0;
28
29 let eval_const = |expr: &akar_parser::ast::Expression, chunk: Option<&DataChunk>, row: usize| -> Value {
31 if let Some(c) = chunk {
32 crate::physical::write_ops::set::evaluate_expression_for_row(expr, c, row)
33 } else {
34 match expr {
35 akar_parser::ast::Expression::Constant(c) => match c {
36 akar_parser::ast::Constant::Null => Value::Null,
37 akar_parser::ast::Constant::Bool(b) => Value::Bool(*b),
38 akar_parser::ast::Constant::Integer(i) => Value::Int64(*i),
39 akar_parser::ast::Constant::Float(f) => Value::Double(*f),
40 akar_parser::ast::Constant::String(s) => Value::String(s.clone()),
41 },
42 _ => Value::Null,
43 }
44 }
45 };
46
47 let num_cols = {
49 let tbl = self
50 .table_catalog
51 .get_node_table_by_name(&self.table_name)
52 .ok_or_else(|| format!("Table '{}' not found for MERGE", self.table_name))?;
53 tbl.columns.len()
54 };
55
56 let chunks = if _input.is_empty() {
58 let mut v = ValueVector::new(PhysicalTypeID::Int64, 1);
59 v.resize(1);
60 v.set_i64(0, 0);
61 let arr = akar_common::arrow_vector::ArrowVector::from_legacy(&v).array;
62 vec![DataChunk::new(vec![arr], vec![PhysicalTypeID::Int64])]
63 } else {
64 _input
65 };
66
67 for chunk in &chunks {
68 for row in 0..chunk.size {
69 let table_info = self
70 .table_catalog
71 .get_node_table_by_name(&self.table_name)
72 .ok_or_else(|| format!("Table '{}' not found", self.table_name))?;
73
74 let mut pk_val = None;
76 for col_idx in 0..num_cols {
77 let col = &table_info.columns[col_idx];
78 if col.is_primary_key {
79 if let Some((_, expr)) = self.properties.iter().find(|(n, _)| n == &col.name) {
80 let val = eval_const(expr, Some(chunk), row);
81 pk_val = Some(val);
82 }
83 }
84 }
85
86 let is_node = true; let mut matched = false;
88
89 if is_node {
90 if let Some(val) = &pk_val {
91 let row_ids = table_info.lookup_by_pk_range(Some(val), true, Some(val), true, 1);
92 if !row_ids.is_empty() {
93 matched = true;
94 }
95 } else {
96 if let Some((prop_name, expr)) = self.properties.first() {
100 let first_val = eval_const(expr, Some(chunk), row);
101 if let Some(prop_col) = table_info.columns.iter().position(|c| &c.name == prop_name) {
102 for row_idx in 0..table_info.num_rows as usize {
104 if let Some(val) = table_info.get_value(row_idx, prop_col) {
105 if val == &first_val {
106 matched = true;
107 break;
108 }
109 }
110 }
111 }
112 }
113 }
114 } else {
115 }
117 drop(table_info);
118
119 if matched {
120 for set_op in &self.on_match {
121 let single_chunk = DataChunk::new(chunk.fields.clone(), chunk.field_types.clone()); let _ = set_op.execute(vec![single_chunk])?;
123 }
124 } else {
125 let table_info = self.table_catalog.get_node_table_by_name(&self.table_name).unwrap();
126 let mut new_values: Vec<Value> = Vec::new();
127 for col_idx in 0..num_cols {
128 let col_name = &table_info.columns[col_idx].name;
129 if let Some((_, expr)) = self.properties.iter().find(|(n, _)| n == col_name) {
130 new_values.push(eval_const(expr, Some(chunk), row));
131 } else if table_info.columns[col_idx].is_primary_key {
132 return Err(format!("MERGE CREATE requires primary key '{}'", col_name).into());
133 } else {
134 new_values.push(Value::Null);
135 }
136 }
137 drop(table_info);
138
139 if let Some(mut tbl) = self.table_catalog.get_node_table_by_name_mut(&self.table_name) {
140 tbl.insert_row(new_values)
141 .map_err(|e| format!("MERGE CREATE failed: {e}"))?;
142 _merged_count += 1;
143 }
144
145 for set_op in &self.on_create {
146 let single_chunk = DataChunk::new(chunk.fields.clone(), chunk.field_types.clone());
147 let _ = set_op.execute(vec![single_chunk])?;
148 }
149 }
150 }
151 }
152
153 tracing::info!("MERGE: processed merges in '{}'", self.table_name);
154
155 let mut v = ValueVector::new(PhysicalTypeID::Int64, 1);
156 v.resize(1);
157 v.set_i64(0, _merged_count as i64);
158 let arr = akar_common::arrow_vector::ArrowVector::from_legacy(&v).array;
159 Ok(vec![DataChunk::new(vec![arr], vec![PhysicalTypeID::Int64])])
160 }
161}