1use std::sync::Arc;
5
6use reifydb_catalog::error::{CatalogError, CatalogObjectKind};
7use reifydb_codec::row::bytes::{EncodedBytes, read_fingerprint};
8use reifydb_core::{
9 interface::{
10 catalog::{
11 config::{ConfigKey, GetConfig},
12 id::IndexId,
13 key::PrimaryKey,
14 namespace::Namespace,
15 object::ObjectId,
16 policy::{DataOp, PolicyTargetType},
17 table::Table,
18 },
19 resolved::{ResolvedNamespace, ResolvedObject, ResolvedTable},
20 },
21 internal_error,
22 key::{
23 any::TaggedKey,
24 catalog::IndexEntryKey,
25 row::{PartitionedRowKey, RowKeyRange},
26 },
27 value::column::columns::Columns,
28};
29use reifydb_evaluate::stack::SymbolTable;
30use reifydb_rql::{nodes::DeleteTableNode, query::QueryPlan};
31use reifydb_transaction::{multi::RangeScope, transaction::Transaction};
32use reifydb_value::{
33 fragment::Fragment,
34 params::Params,
35 value::{Value, partition::Partition, row_number::RowNumber},
36};
37
38use super::{
39 context::{TableTarget, WriteExecCtx},
40 primary_key,
41 returning::{decode_returning_dictionaries, decode_rows_to_columns, evaluate_returning, with_pre_image},
42 shape::get_or_create_table_shape,
43};
44use crate::{
45 Result,
46 error::EngineError,
47 partition::row_key_from_partition,
48 policy::PolicyEvaluator,
49 transaction::operation::table::TableOperations,
50 vm::{
51 services::Services,
52 volcano::{
53 compile::compile,
54 query::{QueryContext, QueryNode, query_budget},
55 },
56 },
57};
58
59pub(crate) fn delete(
60 services: &Arc<Services>,
61 txn: &mut Transaction<'_>,
62 plan: DeleteTableNode,
63 params: Params,
64 symbols: &SymbolTable,
65) -> Result<Columns> {
66 let DeleteTableNode {
67 input,
68 target,
69 returning,
70 } = plan;
71 let target = target.expect("DELETE without input requires explicit target table");
72 let (namespace, table) = resolve_delete_table_target(services, txn, &target)?;
73 let resolved_source = build_delete_table_resolved_source(&namespace, &table);
74 let target_data = TableTarget {
75 namespace: &namespace,
76 table: &table,
77 fragment: target.identifier(),
78 };
79
80 let exec = WriteExecCtx {
81 services,
82 symbols,
83 };
84 let (deleted_count, returned_rows) = if let Some(input_plan) = input {
85 run_table_delete_with_input(
86 &exec,
87 txn,
88 *input_plan,
89 &target_data,
90 &resolved_source,
91 ¶ms,
92 returning.is_some(),
93 )?
94 } else {
95 run_table_delete_all(services, txn, &table, returning.is_some())?
96 };
97
98 if let Some(returning_exprs) = &returning {
99 let shape = get_or_create_table_shape(&services.catalog, &table, txn)?;
100 let mut columns = decode_rows_to_columns(&shape, &returned_rows);
101 decode_returning_dictionaries(services, txn, &table.columns, &mut columns)?;
102 let columns = with_pre_image(columns.clone(), &columns);
103 return evaluate_returning(services, symbols, returning_exprs, columns, txn.identity());
104 }
105 Ok(delete_table_result(namespace.name(), &table.name, deleted_count))
106}
107
108#[inline]
109fn resolve_delete_table_target(
110 services: &Arc<Services>,
111 txn: &mut Transaction<'_>,
112 target: &ResolvedTable,
113) -> Result<(Namespace, Table)> {
114 let namespace_name = target.namespace().name();
115 let Some(namespace) = services.catalog.find_namespace_by_name(txn, namespace_name)? else {
116 return Err(CatalogError::NotFound {
117 kind: CatalogObjectKind::Namespace,
118 namespace: namespace_name.to_string(),
119 name: String::new(),
120 fragment: Fragment::internal(namespace_name),
121 }
122 .into());
123 };
124 let Some(table) = services.catalog.find_table_by_name(txn, namespace.id(), target.name())? else {
125 return Err(CatalogError::NotFound {
126 kind: CatalogObjectKind::Table,
127 namespace: namespace_name.to_string(),
128 name: target.name().to_string(),
129 fragment: target.identifier().clone(),
130 }
131 .into());
132 };
133 Ok((namespace, table))
134}
135
136#[inline]
137fn build_delete_table_resolved_source(namespace: &Namespace, table: &Table) -> Option<ResolvedObject> {
138 let namespace_ident = Fragment::internal(namespace.name());
139 let resolved_namespace = ResolvedNamespace::new(namespace_ident, namespace.clone());
140 let table_ident = Fragment::internal(table.name.clone());
141 let resolved_table = ResolvedTable::new(table_ident, resolved_namespace, table.clone());
142 Some(ResolvedObject::Table(resolved_table))
143}
144
145fn run_table_delete_with_input(
146 exec: &WriteExecCtx<'_>,
147 txn: &mut Transaction<'_>,
148 input_plan: QueryPlan,
149 target: &TableTarget<'_>,
150 resolved_source: &Option<ResolvedObject>,
151 params: &Params,
152 has_returning: bool,
153) -> Result<(u64, Vec<(RowNumber, EncodedBytes)>)> {
154 let context = QueryContext {
155 services: exec.services.clone(),
156 source: resolved_source.clone(),
157 batch_size: exec.services.catalog.get_config_uint2(ConfigKey::QueryRowBatchSize) as u64,
158 params: params.clone(),
159 symbols: exec.symbols.clone(),
160 identity: txn.identity(),
161 memory: query_budget(exec.services),
162 };
163 let mut input_node = compile(input_plan, txn, Arc::new(context.clone()));
164 input_node.initialize(txn, &context)?;
165
166 let (row_numbers_to_delete, partitions_to_delete) =
167 collect_rows_to_delete(exec, txn, &mut input_node, &context, target)?;
168
169 if !target.table.partition_by.is_empty() && partitions_to_delete.len() != row_numbers_to_delete.len() {
170 return Err(EngineError::MissingPartitionAddress {
171 object: ObjectId::Table(target.table.id),
172 operation: "DELETE",
173 }
174 .into());
175 }
176
177 let pk_def = primary_key::get_primary_key(&exec.services.catalog, txn, target.table)?;
178
179 let mut filtered_ids: Vec<RowNumber> = Vec::with_capacity(row_numbers_to_delete.len());
180 let mut filtered_partitions: Vec<Partition> = Vec::with_capacity(partitions_to_delete.len());
181 for (idx, row_number) in row_numbers_to_delete.into_iter().enumerate() {
182 let partition = partitions_to_delete.get(idx).copied();
183 let row_key = row_key_from_partition(target.table.id, partition, row_number);
184 let bytes = match txn.get(&row_key)? {
185 Some(v) => v.bytes,
186 None => continue,
187 };
188 if let Some(ref pk_def) = pk_def {
189 remove_table_pk_index_for(exec.services, txn, target.table, pk_def, &bytes)?;
190 }
191 filtered_ids.push(row_number);
192 if let Some(p) = partition {
193 filtered_partitions.push(p);
194 }
195 }
196
197 let removed = txn.remove_from_table(target.table, &filtered_ids, &filtered_partitions)?;
198 let deleted_count = removed.len() as u64;
199 let returned_rows: Vec<(RowNumber, EncodedBytes)> = if has_returning {
200 removed
201 } else {
202 Vec::new()
203 };
204 Ok((deleted_count, returned_rows))
205}
206
207fn collect_rows_to_delete(
208 exec: &WriteExecCtx<'_>,
209 txn: &mut Transaction<'_>,
210 input_node: &mut Box<dyn QueryNode>,
211 context: &QueryContext,
212 target: &TableTarget<'_>,
213) -> Result<(Vec<RowNumber>, Vec<Partition>)> {
214 let mut row_numbers_to_delete = Vec::new();
215 let mut partitions_to_delete = Vec::new();
216 let mut mutable_context = context.clone();
217 while let Some(columns) = input_node.next(txn, &mut mutable_context)? {
218 PolicyEvaluator::new(exec.services, exec.symbols).enforce_write_policies(
219 txn,
220 target.namespace.name(),
221 &target.table.name,
222 DataOp::Delete,
223 &columns,
224 PolicyTargetType::Table,
225 )?;
226 if columns.row_numbers().is_empty() {
227 return Err(EngineError::MissingRowNumberColumn.into());
228 }
229 let row_numbers = &columns.row_numbers();
230 for row_idx in 0..columns.row_count() {
231 row_numbers_to_delete.push(row_numbers[row_idx]);
232 if !columns.partitions().is_empty() {
233 partitions_to_delete.push(columns.partitions()[row_idx]);
234 }
235 }
236 }
237 Ok((row_numbers_to_delete, partitions_to_delete))
238}
239
240fn run_table_delete_all(
241 services: &Arc<Services>,
242 txn: &mut Transaction<'_>,
243 table: &Table,
244 has_returning: bool,
245) -> Result<(u64, Vec<(RowNumber, EncodedBytes)>)> {
246 let partitioned = !table.partition_by.is_empty();
247 let range = if partitioned {
248 PartitionedRowKey::full_scan(table.id)
249 } else {
250 RowKeyRange::storage_scan(table.id.into())
251 };
252 let pk_def = primary_key::get_primary_key(&services.catalog, txn, table)?;
253 let rows: Vec<_> = txn.range(range, RangeScope::All, 32)?.collect::<Result<Vec<_>>>()?;
254
255 let mut filtered_ids: Vec<RowNumber> = Vec::with_capacity(rows.len());
256 let mut filtered_partitions: Vec<Partition> = Vec::with_capacity(rows.len());
257 for multi in rows {
258 if let Some(ref pk_def) = pk_def {
259 remove_table_pk_index_for(services, txn, table, pk_def, &multi.bytes)?;
260 }
261 if partitioned {
262 let TaggedKey::PartitionedRow(key) = multi.key else {
263 panic!("valid PartitionedRowKey encoding");
264 };
265 filtered_ids.push(key.row);
266 filtered_partitions.push(key.partition);
267 } else {
268 let TaggedKey::Row(row_key) = multi.key else {
269 panic!("valid RowKey encoding");
270 };
271 filtered_ids.push(row_key.row);
272 }
273 }
274
275 let removed = txn.remove_from_table(table, &filtered_ids, &filtered_partitions)?;
276 let deleted_count = removed.len() as u64;
277 let returned_rows: Vec<(RowNumber, EncodedBytes)> = if has_returning {
278 removed
279 } else {
280 Vec::new()
281 };
282 Ok((deleted_count, returned_rows))
283}
284
285#[inline]
286fn remove_table_pk_index_for(
287 services: &Arc<Services>,
288 txn: &mut Transaction<'_>,
289 table: &Table,
290 pk_def: &PrimaryKey,
291 values: &EncodedBytes,
292) -> Result<()> {
293 let fingerprint = read_fingerprint(values);
294 let shape = services.catalog.get_or_load_row_shape(fingerprint, txn)?.ok_or_else(|| {
295 internal_error!("Row shape with fingerprint {:?} not found for table {}", fingerprint, table.name)
296 })?;
297 let index_key = primary_key::encode_primary_key(pk_def, values, table, &shape)?;
298 txn.remove(&IndexEntryKey::new(table.id, IndexId::primary(pk_def.id), index_key))?;
299 Ok(())
300}
301
302#[inline]
303fn delete_table_result(namespace: &str, table: &str, deleted: u64) -> Columns {
304 Columns::single_row([
305 ("namespace", Value::Utf8(namespace.to_string())),
306 ("table", Value::Utf8(table.to_string())),
307 ("deleted", Value::Uint8(deleted)),
308 ])
309}