Bulk delete: scan documents matching filters, delete all matches. Cascades to inverted index, secondary indexes, and graph edges. When `returning` is `None`, returns affected row count as JSON payload: `{"affected": N}`. When `returning` is `Some(spec)`, returns a `RowsPayload` with the pre-deletion documents.
(
&mut self,
task: &ExecutionTask,
tid: u64,
collection: &str,
filter_bytes: &[u8],
returning: Option<&ReturningSpec>,
ollp_predicted_surrogates
| 324 | /// When `returning` is `None`, returns affected row count as JSON payload: `{"affected": N}`. |
| 325 | /// When `returning` is `Some(spec)`, returns a `RowsPayload` with the pre-deletion documents. |
| 326 | pub(in crate::data::executor) fn execute_bulk_delete( |
| 327 | &mut self, |
| 328 | task: &ExecutionTask, |
| 329 | tid: u64, |
| 330 | collection: &str, |
| 331 | filter_bytes: &[u8], |
| 332 | returning: Option<&ReturningSpec>, |
| 333 | ollp_predicted_surrogates: Option<&[u32]>, |
| 334 | ) -> Response { |
| 335 | debug!(core = self.core_id, %collection, has_returning = returning.is_some(), "bulk delete"); |
| 336 | |
| 337 | // Empty `filter_bytes` means "no WHERE clause" — match every row. |
| 338 | let filters: Vec<ScanFilter> = if filter_bytes.is_empty() { |
| 339 | Vec::new() |
| 340 | } else { |
| 341 | match zerompk::from_msgpack(filter_bytes) { |
| 342 | Ok(f) => f, |
| 343 | Err(e) => { |
| 344 | return self.response_error( |
| 345 | task, |
| 346 | ErrorCode::Internal { |
| 347 | detail: format!("deserialize filters: {e}"), |
| 348 | }, |
| 349 | ); |
| 350 | } |
| 351 | } |
| 352 | }; |
| 353 | |
| 354 | let matching_ids = match self.scan_matching_documents(tid, collection, &filters) { |
| 355 | Ok(ids) => ids, |
| 356 | Err(e) => { |
| 357 | return self.response_error( |
| 358 | task, |
| 359 | ErrorCode::Internal { |
| 360 | detail: e.to_string(), |
| 361 | }, |
| 362 | ); |
| 363 | } |
| 364 | }; |
| 365 | |
| 366 | // OLLP verification: when predicted surrogates are provided, compare |
| 367 | // against the actual matching set. On mismatch return OllpRetryRequired |
| 368 | // WITHOUT writing. The set comparison is deterministic: both sides are |
| 369 | // sorted before comparison. |
| 370 | if let Some(predicted) = ollp_predicted_surrogates { |
| 371 | let actual = ollp_actual_surrogates(&matching_ids); |
| 372 | let mut predicted_sorted: Vec<u32> = predicted.to_vec(); |
| 373 | predicted_sorted.sort_unstable(); |
| 374 | if actual != predicted_sorted { |
| 375 | return self.response_error(task, ErrorCode::OllpRetryRequired); |
| 376 | } |
| 377 | } |
| 378 | |
| 379 | // Delete each matching document with full cascade. |
| 380 | let mut affected = 0u64; |
| 381 | let mut returned_docs: Vec<serde_json::Value> = if returning.is_some() { |
| 382 | Vec::with_capacity(matching_ids.len()) |
| 383 | } else { |
no test coverage detected