Skip to main content

uqa_storage/mvcc/
outcome.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Logical completion does not require allocating a physical write receipt.
8
9use super::{CommitErrorOutcome, SerializableTransactionId, StorageTransactionId};
10use crate::StorageBackendError;
11
12/// Identity retained by a session while resolving completion. A serializable participant can finish without publishing records.
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum TransactionOutcomeId {
15    Records(StorageTransactionId),
16    Serializable(SerializableTransactionId),
17}
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum TransactionOutcome {
21    Indeterminate(TransactionOutcomeId),
22    Committed(TransactionOutcomeId),
23    Aborted(TransactionOutcomeId),
24}
25
26impl TransactionOutcome {
27    pub const fn id(self) -> TransactionOutcomeId {
28        match self {
29            Self::Indeterminate(id) | Self::Committed(id) | Self::Aborted(id) => id,
30        }
31    }
32}
33
34impl From<CommitErrorOutcome> for TransactionOutcome {
35    fn from(outcome: CommitErrorOutcome) -> Self {
36        match outcome {
37            CommitErrorOutcome::Indeterminate(id) => {
38                Self::Indeterminate(TransactionOutcomeId::Records(id))
39            }
40            CommitErrorOutcome::Committed(receipt) => {
41                Self::Committed(TransactionOutcomeId::Records(receipt.transaction))
42            }
43            CommitErrorOutcome::Aborted(id) => Self::Aborted(TransactionOutcomeId::Records(id)),
44        }
45    }
46}
47
48/// Completion evidence takes precedence over a later checkpoint, cancellation or cleanup error. Keep any physical receipt alongside the logical identity for existing receipt consumers.
49#[derive(Debug, thiserror::Error)]
50#[error("transaction completion {outcome:?}: {source}")]
51pub struct TransactionCompletionError {
52    pub outcome: TransactionOutcome,
53    pub publication: Option<CommitErrorOutcome>,
54    #[source]
55    pub source: StorageBackendError,
56}
57
58impl StorageBackendError {
59    /// Inspect retained logical or physical completion through provider wrappers without parsing diagnostics. The outer completion owner has precedence over an earlier nested failure.
60    pub fn transaction_outcome(&self) -> Option<TransactionOutcome> {
61        let mut cause: Option<&(dyn std::error::Error + 'static)> = Some(self);
62        while let Some(error) = cause {
63            if let Some(error) = error.downcast_ref::<TransactionCompletionError>() {
64                return Some(error.outcome);
65            }
66            if let Some(outcome) = super::persistence::error_commit_outcome(error) {
67                return Some(outcome.into());
68            }
69            cause = error.source();
70        }
71        None
72    }
73}