haematite-cli 0.21.7

The haematite command: serve a store, its door and Studio, answer MCP, query with jq, and host the store's assistant seat
Documentation
//! Bounded whole-list deletion reuses the single-document cascade.

use std::collections::{BTreeMap, BTreeSet};

use super::mutations::PreparedWrite;
use super::{Captured, Failure, Storage, identifier, reads};
use crate::serve::protocol::{Change, DocumentDelete, DocumentDeleteMany, Version};

impl Storage {
    pub(super) fn prepare_delete_many(
        &self,
        at: &Captured,
        delete: &DocumentDeleteMany,
        next: Version,
    ) -> Result<Vec<PreparedWrite>, Failure> {
        validate(&self.reader, at, delete)?;
        let mut writes = BTreeMap::new();
        for document_id in &delete.document_ids {
            let change = Change::Delete(DocumentDelete {
                collection: delete.collection.clone(),
                document_id: document_id.clone(),
            });
            // Adjacent deleted records and self-edges share cascade keys.
            writes.extend(self.prepare_change(at, &change, next)?);
        }
        Ok(writes.into_iter().collect())
    }
}

fn validate(
    reader: &haematite::db::ReadOnlyDatabase,
    at: &Captured,
    delete: &DocumentDeleteMany,
) -> Result<(), Failure> {
    if delete.document_ids.is_empty() {
        return Err(Failure {
            path: Some("/document_ids".to_owned()),
            ..Failure::new(
                "import_empty",
                "a delete batch must contain at least one document",
            )
        });
    }
    reads::collection(reader, at, &delete.collection)
        .map_err(|failure| Failure {
            path: Some("/collection".to_owned()),
            ..failure
        })?
        .ok_or_else(|| Failure {
            path: Some("/collection".to_owned()),
            ..Failure::new("collection_missing", &delete.collection)
        })?;
    let mut ids = BTreeSet::new();
    for (index, id) in delete.document_ids.iter().enumerate() {
        let in_row = |failure: Failure| Failure {
            path: Some(format!("/document_ids/{index}")),
            ..failure
        };
        identifier(id).map_err(in_row)?;
        if !ids.insert(id) {
            return Err(in_row(Failure::new(
                "import_duplicate",
                "repeated document_id in delete batch",
            )));
        }
        if !reads::document_exists_for_constraint(reader, at, &delete.collection, id)
            .map_err(in_row)?
        {
            return Err(in_row(Failure::new("document_missing", id)));
        }
    }
    Ok(())
}