akar_processor/physical/write_ops/
insert.rs1use crate::physical::types::{OperatorResult, PhysicalOperatorExec};
4use crate::physical::write_ops::set::evaluate_expression_for_row;
5use akar_common::types::{PhysicalTypeID, Value};
6use akar_common::vector::{DataChunk, ValueVector};
7use akar_storage::table::TableCatalog;
8use akar_transaction::UndoRecord;
9use std::sync::{Arc, Mutex};
10
11pub struct PhysicalInsertNode {
13 pub table_name: String,
14 pub table_id: u64,
15 pub out_var_name: String,
16 pub properties: Vec<(String, akar_parser::ast::Expression)>,
17 pub table_catalog: Arc<TableCatalog>,
18 pub txn_id: Option<u64>,
20 pub undo_sink: Option<Arc<Mutex<Vec<UndoRecord>>>>,
22}
23
24impl PhysicalOperatorExec for PhysicalInsertNode {
25 fn operator_type(&self) -> &str {
26 "insert_node"
27 }
28
29 fn execute(&self, input: Vec<DataChunk>) -> OperatorResult {
30 if !input.is_empty() && input.iter().all(|c| c.size == 0) {
33 return Ok(vec![DataChunk::new(vec![], vec![])]);
34 }
35
36 let mut assigned_row_ids: Vec<i64> = Vec::new();
37 let mut table = self
38 .table_catalog
39 .get_node_table_by_name_mut(&self.table_name)
40 .ok_or_else(|| format!("Node table '{}' not found for INSERT", self.table_name))?;
41
42 let chunks = if input.is_empty() {
45 let mut v = ValueVector::new(PhysicalTypeID::Int64, 1);
46 v.resize(1);
47 v.set_i64(0, 0);
48 let arr = akar_common::arrow_vector::ArrowVector::from_legacy(&v).array;
49 vec![DataChunk::new(vec![arr], vec![PhysicalTypeID::Int64])]
50 } else {
51 input
52 };
53
54 let num_cols = table.columns.len();
55 for chunk in &chunks {
56 for row in 0..chunk.size {
57 let mut row_values = vec![Value::Null; num_cols];
58
59 for (prop_name, expr) in &self.properties {
60 if let Some(col_idx) = table.columns.iter().position(|c| c.name == *prop_name) {
61 let val = evaluate_expression_for_row(expr, chunk, row);
62 row_values[col_idx] = val;
63 }
64 }
65
66 let row_id = table
70 .insert_row_with_txn(row_values, self.txn_id)
71 .map_err(|e| format!("INSERT NODE row {row} failed in '{}': {e}", self.table_name))?;
72 assigned_row_ids.push(row_id as i64);
73 if let Some(sink) = self.undo_sink.as_ref()
74 && let Ok(mut u) = sink.lock()
75 {
76 u.push(UndoRecord::insert(self.table_id, row_id));
77 }
78 }
79 }
80
81 let inserted_count = assigned_row_ids.len();
82 tracing::info!("INSERT NODE: added {inserted_count} rows to '{}'", self.table_name);
83
84 let mut count_v = ValueVector::new(PhysicalTypeID::Int64, 1);
85 count_v.resize(1);
86 count_v.set_i64(0, inserted_count as i64);
87 let arr_count = akar_common::arrow_vector::ArrowVector::from_legacy(&count_v).array;
88
89 let mut ids_v = ValueVector::new(PhysicalTypeID::Int64, assigned_row_ids.len());
91 ids_v.resize(assigned_row_ids.len());
92 for (i, rid) in assigned_row_ids.iter().enumerate() {
93 ids_v.set_i64(i, *rid);
94 }
95 let arr_ids = akar_common::arrow_vector::ArrowVector::from_legacy(&ids_v).array;
96
97 Ok(vec![DataChunk::new(
98 vec![arr_count, arr_ids],
99 vec![PhysicalTypeID::Int64, PhysicalTypeID::Int64],
100 )])
101 }
102}
103
104pub struct PhysicalInsertRel {
106 pub table_name: String,
107 pub table_id: u64,
108 pub src_node_name: String,
109 pub dst_node_name: String,
110 pub properties: Vec<(String, akar_parser::ast::Expression)>,
111 pub table_catalog: Arc<TableCatalog>,
112 pub txn_id: Option<u64>,
114 pub undo_sink: Option<Arc<Mutex<Vec<UndoRecord>>>>,
116}
117
118impl PhysicalOperatorExec for PhysicalInsertRel {
119 fn operator_type(&self) -> &str {
120 "insert_rel"
121 }
122
123 fn execute(&self, input: Vec<DataChunk>) -> OperatorResult {
124 let mut inserted_count = 0u64;
125 let mut table = self
126 .table_catalog
127 .get_rel_table_by_name_mut(&self.table_name)
128 .ok_or_else(|| format!("Rel table '{}' not found for INSERT", self.table_name))?;
129
130 if !input.is_empty() && input.iter().all(|c| c.size == 0) {
133 return Ok(vec![DataChunk::new(vec![], vec![])]);
134 }
135
136 let num_cols = table.columns.len();
137 let mut rels_to_insert = Vec::new();
138
139 for chunk in &input {
140 let src_name_id = format!("{}.{}", self.src_node_name, "_id");
141 let src_name_pk = format!("{}.{}", self.src_node_name, "id");
142 let src_name_pk_upper = format!("{}.{}", self.src_node_name, "ID");
143 let src_node_col_idx = chunk
144 .field_names
145 .iter()
146 .position(|name| name == &src_name_id)
147 .or_else(|| chunk.field_names.iter().position(|name| name == &self.src_node_name))
148 .or_else(|| chunk.field_names.iter().position(|name| name == &src_name_pk))
149 .or_else(|| chunk.field_names.iter().position(|name| name == &src_name_pk_upper))
150 .or_else(|| {
151 chunk
152 .field_names
153 .iter()
154 .position(|name| name.eq_ignore_ascii_case(&src_name_pk))
155 })
156 .ok_or_else(|| {
157 format!(
158 "Source node variable {} not found (fields: {:?})",
159 self.src_node_name, chunk.field_names
160 )
161 })?;
162
163 let dst_name_id = format!("{}.{}", self.dst_node_name, "_id");
164 let dst_name_pk = format!("{}.{}", self.dst_node_name, "id");
165 let dst_name_pk_upper = format!("{}.{}", self.dst_node_name, "ID");
166 let dst_node_col_idx = chunk
167 .field_names
168 .iter()
169 .position(|name| name == &dst_name_id)
170 .or_else(|| chunk.field_names.iter().position(|name| name == &self.dst_node_name))
171 .or_else(|| chunk.field_names.iter().position(|name| name == &dst_name_pk))
172 .or_else(|| chunk.field_names.iter().position(|name| name == &dst_name_pk_upper))
173 .or_else(|| {
174 chunk
175 .field_names
176 .iter()
177 .position(|name| name.eq_ignore_ascii_case(&dst_name_pk))
178 })
179 .ok_or_else(|| {
180 format!(
181 "Destination node variable {} not found (fields: {:?})",
182 self.dst_node_name, chunk.field_names
183 )
184 })?;
185
186 if src_node_col_idx >= chunk.fields.len() || dst_node_col_idx >= chunk.fields.len() {
187 return Err("Src/Dst node column index out of bounds in INSERT REL".into());
188 }
189
190 for row in 0..chunk.size {
191 let src_id = if let Some(Value::Int64(val)) = chunk.get_value(src_node_col_idx, row) {
192 val as u64
193 } else {
194 0
195 };
196 let dst_id = if let Some(Value::Int64(val)) = chunk.get_value(dst_node_col_idx, row) {
197 val as u64
198 } else {
199 0
200 };
201
202 let mut props = vec![Value::Null; num_cols];
203 for (prop_name, expr) in &self.properties {
204 if let Some(col_idx) = table.columns.iter().position(|c| c.name == *prop_name) {
205 let val = evaluate_expression_for_row(expr, chunk, row);
206 props[col_idx] = val;
207 }
208 }
209
210 rels_to_insert.push((src_id, dst_id, props));
211 }
212 }
213
214 if !rels_to_insert.is_empty() {
216 inserted_count = table
217 .insert_rels_batch(&rels_to_insert)
218 .map_err(|e| format!("BatchInsert rel error: {e}"))?;
219 if let Some(sink) = self.undo_sink.as_ref()
220 && let Ok(mut u) = sink.lock()
221 {
222 let num_edges = table.edges.len();
223 for idx in (num_edges - inserted_count as usize)..num_edges {
224 u.push(UndoRecord::insert(self.table_id, idx as u64));
225 }
226 }
227 }
228
229 tracing::info!("INSERT REL: added {inserted_count} rels to '{}'", self.table_name);
230
231 let mut v = ValueVector::new(PhysicalTypeID::Int64, 1);
232 v.resize(1);
233 v.set_i64(0, inserted_count as i64);
234 let arr = akar_common::arrow_vector::ArrowVector::from_legacy(&v).array;
235 Ok(vec![DataChunk::new(vec![arr], vec![PhysicalTypeID::Int64])])
236 }
237}