uqa_storage/document_store/
identifiers.rs1use std::num::NonZeroU64;
10
11use crate::mvcc::{IdentifierAllocator, IdentifierRequest, ObservedIdentifier};
12use crate::{CatalogFacade, KeyValueBatch, StorageBackendError, StorageBackendResult};
13
14pub mod conformance;
15
16#[cfg(test)]
17mod tests;
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub struct DocumentIdNamespace {
22 pub object: [u8; 16],
23 pub generation: [u8; 16],
24}
25
26pub struct DocumentIdAllocator<'a> {
28 durable: Option<&'a dyn IdentifierAllocator>,
29 namespace: [u8; 41],
30}
31
32impl<'a> DocumentIdAllocator<'a> {
33 pub fn new(
34 durable: Option<&'a dyn IdentifierAllocator>,
35 object: [u8; 16],
36 generation: [u8; 16],
37 ) -> StorageBackendResult<Self> {
38 if durable.is_some() {
39 validate_identity(object, generation)?;
40 }
41 let namespace = document_namespace(object, generation);
42 Ok(Self { durable, namespace })
43 }
44
45 pub fn is_durable(&self) -> bool {
46 self.durable.is_some()
47 }
48
49 pub fn allocate(&self, next: &mut u128) -> StorageBackendResult<u64> {
51 let minimum = u64::try_from(*next)
52 .map_err(|_| StorageBackendError::Other("document id space is exhausted".into()))?;
53 let id = match self.durable {
54 Some(allocator) => allocator
55 .allocate_identifiers(
56 &self.namespace,
57 IdentifierRequest::Reserve {
58 minimum,
59 maximum: u64::MAX,
60 count: NonZeroU64::MIN,
61 },
62 )?
63 .watermark(),
64 None => minimum,
65 };
66 *next = u128::from(id) + 1;
67 Ok(id)
68 }
69
70 pub fn observe(next: &mut u128, id: u64) -> StorageBackendResult<()> {
72 watermark_identity(*next)?;
73 *next = (*next).max(u128::from(id) + 1);
74 Ok(())
75 }
76
77 pub fn observe_durably(&self, id: u64) -> StorageBackendResult<ObservedIdentifier> {
79 match self.durable {
80 Some(allocator) => allocator.observe_identifier(&self.namespace, id),
81 None => Ok(ObservedIdentifier::Covered),
82 }
83 }
84
85 pub fn synchronize(&self, next: &mut u128) -> StorageBackendResult<()> {
87 let observed = watermark_identity(*next)?;
88 if let Some(allocator) = self.durable {
89 let allocated = allocator
90 .allocate_identifiers(&self.namespace, IdentifierRequest::Observe(observed))?;
91 *next = u128::from(allocated.watermark()) + 1;
92 }
93 Ok(())
94 }
95
96 pub fn persist(
98 &self,
99 catalog: &dyn CatalogFacade,
100 table: &str,
101 next: &mut u128,
102 ) -> StorageBackendResult<()> {
103 self.synchronize(next)?;
104 let key = legacy_document_id_metadata_key(table);
105 if self.is_durable() {
106 if catalog
107 .get_metadata(&key)?
108 .is_some_and(|value| !value.is_empty())
109 {
110 catalog.set_metadata(&key, "")?;
111 }
112 Ok(())
113 } else {
114 catalog.set_metadata(&key, &next.to_string())
115 }
116 }
117}
118
119fn watermark_identity(next: u128) -> StorageBackendResult<u64> {
121 next.checked_sub(1)
122 .and_then(|value| u64::try_from(value).ok())
123 .ok_or_else(|| StorageBackendError::Other("invalid document id watermark".into()))
124}
125
126pub fn observe_document_id(
128 batch: &mut dyn KeyValueBatch,
129 object: [u8; 16],
130 generation: [u8; 16],
131 id: u64,
132) -> StorageBackendResult<()> {
133 validate_identity(object, generation)?;
134 batch.observe_identifier(&document_namespace(object, generation), id)
135}
136
137pub fn inherit_document_ids(
139 batch: &mut dyn KeyValueBatch,
140 from: ([u8; 16], [u8; 16]),
141 to: ([u8; 16], [u8; 16]),
142) -> StorageBackendResult<()> {
143 validate_identity(from.0, from.1)?;
144 validate_identity(to.0, to.1)?;
145 batch.inherit_identifiers(
146 &document_namespace(from.0, from.1),
147 &document_namespace(to.0, to.1),
148 )
149}
150
151fn validate_identity(object: [u8; 16], generation: [u8; 16]) -> StorageBackendResult<()> {
152 if object == [0; 16] || generation == [0; 16] {
153 return Err(StorageBackendError::Other(
154 "document allocation requires nonzero table and storage identities".into(),
155 ));
156 }
157 Ok(())
158}
159
160fn document_namespace(object: [u8; 16], generation: [u8; 16]) -> [u8; 41] {
161 let mut namespace = [0; 41];
162 namespace[..9].copy_from_slice(b"document\x01");
163 namespace[9..25].copy_from_slice(&object);
164 namespace[25..].copy_from_slice(&generation);
165 namespace
166}
167
168pub fn legacy_document_id_metadata_key(table: &str) -> String {
169 format!("uqa.table_next_id.v1:{table}")
170}
171
172pub fn restored_document_id_watermark(maximum: u64, reserved_next: Option<u128>) -> u128 {
174 reserved_next.unwrap_or(1).max(u128::from(maximum) + 1)
175}
176
177pub fn load_legacy_document_id_watermark(
178 catalog: &dyn CatalogFacade,
179 table: &str,
180) -> StorageBackendResult<Option<u128>> {
181 let Some(value) = catalog.get_metadata(&legacy_document_id_metadata_key(table))? else {
182 return Ok(None);
183 };
184 if value.is_empty() {
185 return Ok(None);
186 }
187 value.parse::<u128>().map(Some).map_err(|error| {
188 StorageBackendError::Other(format!(
189 "invalid persisted next id for table `{table}`: {error}"
190 ))
191 })
192}