foundationdb_tuple/
subspace.rs

1// Copyright 2018 foundationdb-rs developers, https://github.com/Clikengo/foundationdb-rs/graphs/contributors
2// Copyright 2013-2018 Apple, Inc and the FoundationDB project authors.
3//
4// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
5// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
6// http://opensource.org/licenses/MIT>, at your option. This file may not be
7// copied, modified, or distributed except according to those terms.
8
9use super::*;
10use std::hash::Hash;
11
12/// Represents a well-defined region of keyspace in a FoundationDB database
13///
14/// It provides a convenient way to use FoundationDB tuples to define namespaces for
15/// different categories of data. The namespace is specified by a prefix tuple which is prepended
16/// to all tuples packed by the subspace. When unpacking a key with the subspace, the prefix tuple
17/// will be removed from the result.
18///
19/// As a best practice, API clients should use at least one subspace for application data. For
20/// general guidance on subspace usage, see the Subspaces section of the [Developer Guide].
21///
22/// [Developer Guide]: https://apple.github.io/foundationdb/developer-guide.html#subspaces
23#[derive(Debug, Clone, Eq, PartialEq, Hash)]
24pub struct Subspace {
25    prefix: Vec<u8>,
26    versionstamp_offset: VersionstampOffset,
27}
28
29impl<E: TuplePack> From<E> for Subspace {
30    fn from(e: E) -> Self {
31        let mut prefix = vec![];
32        let versionstamp_offset = pack_into(&e, &mut prefix);
33        Self {
34            prefix,
35            versionstamp_offset,
36        }
37    }
38}
39
40impl Subspace {
41    /// `all` returns the Subspace corresponding to all keys in a FoundationDB database.
42    pub fn all() -> Self {
43        Self {
44            prefix: Vec::new(),
45            versionstamp_offset: VersionstampOffset::None { size: 0 },
46        }
47    }
48
49    /// Returns a new Subspace from the provided bytes.
50    pub fn from_bytes(prefix: impl Into<Vec<u8>>) -> Self {
51        let prefix = prefix.into();
52        Self {
53            versionstamp_offset: VersionstampOffset::None {
54                size: u32::try_from(prefix.len()).expect("data doesn't fit u32"),
55            },
56            prefix,
57        }
58    }
59
60    /// Convert into prefix key bytes
61    pub fn into_bytes(self) -> Vec<u8> {
62        self.prefix
63    }
64
65    /// Returns a new Subspace whose prefix extends this Subspace with a given tuple encodable.
66    pub fn subspace<T: TuplePack>(&self, t: &T) -> Self {
67        let mut prefix = self.prefix.clone();
68        let mut versionstamp_offset = self.versionstamp_offset;
69        versionstamp_offset += pack_into(t, &mut prefix);
70        Self {
71            prefix,
72            versionstamp_offset,
73        }
74    }
75
76    /// `bytes` returns the literal bytes of the prefix of this Subspace.
77    pub fn bytes(&self) -> &[u8] {
78        self.prefix.as_slice()
79    }
80
81    /// Returns the key encoding the specified Tuple with the prefix of this Subspace
82    /// prepended.
83    pub fn pack<T: TuplePack>(&self, t: &T) -> Vec<u8> {
84        let mut out = self.prefix.clone();
85        pack_into(t, &mut out);
86        out
87    }
88
89    /// Returns the key encoding the specified Tuple with the prefix of this Subspace
90    /// prepended, with a versionstamp.
91    pub fn pack_with_versionstamp<T: TuplePack>(&self, t: &T) -> Vec<u8> {
92        let mut output = self.prefix.clone();
93        let mut versionstamp_offset = self.versionstamp_offset;
94        versionstamp_offset += pack_into(t, &mut output);
95        match versionstamp_offset {
96            VersionstampOffset::OneIncomplete { offset } => {
97                output.extend_from_slice(&offset.to_le_bytes());
98            }
99            VersionstampOffset::MultipleIncomplete => {
100                panic!("Subspace cannot contain more than one incomplete versionstamp");
101            }
102            _ => {}
103        }
104        output
105    }
106
107    /// `unpack` returns the Tuple encoded by the given key with the prefix of this Subspace
108    /// removed.  `unpack` will return an error if the key is not in this Subspace or does not
109    /// encode a well-formed Tuple.
110    pub fn unpack<'de, T: TupleUnpack<'de>>(&self, key: &'de [u8]) -> PackResult<T> {
111        if !self.is_start_of(key) {
112            return Err(PackError::BadPrefix);
113        }
114        let key = &key[self.prefix.len()..];
115        unpack(key)
116    }
117
118    /// `is_start_of` returns true if the provided key starts with the prefix of this Subspace,
119    /// indicating that the Subspace logically contains the key.
120    pub fn is_start_of(&self, key: &[u8]) -> bool {
121        key.starts_with(&self.prefix)
122    }
123
124    /// `range` returns first and last key of given Subspace
125    pub fn range(&self) -> (Vec<u8>, Vec<u8>) {
126        let mut begin = Vec::with_capacity(self.prefix.len() + 1);
127        begin.extend_from_slice(&self.prefix);
128        begin.push(0x00);
129
130        let mut end = Vec::with_capacity(self.prefix.len() + 1);
131        end.extend_from_slice(&self.prefix);
132        end.push(0xff);
133
134        (begin, end)
135    }
136}
137
138#[cfg(test)]
139mod tests {
140    use super::*;
141    use std::collections::HashMap;
142
143    #[test]
144    fn sub() {
145        let ss0: Subspace = 1.into();
146        let ss1 = ss0.subspace(&2);
147
148        let ss2: Subspace = (1, 2).into();
149
150        assert_eq!(ss1.bytes(), ss2.bytes());
151    }
152
153    #[test]
154    fn pack_unpack() {
155        let ss0: Subspace = 1.into();
156        let tup = (2, 3);
157
158        let packed = ss0.pack(&tup);
159        let expected = pack(&(1, 2, 3));
160        assert_eq!(expected, packed);
161
162        let tup_unpack: (i64, i64) = ss0.unpack(&packed).unwrap();
163        assert_eq!(tup, tup_unpack);
164
165        assert!(ss0.unpack::<(i64, i64, i64)>(&packed).is_err());
166    }
167
168    #[test]
169    fn subspace_pack_with_versionstamp() {
170        let subspace: Subspace = 1.into();
171        let tup = (Versionstamp::incomplete(0), 2);
172
173        let packed = subspace.pack_with_versionstamp(&tup);
174        let expected = pack_with_versionstamp(&(1, Versionstamp::incomplete(0), 2));
175        assert_eq!(expected, packed);
176    }
177
178    #[test]
179    fn subspace_unpack_with_versionstamp() {
180        // On unpack, the versionstamp will be complete, so pack_with_versionstamp won't append
181        // the offset.
182        let subspace: Subspace = 1.into();
183        let tup = (Versionstamp::complete([1; 10], 0), 2);
184        let packed = subspace.pack_with_versionstamp(&tup);
185        let tup_unpack: (Versionstamp, i64) = subspace.unpack(&packed).unwrap();
186        assert_eq!(tup, tup_unpack);
187
188        assert!(subspace
189            .unpack::<(i64, Versionstamp, i64)>(&packed)
190            .is_err());
191    }
192
193    #[test]
194    fn unpack_with_subspace_versionstamp() {
195        let subspace: Subspace = Versionstamp::complete([1; 10], 2).into();
196        let tup = (Versionstamp::complete([1; 10], 0), 2);
197        let packed = subspace.pack_with_versionstamp(&tup);
198        let tup_unpack: (Versionstamp, i64) = subspace.unpack(&packed).unwrap();
199        assert_eq!(tup, tup_unpack);
200        assert!(subspace
201            .unpack::<(Versionstamp, Versionstamp, i64)>(&packed)
202            .is_err());
203    }
204
205    #[test]
206    fn subspace_can_use_incomplete_versionstamp() {
207        let subspace: Subspace = Versionstamp::incomplete(0).into();
208        let tup = (1, 2);
209
210        let packed = subspace.pack_with_versionstamp(&tup);
211        let expected = pack_with_versionstamp(&(Versionstamp::incomplete(0), 1, 2));
212        assert_eq!(expected, packed);
213    }
214
215    #[test]
216    fn child_subspace_can_use_incomplete_versionstamp() {
217        let subspace: Subspace = 1.into();
218        let subspace = subspace.subspace(&Versionstamp::incomplete(0));
219        let tup = (1, 2);
220        let packed = subspace.pack_with_versionstamp(&tup);
221        let expected = pack_with_versionstamp(&(1, Versionstamp::incomplete(0), 1, 2));
222        assert_eq!(expected, packed);
223    }
224
225    #[test]
226    #[should_panic]
227    fn subspace_cannot_use_multiple_incomplete_versionstamps() {
228        let subspace: Subspace = Versionstamp::incomplete(0).into();
229        let subspace = subspace.subspace(&Versionstamp::incomplete(0));
230        subspace.pack_with_versionstamp(&1);
231    }
232
233    #[test]
234    #[should_panic]
235    fn subspace_cannot_pack_multiple_incomplete_versionstamps() {
236        let subspace: Subspace = Versionstamp::incomplete(0).into();
237        subspace.pack_with_versionstamp(&Versionstamp::incomplete(0));
238    }
239
240    #[test]
241    fn is_start_of() {
242        let ss0: Subspace = 1.into();
243        let ss1: Subspace = 2.into();
244        let tup = (2, 3);
245
246        assert!(ss0.is_start_of(&ss0.pack(&tup)));
247        assert!(!ss1.is_start_of(&ss0.pack(&tup)));
248        assert!(Subspace::from("start").is_start_of(&pack(&"start")));
249        assert!(Subspace::from("start").is_start_of(&pack(&"start".to_string())));
250        assert!(!Subspace::from("start").is_start_of(&pack(&"starting")));
251        assert!(Subspace::from("start").is_start_of(&pack(&("start", "end"))));
252        assert!(Subspace::from(("start", 42)).is_start_of(&pack(&("start", 42, "end"))));
253    }
254
255    #[test]
256    fn range() {
257        let ss: Subspace = 1.into();
258        let tup = (2, 3);
259        let packed = ss.pack(&tup);
260
261        let (begin, end) = ss.range();
262        assert!(packed >= begin && packed <= end);
263    }
264
265    #[test]
266    fn equality() {
267        let sub1 = Subspace::all().subspace(&"test");
268        let sub2 = Subspace::all().subspace(&"test");
269        let sub3 = Subspace::all().subspace(&"test2");
270        assert_eq!(sub1, sub2);
271        assert_ne!(sub1, sub3);
272    }
273
274    #[test]
275    fn hash() {
276        let sub1 = Subspace::all().subspace(&"test");
277        let sub2 = Subspace::all().subspace(&"test2");
278
279        let map: HashMap<Subspace, u8> = HashMap::from([(sub1, 1), (sub2, 2)]);
280
281        assert_eq!(map.get(&Subspace::all().subspace(&"test")).unwrap(), &1);
282        assert_eq!(map.get(&Subspace::all().subspace(&"test2")).unwrap(), &2);
283    }
284}