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_storage::wal::{WalSink, log_delete_record};
8use akar_transaction::UndoRecord;
9use std::sync::{Arc, Mutex};
10
11pub struct PhysicalDelete {
15 pub table_name: String,
16 pub table_id: u64,
17 pub primary_key_column: String,
18 pub is_node: bool,
19 pub detach: bool,
20 pub row_indices: Vec<u64>,
22 pub table_catalog: Arc<TableCatalog>,
23 pub txn_id: Option<u64>,
25 pub undo_sink: Option<Arc<Mutex<Vec<UndoRecord>>>>,
27 pub wal_sink: Option<WalSink>,
29}
30
31impl PhysicalOperatorExec for PhysicalDelete {
32 fn operator_type(&self) -> &str {
33 "delete"
34 }
35
36 fn execute(&self, input: Vec<DataChunk>) -> OperatorResult {
37 let mut rows_to_delete: Vec<u64> = self.row_indices.clone();
39
40 for chunk in &input {
45 let row_id_col = row_id_column_index(chunk);
46 for row in 0..chunk.size {
47 if !chunk.fields.is_empty() {
48 if let Some(akar_common::types::Value::Int64(val)) = chunk.get_value(row_id_col.unwrap_or(0), row) {
49 rows_to_delete.push(val as u64);
50 }
51 }
52 }
53 }
54
55 if rows_to_delete.is_empty() {
56 let mut out = Vec::with_capacity(input.len());
61 for mut chunk in input {
62 chunk.resize(0);
63 chunk.sel_vector = None;
64 out.push(chunk);
65 }
66 return Ok(out);
67 }
68
69 let mut deleted = 0u64;
71 if self.is_node {
72 for &row_idx in &rows_to_delete {
73 if !self.detach && self.table_catalog.has_incident_edges(self.table_id, row_idx) {
74 return Err(format!(
75 "Cannot delete node {} because it has incident edges (use DETACH DELETE)",
76 row_idx
77 )
78 .into());
79 }
80 }
81 if self.detach {
82 for rel in self.table_catalog.all_rel_tables() {
87 if rel.src_table_id != self.table_id && rel.dst_table_id != self.table_id {
88 continue;
89 }
90 let rel_id = *rel.key();
91 for &row_idx in &rows_to_delete {
92 if rel.src_table_id == self.table_id
93 && let Some(edges) = rel.fwd_adj.get(&row_idx)
94 {
95 for &(_, edge_idx) in edges.iter() {
96 log_delete_record(&self.wal_sink, rel_id, edge_idx as u64);
97 }
98 }
99 if rel.dst_table_id == self.table_id
100 && let Some(edges) = rel.rev_adj.get(&row_idx)
101 {
102 for &(_, edge_idx) in edges.iter() {
103 log_delete_record(&self.wal_sink, rel_id, edge_idx as u64);
104 }
105 }
106 }
107 }
108 for &row_idx in &rows_to_delete {
109 self.table_catalog.detach_node(self.table_id, row_idx);
110 }
111 }
112 if let Some(mut table) = self.table_catalog.get_node_table_by_name_mut(&self.table_name) {
113 for &row_idx in &rows_to_delete {
114 if let Some(sink) = self.undo_sink.as_ref()
117 && let Ok(mut u) = sink.lock()
118 {
119 let old_data = table.row_undo_bytes(row_idx);
120 u.push(UndoRecord::delete(self.table_id, row_idx, old_data));
121 }
122 if table.delete_row_with_txn(row_idx, self.txn_id).is_ok() {
123 deleted += 1;
124 log_delete_record(&self.wal_sink, self.table_id, row_idx);
125 }
126 }
127 } else {
128 return Err(format!("Node table '{}' not found for DELETE", self.table_name).into());
129 }
130 } else {
131 if let Some(mut table) = self.table_catalog.get_rel_table_by_name_mut(&self.table_name) {
132 for &edge_idx in &rows_to_delete {
133 if let Some(sink) = self.undo_sink.as_ref()
134 && let Ok(mut u) = sink.lock()
135 {
136 let old_data = table.edge_undo_bytes(edge_idx as usize);
137 u.push(UndoRecord::delete(self.table_id, edge_idx, old_data));
138 }
139 if table.delete_edge(edge_idx as usize).is_ok() {
140 deleted += 1;
141 log_delete_record(&self.wal_sink, self.table_id, edge_idx);
142 }
143 }
144 } else {
145 return Err(format!("Rel table '{}' not found for DELETE", self.table_name).into());
146 }
147 }
148
149 tracing::info!("DELETE: removed {deleted} rows from '{}'", self.table_name);
150
151 let n = deleted as usize;
156 let mut v = ValueVector::new(PhysicalTypeID::Int64, n);
157 v.resize(n);
158 for i in 0..n {
159 v.set_i64(i, deleted as i64);
160 }
161 let arr = akar_common::arrow_vector::ArrowVector::from_legacy(&v).array;
162 Ok(vec![DataChunk::new(vec![arr], vec![PhysicalTypeID::Int64])])
163 }
164}
165
166pub fn ast_constant_to_value(c: &Constant) -> Value {
168 match c {
169 Constant::Null => Value::Null,
170 Constant::Bool(b) => Value::Bool(*b),
171 Constant::Integer(i) => Value::Int64(*i),
172 Constant::Float(f) => Value::Double(*f),
173 Constant::String(s) => Value::String(s.clone()),
174 }
175}
176
177pub fn row_id_column_index(chunk: &DataChunk) -> Option<usize> {
186 chunk.field_names.iter().position(|n| n == "_id" || n.ends_with("._id"))
187}