feat(transactions): add single-node transaction support with session-aware reads, commits, aborts, and transaction metrics

This commit is contained in:
2026-04-29 22:14:46 +00:00
parent e79fe339aa
commit b72e8ed5e7
19 changed files with 913 additions and 77 deletions
@@ -7,6 +7,7 @@ use tracing::debug;
use crate::context::CommandContext;
use crate::error::{CommandError, CommandResult};
use crate::transactions;
/// Handle the `delete` command.
pub async fn handle(
@@ -36,6 +37,7 @@ pub async fn handle(
);
let ns_key = format!("{}.{}", db, coll);
let txn_id = transactions::active_transaction_id(ctx, cmd);
let mut total_deleted: i32 = 0;
let mut write_errors: Vec<Document> = Vec::new();
@@ -69,7 +71,7 @@ pub async fn handle(
_ => 0, // default: delete all matches
};
match delete_matching(db, coll, &ns_key, &filter, limit, ctx).await {
match delete_matching(db, coll, &ns_key, &filter, limit, ctx, txn_id.as_deref()).await {
Ok(count) => {
total_deleted += count;
}
@@ -114,7 +116,24 @@ async fn delete_matching(
filter: &Document,
limit: i32,
ctx: &CommandContext,
txn_id: Option<&str>,
) -> Result<i32, CommandError> {
if let Some(txn_id) = txn_id {
let docs = transactions::load_transaction_docs(ctx, txn_id, db, coll).await?;
let matched = QueryMatcher::filter(&docs, filter);
let to_delete: &[Document] = if limit == 1 && !matched.is_empty() {
&matched[..1]
} else {
&matched
};
for doc in to_delete {
transactions::record_delete(ctx, txn_id, db, coll, doc.clone()).await?;
}
return Ok(to_delete.len() as i32);
}
// Check if the collection exists; if not, nothing to delete.
match ctx.storage.collection_exists(db, coll).await {
Ok(false) => return Ok(0),