Skip to main content

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