1use std::sync::Arc;
10
11use uqa_core::memory::BudgetedVec;
12
13use crate::read_control::StorageReadControl;
14use crate::StorageBackendError;
15
16use super::{
17 CommitSequence, CommittedRecordSnapshot, PreparedRecordCommit, ScannedRecord, VersionError,
18 VersionResult,
19};
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
22pub struct DatabaseId([u8; 16]);
23
24impl DatabaseId {
25 pub const fn from_bytes(bytes: [u8; 16]) -> Self {
26 Self(bytes)
27 }
28 pub const fn as_bytes(self) -> [u8; 16] {
29 self.0
30 }
31}
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
35pub struct StorageTransactionId {
36 database: DatabaseId,
37 allocation: u64,
38}
39
40impl StorageTransactionId {
41 pub fn new(database: DatabaseId, allocation: u64) -> VersionResult<Self> {
42 if allocation == 0 {
43 return Err(VersionError::InvalidTransactionId);
44 }
45 Ok(Self {
46 database,
47 allocation,
48 })
49 }
50 pub const fn database(self) -> DatabaseId {
51 self.database
52 }
53 pub const fn allocation(self) -> u64 {
54 self.allocation
55 }
56}
57
58pub type CommitFingerprint = [u8; 32];
59pub type RecordPage = BudgetedVec<ScannedRecord>;
60
61#[derive(Debug, Clone, Copy, PartialEq, Eq)]
62pub struct CommitReceipt {
63 pub transaction: StorageTransactionId,
64 pub sequence: CommitSequence,
65 pub fingerprint: CommitFingerprint,
66}
67
68#[derive(Debug, Clone, Copy, PartialEq, Eq)]
69pub enum CommitStatus {
70 Unknown,
72 Pending,
73 Aborted,
74 Committed(CommitReceipt),
75}
76
77#[derive(Debug, Clone, Copy, PartialEq, Eq)]
79pub enum CommitErrorOutcome {
80 Indeterminate(StorageTransactionId),
81 Committed(CommitReceipt),
82 Aborted(StorageTransactionId),
83}
84
85impl StorageBackendError {
86 pub fn commit_outcome(&self) -> Option<CommitErrorOutcome> {
88 let mut cause: Option<&(dyn std::error::Error + 'static)> = Some(self);
89 while let Some(error) = cause {
90 if let Some(outcome) = error
91 .downcast_ref::<super::TransactionCompletionError>()
92 .and_then(|error| error.publication)
93 .or_else(|| error_commit_outcome(error))
94 {
95 return Some(outcome);
96 }
97 cause = error.source();
98 }
99 None
100 }
101}
102
103pub(super) fn error_commit_outcome(
104 error: &(dyn std::error::Error + 'static),
105) -> Option<CommitErrorOutcome> {
106 if let Some(CommitFailure::Indeterminate { transaction, .. }) =
107 error.downcast_ref::<CommitFailure>()
108 {
109 Some(CommitErrorOutcome::Indeterminate(*transaction))
110 } else if let Some(VersionError::AlreadyCommitted(receipt)) =
111 error.downcast_ref::<VersionError>()
112 {
113 Some(CommitErrorOutcome::Committed(*receipt))
114 } else if let Some(VersionError::AlreadyAborted(transaction)) =
115 error.downcast_ref::<VersionError>()
116 {
117 Some(CommitErrorOutcome::Aborted(*transaction))
118 } else {
119 None
120 }
121}
122
123#[derive(Debug, thiserror::Error)]
124pub enum CommitFailure {
125 #[error(transparent)]
127 Rejected(#[from] VersionError),
128 #[error("commit outcome for {transaction:?} is indeterminate: {source}")]
129 Indeterminate {
130 transaction: StorageTransactionId,
131 #[source]
132 source: StorageBackendError,
133 },
134}
135
136pub type CommitResult = Result<CommitReceipt, CommitFailure>;
137
138pub trait VersionedPersistence: Send + Sync {
140 fn database_id(&self) -> DatabaseId;
141
142 fn serializable_coordinator(&self) -> Option<&dyn super::SerializableCoordinator> {
144 None
145 }
146
147 fn identifier_watermark(
149 &self,
150 namespace: &[u8],
151 control: &StorageReadControl,
152 ) -> VersionResult<Option<u64>>;
153
154 fn allocate_identifiers(
156 &self,
157 namespace: &[u8],
158 request: super::IdentifierRequest,
159 control: &StorageReadControl,
160 ) -> VersionResult<super::IdentifierAllocation>;
161
162 fn graph_record_layout(&self) -> Option<&dyn super::GraphRecordLayout> {
163 None
164 }
165
166 fn occurrence_record_layout(&self) -> &dyn super::OccurrenceRecordLayout {
168 &crate::key_value::KeyValueOccurrenceRecords
169 }
170
171 fn notification_record_layout(&self) -> &dyn super::NotificationRecordLayout {
172 &crate::key_value::KeyValueNotificationRecords
173 }
174
175 fn maintenance_record_layout(&self) -> &dyn super::MaintenanceRecordLayout {
176 &crate::key_value::KeyValueMaintenanceRecords
177 }
178
179 fn ivf_record_layout(&self) -> &dyn super::IVFRecordLayout {
180 &crate::key_value::KeyValueIVFRecords
181 }
182
183 fn hnsw_record_layout(&self) -> Option<&dyn super::HNSWRecordLayout> {
185 Some(&crate::key_value::KeyValueHNSWRecords)
186 }
187
188 fn allocate_transaction(
190 &self,
191 control: &StorageReadControl,
192 ) -> VersionResult<StorageTransactionId>;
193
194 fn allocate_managed_transaction(
196 &self,
197 control: &StorageReadControl,
198 ) -> VersionResult<super::RetainedTransactionAllocation> {
199 self.allocate_transaction(control)
200 .map(super::RetainedTransactionAllocation::untracked)
201 }
202
203 fn snapshot(
204 &self,
205 control: &StorageReadControl,
206 ) -> VersionResult<Arc<dyn CommittedRecordSnapshot>>;
207
208 fn reclaim_versions(&self, control: &StorageReadControl) -> VersionResult<u64>;
210
211 fn commit(
213 &self,
214 transaction: StorageTransactionId,
215 prepared: &PreparedRecordCommit,
216 control: &StorageReadControl,
217 ) -> CommitResult;
218
219 fn commit_status(
220 &self,
221 transaction: StorageTransactionId,
222 control: &StorageReadControl,
223 ) -> VersionResult<CommitStatus>;
224
225 fn acknowledge_transaction(
227 &self,
228 _acknowledgement: super::ReceiptAcknowledgement,
229 _control: &StorageReadControl,
230 ) -> VersionResult<()> {
231 Ok(())
232 }
233
234 fn reclaim_transaction_receipts(&self, _control: &StorageReadControl) -> VersionResult<u64> {
236 Ok(0)
237 }
238
239 fn abort(
241 &self,
242 transaction: StorageTransactionId,
243 control: &StorageReadControl,
244 ) -> VersionResult<CommitStatus>;
245}
246
247pub fn resolve_prepared_receipt(
249 status: CommitStatus,
250 transaction: StorageTransactionId,
251 fingerprint: CommitFingerprint,
252) -> VersionResult<Option<CommitReceipt>> {
253 match status {
254 CommitStatus::Pending => Ok(None),
255 CommitStatus::Committed(receipt)
256 if receipt.transaction == transaction && receipt.fingerprint == fingerprint =>
257 {
258 Ok(Some(receipt))
259 }
260 CommitStatus::Committed(_) => Err(VersionError::CommitMismatch),
261 CommitStatus::Aborted => Err(VersionError::TransactionFinished),
262 CommitStatus::Unknown => Err(VersionError::UnknownTransaction),
263 }
264}