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}