akar_processor/physical/write_ops/
delete.rs1use crate::physical::types::{OperatorResult, PhysicalOperatorExec};
3use akar_common::types::{PhysicalTypeID, Value};
4use akar_common::vector::{DataChunk, ValueVector};
5use akar_parser::ast::Constant;
6use akar_storage::table::TableCatalog;
7use akar_transaction::UndoRecord;
8use std::sync::{Arc, Mutex};
9
10pub struct PhysicalDelete {
14 pub table_name: String,
15 pub table_id: u64,
16 pub primary_key_column: String,
17 pub is_node: bool,
18 pub detach: bool,
19 pub row_indices: Vec<u64>,
21 pub table_catalog: Arc<TableCatalog>,
22 pub txn_id: Option<u64>,
24 pub undo_sink: Option<Arc<Mutex<Vec<UndoRecord>>>>,
26}
27
28impl PhysicalOperatorExec for PhysicalDelete {
29 fn operator_type(&self) -> &str {
30 "delete"
31 }
32
33 fn execute(&self, input: Vec<DataChunk>) -> OperatorResult {
34 let mut rows_to_delete: Vec<u64> = self.row_indices.clone();
36
37 for chunk in &input {
42 let row_id_col = row_id_column_index(chunk);
43 for row in 0..chunk.size {
44 if !chunk.fields.is_empty() {
45 if let Some(akar_common::types::Value::Int64(val)) = chunk.get_value(row_id_col.unwrap_or(0), row) {
46 rows_to_delete.push(val as u64);
47 }
48 }
49 }
50 }
51
52 if rows_to_delete.is_empty() {
53 let mut out = Vec::with_capacity(input.len());
58 for mut chunk in input {
59 chunk.resize(0);
60 chunk.sel_vector = None;
61 out.push(chunk);
62 }
63 return Ok(out);
64 }
65
66 let mut deleted = 0u64;
68 if self.is_node {
69 for &row_idx in &rows_to_delete {
70 if !self.detach && self.table_catalog.has_incident_edges(self.table_id, row_idx) {
71 return Err(format!(
72 "Cannot delete node {} because it has incident edges (use DETACH DELETE)",
73 row_idx
74 )
75 .into());
76 }
77 }
78 if self.detach {
79 for &row_idx in &rows_to_delete {
80 self.table_catalog.detach_node(self.table_id, row_idx);
81 }
82 }
83 if let Some(mut table) = self.table_catalog.get_node_table_by_name_mut(&self.table_name) {
84 for &row_idx in &rows_to_delete {
85 if let Some(sink) = self.undo_sink.as_ref()
88 && let Ok(mut u) = sink.lock()
89 {
90 let old_data = table.row_undo_bytes(row_idx);
91 u.push(UndoRecord::delete(self.table_id, row_idx, old_data));
92 }
93 if table.delete_row_with_txn(row_idx, self.txn_id).is_ok() {
94 deleted += 1;
95 }
96 }
97 } else {
98 return Err(format!("Node table '{}' not found for DELETE", self.table_name).into());
99 }
100 } else {
101 if let Some(mut table) = self.table_catalog.get_rel_table_by_name_mut(&self.table_name) {
102 for &edge_idx in &rows_to_delete {
103 if let Some(sink) = self.undo_sink.as_ref()
104 && let Ok(mut u) = sink.lock()
105 {
106 let old_data = table.edge_undo_bytes(edge_idx as usize);
107 u.push(UndoRecord::delete(self.table_id, edge_idx, old_data));
108 }
109 if table.delete_edge(edge_idx as usize).is_ok() {
110 deleted += 1;
111 }
112 }
113 } else {
114 return Err(format!("Rel table '{}' not found for DELETE", self.table_name).into());
115 }
116 }
117
118 tracing::info!("DELETE: removed {deleted} rows from '{}'", self.table_name);
119
120 let n = deleted as usize;
125 let mut v = ValueVector::new(PhysicalTypeID::Int64, n);
126 v.resize(n);
127 for i in 0..n {
128 v.set_i64(i, deleted as i64);
129 }
130 let arr = akar_common::arrow_vector::ArrowVector::from_legacy(&v).array;
131 Ok(vec![DataChunk::new(vec![arr], vec![PhysicalTypeID::Int64])])
132 }
133}
134
135pub fn ast_constant_to_value(c: &Constant) -> Value {
137 match c {
138 Constant::Null => Value::Null,
139 Constant::Bool(b) => Value::Bool(*b),
140 Constant::Integer(i) => Value::Int64(*i),
141 Constant::Float(f) => Value::Double(*f),
142 Constant::String(s) => Value::String(s.clone()),
143 }
144}
145
146pub fn row_id_column_index(chunk: &DataChunk) -> Option<usize> {
155 chunk.field_names.iter().position(|n| n == "_id" || n.ends_with("._id"))
156}