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 graph_record_layout(&self) -> Option<&dyn super::GraphRecordLayout> {
190 None
191 }
192
193 fn occurrence_record_layout(&self) -> &dyn super::OccurrenceRecordLayout {
195 &crate::key_value::KeyValueOccurrenceRecords
196 }
197
198 fn notification_record_layout(&self) -> &dyn super::NotificationRecordLayout {
199 &crate::key_value::KeyValueNotificationRecords
200 }
201
202 fn maintenance_record_layout(&self) -> &dyn super::MaintenanceRecordLayout {
203 &crate::key_value::KeyValueMaintenanceRecords
204 }
205
206 fn ivf_record_layout(&self) -> &dyn super::IVFRecordLayout {
207 &crate::key_value::KeyValueIVFRecords
208 }
209
210 fn diskann_population_record_layout(
212 &self,
213 ) -> Option<&dyn super::DiskANNPopulationRecordLayout> {
214 None
215 }
216
217 fn hnsw_record_layout(&self) -> Option<&dyn super::HNSWRecordLayout> {
219 Some(&crate::key_value::KeyValueHNSWRecords)
220 }
221
222 fn allocate_transaction(
224 &self,
225 control: &StorageReadControl,
226 ) -> VersionResult<StorageTransactionId>;
227
228 fn allocate_managed_transaction(
230 &self,
231 control: &StorageReadControl,
232 ) -> VersionResult<super::RetainedTransactionAllocation> {
233 self.allocate_transaction(control)
234 .map(super::RetainedTransactionAllocation::untracked)
235 }
236
237 fn snapshot(
238 &self,
239 control: &StorageReadControl,
240 ) -> VersionResult<Arc<dyn CommittedRecordSnapshot>>;
241
242 fn commit_monitor_version(&self) -> VersionResult<Option<u64>> {
244 Ok(None)
245 }
246
247 fn reclaim_versions(&self, control: &StorageReadControl) -> VersionResult<u64>;
249
250 fn vector_field_guard_layout(&self) -> &dyn super::VectorFieldGuardLayout {
252 &crate::key_value::KeyValueVectorFieldGuards
253 }
254
255 fn reclaim_diskann_tombstones(&self, control: &StorageReadControl) -> VersionResult<()> {
257 super::reclaim_key_value_diskann_tombstones(self, control)
258 }
259
260 fn reclaim_tombstones(
262 &self,
263 _request: &super::TombstoneReclamationRequest<'_>,
264 _control: &StorageReadControl,
265 ) -> VersionResult<super::TombstoneReclamationStep> {
266 Err(VersionError::InvalidEncoding(
267 "scoped tombstone reclamation is not implemented by this provider",
268 ))
269 }
270
271 fn commit(
273 &self,
274 transaction: StorageTransactionId,
275 prepared: &PreparedRecordCommit,
276 control: &StorageReadControl,
277 ) -> CommitResult;
278
279 fn commit_status(
280 &self,
281 transaction: StorageTransactionId,
282 control: &StorageReadControl,
283 ) -> VersionResult<CommitStatus>;
284
285 fn acknowledge_transaction(
287 &self,
288 _acknowledgement: super::ReceiptAcknowledgement,
289 _control: &StorageReadControl,
290 ) -> VersionResult<()> {
291 Ok(())
292 }
293
294 fn reclaim_transaction_receipts(&self, _control: &StorageReadControl) -> VersionResult<u64> {
296 Ok(0)
297 }
298
299 fn abort(
301 &self,
302 transaction: StorageTransactionId,
303 control: &StorageReadControl,
304 ) -> VersionResult<CommitStatus>;
305}
306
307pub fn resolve_prepared_receipt(
309 status: CommitStatus,
310 transaction: StorageTransactionId,
311 fingerprint: CommitFingerprint,
312) -> VersionResult<Option<CommitReceipt>> {
313 match status {
314 CommitStatus::Pending => Ok(None),
315 CommitStatus::Committed(receipt)
316 if receipt.transaction == transaction && receipt.fingerprint == fingerprint =>
317 {
318 Ok(Some(receipt))
319 }
320 CommitStatus::Committed(_) => Err(VersionError::CommitMismatch),
321 CommitStatus::Aborted => Err(VersionError::TransactionFinished),
322 CommitStatus::Unknown => Err(VersionError::UnknownTransaction),
323 }
324}