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
58#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
60pub struct StorageMutationOrigin {
61 transaction: StorageTransactionId,
62 revision: u64,
63}
64
65impl StorageMutationOrigin {
66 pub(in crate::mvcc) const fn new(transaction: StorageTransactionId, revision: u64) -> Self {
67 Self {
68 transaction,
69 revision,
70 }
71 }
72 pub const fn transaction(self) -> StorageTransactionId {
73 self.transaction
74 }
75 pub const fn revision(self) -> u64 {
76 self.revision
77 }
78}
79
80pub type CommitFingerprint = [u8; 32];
81pub type RecordPage = BudgetedVec<ScannedRecord>;
82
83#[derive(Debug, Clone, Copy, PartialEq, Eq)]
84pub struct CommitReceipt {
85 pub transaction: StorageTransactionId,
86 pub sequence: CommitSequence,
87 pub fingerprint: CommitFingerprint,
88}
89
90#[derive(Debug, Clone, Copy, PartialEq, Eq)]
91pub enum CommitStatus {
92 Unknown,
94 Pending,
95 Aborted,
96 Committed(CommitReceipt),
97}
98
99#[derive(Debug, Clone, Copy, PartialEq, Eq)]
101pub enum CommitErrorOutcome {
102 Indeterminate(StorageTransactionId),
103 Committed(CommitReceipt),
104 Aborted(StorageTransactionId),
105}
106
107impl StorageBackendError {
108 pub fn commit_outcome(&self) -> Option<CommitErrorOutcome> {
110 let mut cause: Option<&(dyn std::error::Error + 'static)> = Some(self);
111 while let Some(error) = cause {
112 if let Some(outcome) = error
113 .downcast_ref::<super::TransactionCompletionError>()
114 .and_then(|error| error.publication)
115 .or_else(|| error_commit_outcome(error))
116 {
117 return Some(outcome);
118 }
119 cause = error.source();
120 }
121 None
122 }
123}
124
125pub(super) fn error_commit_outcome(
126 error: &(dyn std::error::Error + 'static),
127) -> Option<CommitErrorOutcome> {
128 if let Some(CommitFailure::Indeterminate { transaction, .. }) =
129 error.downcast_ref::<CommitFailure>()
130 {
131 Some(CommitErrorOutcome::Indeterminate(*transaction))
132 } else if let Some(VersionError::AlreadyCommitted(receipt)) =
133 error.downcast_ref::<VersionError>()
134 {
135 Some(CommitErrorOutcome::Committed(*receipt))
136 } else if let Some(VersionError::AlreadyAborted(transaction)) =
137 error.downcast_ref::<VersionError>()
138 {
139 Some(CommitErrorOutcome::Aborted(*transaction))
140 } else {
141 None
142 }
143}
144
145#[derive(Debug, thiserror::Error)]
146pub enum CommitFailure {
147 #[error(transparent)]
149 Rejected(#[from] VersionError),
150 #[error("commit outcome for {transaction:?} is indeterminate: {source}")]
151 Indeterminate {
152 transaction: StorageTransactionId,
153 #[source]
154 source: StorageBackendError,
155 },
156}
157
158pub type CommitResult = Result<CommitReceipt, CommitFailure>;
159
160pub trait VersionedPersistence: Send + Sync {
162 fn database_id(&self) -> DatabaseId;
163
164 fn resource_leases(&self) -> Option<&dyn super::ResourceLeaseProvider> {
166 None
167 }
168
169 fn serializable_coordinator(&self) -> Option<&dyn super::SerializableCoordinator> {
171 None
172 }
173
174 fn identifier_watermark(
176 &self,
177 namespace: &[u8],
178 control: &StorageReadControl,
179 ) -> VersionResult<Option<u64>>;
180
181 fn allocate_identifiers(
183 &self,
184 namespace: &[u8],
185 request: super::IdentifierRequest,
186 control: &StorageReadControl,
187 ) -> VersionResult<super::IdentifierAllocation>;
188
189 fn private_revision_scope(&self) -> Option<super::PrivateRevisionScope> {
191 None
192 }
193
194 fn graph_record_layout(&self) -> Option<&dyn super::GraphRecordLayout> {
195 None
196 }
197
198 fn occurrence_record_layout(&self) -> &dyn super::OccurrenceRecordLayout {
200 &crate::key_value::KeyValueOccurrenceRecords
201 }
202
203 fn notification_record_layout(&self) -> &dyn super::NotificationRecordLayout {
204 &crate::key_value::KeyValueNotificationRecords
205 }
206
207 fn maintenance_record_layout(&self) -> &dyn super::MaintenanceRecordLayout {
208 &crate::key_value::KeyValueMaintenanceRecords
209 }
210
211 fn ivf_record_layout(&self) -> &dyn super::IVFRecordLayout {
212 &crate::key_value::KeyValueIVFRecords
213 }
214
215 fn diskann_population_record_layout(
217 &self,
218 ) -> Option<&dyn super::DiskANNPopulationRecordLayout> {
219 None
220 }
221
222 fn hnsw_record_layout(&self) -> Option<&dyn super::HNSWRecordLayout> {
224 Some(&crate::key_value::KeyValueHNSWRecords)
225 }
226
227 fn allocate_transaction(
229 &self,
230 control: &StorageReadControl,
231 ) -> VersionResult<StorageTransactionId>;
232
233 fn allocate_managed_transaction(
235 &self,
236 control: &StorageReadControl,
237 ) -> VersionResult<super::RetainedTransactionAllocation> {
238 self.allocate_transaction(control)
239 .map(super::RetainedTransactionAllocation::untracked)
240 }
241
242 fn snapshot(
243 &self,
244 control: &StorageReadControl,
245 ) -> VersionResult<Arc<dyn CommittedRecordSnapshot>>;
246
247 fn commit_monitor_version(&self) -> VersionResult<Option<u64>> {
249 Ok(None)
250 }
251
252 fn reclaim_versions(&self, control: &StorageReadControl) -> VersionResult<u64>;
254
255 fn vector_field_guard_layout(&self) -> &dyn super::VectorFieldGuardLayout {
257 &crate::key_value::KeyValueVectorFieldGuards
258 }
259
260 fn reclaim_diskann_tombstones(&self, control: &StorageReadControl) -> VersionResult<()> {
262 super::reclaim_key_value_diskann_tombstones(self, control)
263 }
264
265 fn reclaim_tombstones(
267 &self,
268 _request: &super::TombstoneReclamationRequest<'_>,
269 _control: &StorageReadControl,
270 ) -> VersionResult<super::TombstoneReclamationStep> {
271 Err(VersionError::InvalidEncoding(
272 "scoped tombstone reclamation is not implemented by this provider",
273 ))
274 }
275
276 fn commit(
278 &self,
279 transaction: StorageTransactionId,
280 prepared: &PreparedRecordCommit,
281 control: &StorageReadControl,
282 ) -> CommitResult;
283
284 fn commit_status(
285 &self,
286 transaction: StorageTransactionId,
287 control: &StorageReadControl,
288 ) -> VersionResult<CommitStatus>;
289
290 fn acknowledge_transaction(
292 &self,
293 _acknowledgement: super::ReceiptAcknowledgement,
294 _control: &StorageReadControl,
295 ) -> VersionResult<()> {
296 Ok(())
297 }
298
299 fn reclaim_transaction_receipts(&self, _control: &StorageReadControl) -> VersionResult<u64> {
301 Ok(0)
302 }
303
304 fn abort(
306 &self,
307 transaction: StorageTransactionId,
308 control: &StorageReadControl,
309 ) -> VersionResult<CommitStatus>;
310}
311
312pub fn resolve_prepared_receipt(
314 status: CommitStatus,
315 transaction: StorageTransactionId,
316 fingerprint: CommitFingerprint,
317) -> VersionResult<Option<CommitReceipt>> {
318 match status {
319 CommitStatus::Pending => Ok(None),
320 CommitStatus::Committed(receipt)
321 if receipt.transaction == transaction && receipt.fingerprint == fingerprint =>
322 {
323 Ok(Some(receipt))
324 }
325 CommitStatus::Committed(_) => Err(VersionError::CommitMismatch),
326 CommitStatus::Aborted => Err(VersionError::TransactionFinished),
327 CommitStatus::Unknown => Err(VersionError::UnknownTransaction),
328 }
329}