fdb/tuple/versionstamp.rs
1use bytes::{Buf, BufMut};
2use bytes::{Bytes, BytesMut};
3
4// As mentioned here [1], depending on the context, there are two
5// concepts of versionstamp.
6//
7// At the `fdb_c` client level, the "versionstamp" is 10 bytes,
8// consisting of the transaction's commit version (8 bytes) and
9// transaction batch order (2 bytes).
10//
11// In the context of the Tuple layer, the "versionstamp" is 12
12// bytes. The user can manually add 2 additional bytes to provide
13// application level ordering.
14//
15// `VERSIONSTAMP_TR_VERSION_LEN` below is the `fdb_c` client level
16// versionstamp. When `VERSIONSTAMP_TR_VERSION` is all `\xFF`, it
17// means that versionstamp is "incomplete".
18//
19// [1]: https://apple.github.io/foundationdb/data-modeling.html#versionstamps
20const VERSIONSTAMP_TR_VERSION_LEN: usize = 10;
21const VERSIONSTAMP_USER_VERSION_LEN: usize = 2;
22
23/// Used to represent values written by versionstamp operations with a
24/// [`Tuple`].
25///
26/// [`Versionstamp`] contains twelve bytes. The first ten bytes are
27/// the "transaction" version, and they are usually assigned by the
28/// database in such a way that all transactions receive a different
29/// version that is consistent with a serialization order of the
30/// transactions within the database (One can use the
31/// [`get_versionstamp`] method to retrieve this version from a
32/// [`Transaction`]). This also implies that the transaction version
33/// of newly committed transactions will be monotonically increasing
34/// over time. The final two bytes are the "user" version and should
35/// be set by the client. This allows the user to use this type to
36/// impose a total order of items across multiple transactions in the
37/// database in a consistent and conflict-free way.
38///
39/// All [`Versionstamp`]s can exist in one of two states: "incomplete"
40/// and "complete". An "incomplete" [`Versionstamp`] is a [`Versionstamp`]
41/// that has not been initialized with a meaningful transaction
42/// version. For example, this might be used with a [`Versionstamp`] that
43/// one wants to fill in with the current transaction's version
44/// information. A "complete" [`Versionstamp`], in contradistinction, is
45/// one that *has* been assigned a meaningful transaction version. This
46/// is usually the case if one is reading back a Versionstamp from the
47/// database.
48///
49/// Example usage might be to do something like the following:
50///
51/// ```ignore
52/// let tr_version = fdb_database
53/// .run(|tr| async move {
54/// let t = {
55/// let mut tup = Tuple::new();
56/// tup.add_string(String::from("prefix"));
57/// tup.add_versionstamp(Versionstamp::incomplete(0));
58/// tup
59/// };
60///
61/// unsafe {
62/// tr.mutate(
63/// MutationType::SetVersionstampedKey,
64/// t.pack_with_versionstamp(Bytes::new())?,
65/// Bytes::new(),
66/// );
67/// }
68///
69/// Ok(unsafe { tr.get_versionstamp() })
70/// })
71/// .await?
72/// .get()
73/// .await?;
74///
75/// let vs = fdb_database
76/// .run(|tr| async move {
77/// let subspace = Subspace::new(Bytes::new()).subspace(&{
78/// let mut tup = Tuple::new();
79/// tup.add_string("prefix".to_string());
80/// tup
81/// });
82///
83/// let subspace_range = subspace.range(&Tuple::new());
84///
85/// let key = subspace_range
86/// .into_stream(&tr, RangeOptions::default())
87/// .take(1)
88/// .next()
89/// .await
90/// .unwrap()?
91/// .into_key();
92///
93/// Ok(subspace
94/// .unpack(&key.into())?
95/// .get_versionstamp_ref(0)?
96/// .clone())
97/// })
98/// .await?;
99///
100/// assert_eq!(vs, Versionstamp::complete(tr_version, 0));
101/// ```
102///
103/// Here, an incomplete [`Versionstamp`] is packed and written to the
104/// database with [`SetVersionstampedKey`] mutation type.
105///
106/// After committing, we then attempt to read back the same key that
107/// we just wrote. Then we verify the invariant that the deserialized
108/// [`Versionstamp`] is the same as a complete [`Versionstamp`] value
109/// created from the first transaction's version information.
110///
111/// [`Tuple`]: crate::tuple::Tuple
112/// [`get_versionstamp`]: crate::transaction::Transaction::get_versionstamp
113/// [`Transaction`]: crate::transaction::Transaction
114/// [`SetVersionstampedKey`]: crate::transaction::MutationType::SetVersionstampedKey
115#[derive(Clone, Ord, Eq, PartialOrd, PartialEq, Debug)]
116pub struct Versionstamp {
117 complete: bool,
118 tr_version: Bytes,
119 user_version: u16,
120}
121
122impl Versionstamp {
123 /// Creates a complete [`Versionstamp`] instance with the given
124 /// transaction and user versions.
125 ///
126 /// # Panic
127 ///
128 /// Panics if the length of the transaction version is incorrect.
129 pub fn complete(tr_version: Bytes, user_version: u16) -> Versionstamp {
130 if tr_version.len() != VERSIONSTAMP_TR_VERSION_LEN {
131 panic!("Global version has invalid length {}", tr_version.len());
132 }
133
134 let complete = true;
135
136 Versionstamp {
137 complete,
138 tr_version,
139 user_version,
140 }
141 }
142
143 /// Creates a value of [`Versionstamp`] type based on the given
144 /// byte array.
145 ///
146 /// # Panic
147 ///
148 /// Panics if the length of the byte array is incorrect.
149 pub fn from_bytes(version_bytes: Bytes) -> Versionstamp {
150 if version_bytes.len() != VERSIONSTAMP_TR_VERSION_LEN + VERSIONSTAMP_USER_VERSION_LEN {
151 panic!(
152 "Versionstamp bytes must have length {}",
153 VERSIONSTAMP_TR_VERSION_LEN + VERSIONSTAMP_USER_VERSION_LEN
154 );
155 }
156
157 // If we find any of the bytes to be not `0xFF`, then it means
158 // that versionstamp is in complete state.
159 let mut complete = false;
160 version_bytes[0..VERSIONSTAMP_TR_VERSION_LEN]
161 .iter()
162 .for_each(|x| {
163 if *x != 0xFF {
164 complete = true;
165 }
166 });
167
168 let tr_version = version_bytes.slice(0..VERSIONSTAMP_TR_VERSION_LEN);
169 let user_version = version_bytes.slice(VERSIONSTAMP_TR_VERSION_LEN..).get_u16();
170
171 Versionstamp {
172 complete,
173 tr_version,
174 user_version,
175 }
176 }
177
178 /// Creates an incomplete [`Versionstamp`] instance with the given
179 /// user version.
180 pub fn incomplete(user_version: u16) -> Versionstamp {
181 let complete = false;
182 let tr_version = Bytes::from_static(&b"\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"[..]);
183
184 Versionstamp {
185 complete,
186 tr_version,
187 user_version,
188 }
189 }
190
191 /// Retrieve a byte representation of this [`Versionstamp`].
192 pub fn get_bytes(&self) -> Bytes {
193 let mut buf =
194 BytesMut::with_capacity(VERSIONSTAMP_TR_VERSION_LEN + VERSIONSTAMP_USER_VERSION_LEN);
195 buf.put(self.tr_version.clone());
196
197 buf.put_u16(self.user_version);
198 buf.into()
199 }
200
201 /// Retrieve the portion of this [`Versionstamp`] that is set by
202 /// the database.
203 pub fn get_transaction_version(&self) -> Bytes {
204 self.tr_version.clone()
205 }
206
207 /// Retrieve the portion of this [`Versionstamp`] that is set by
208 /// the user.
209 pub fn get_user_version(&self) -> u16 {
210 self.user_version
211 }
212
213 /// Whether this [`Versionstamp`]'s transaction version is
214 /// meaningful.
215 pub fn is_complete(&self) -> bool {
216 self.complete
217 }
218}
219
220#[cfg(test)]
221mod tests {
222 use bytes::Bytes;
223
224 use super::Versionstamp;
225
226 #[test]
227 fn complete() {
228 assert!(std::panic::catch_unwind(|| {
229 Versionstamp::complete(Bytes::from_static(&b"invalid"[..]), 0)
230 })
231 .is_err());
232
233 assert_eq!(
234 Versionstamp::complete(
235 Bytes::from_static(&b"\xAA\xBB\xCC\xDD\xEE\xFF\x00\x01\x02\x03"[..]),
236 0
237 ),
238 Versionstamp {
239 complete: true,
240 tr_version: Bytes::from_static(&b"\xAA\xBB\xCC\xDD\xEE\xFF\x00\x01\x02\x03"[..]),
241 user_version: 0
242 }
243 );
244 }
245
246 #[test]
247 fn from_bytes() {
248 assert!(std::panic::catch_unwind(|| {
249 Versionstamp::from_bytes(Bytes::from_static(&b"invalid"[..]))
250 })
251 .is_err());
252
253 assert_eq!(
254 Versionstamp::from_bytes(Bytes::from_static(
255 &b"\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x02\x91"[..]
256 )),
257 Versionstamp::incomplete(657)
258 );
259
260 assert_eq!(
261 Versionstamp::from_bytes(Bytes::from_static(
262 &b"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x02\x91"[..]
263 )),
264 Versionstamp::complete(
265 Bytes::from_static(&b"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A"[..]),
266 657
267 )
268 );
269 }
270
271 #[test]
272 fn incomplete() {
273 assert_eq!(
274 Versionstamp::incomplete(657),
275 Versionstamp {
276 complete: false,
277 tr_version: Bytes::from_static(&b"\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"[..]),
278 user_version: 657
279 }
280 );
281 }
282
283 #[test]
284 fn get_bytes() {
285 assert_eq!(
286 (Versionstamp::complete(
287 Bytes::from_static(&b"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A"[..]),
288 657
289 ))
290 .get_bytes(),
291 Bytes::from_static(&b"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x02\x91"[..])
292 );
293 assert_eq!(
294 (Versionstamp::incomplete(657)).get_bytes(),
295 Bytes::from_static(&b"\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x02\x91"[..])
296 );
297 }
298
299 #[test]
300 fn get_transaction_version() {
301 assert_eq!(
302 (Versionstamp::complete(
303 Bytes::from_static(&b"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A"[..]),
304 657
305 ))
306 .get_transaction_version(),
307 Bytes::from_static(&b"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A"[..])
308 );
309
310 assert_eq!(
311 (Versionstamp::incomplete(657)).get_transaction_version(),
312 Bytes::from_static(&b"\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"[..])
313 );
314 }
315
316 #[test]
317 fn get_user_version() {
318 assert_eq!(
319 (Versionstamp::complete(
320 Bytes::from_static(&b"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A"[..]),
321 657
322 ))
323 .get_user_version(),
324 657
325 );
326
327 assert_eq!((Versionstamp::incomplete(657)).get_user_version(), 657);
328 }
329
330 #[test]
331 fn is_complete() {
332 assert!((Versionstamp::complete(
333 Bytes::from_static(&b"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A"[..]),
334 657
335 ))
336 .is_complete());
337
338 assert!(!(Versionstamp::incomplete(657)).is_complete());
339 }
340}