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!(subspace
205            .unpack::<(i64, Versionstamp, i64)>(&packed)
206            .is_err());
207    }
208
209    #[test]
210    fn unpack_with_subspace_versionstamp() {
211        let subspace: Subspace = Versionstamp::complete([1; 10], 2).into();
212        let tup = (Versionstamp::complete([1; 10], 0), 2);
213        let packed = subspace.pack_with_versionstamp(&tup);
214        let tup_unpack: (Versionstamp, i64) = subspace.unpack(&packed).unwrap();
215        assert_eq!(tup, tup_unpack);
216        assert!(subspace
217            .unpack::<(Versionstamp, Versionstamp, i64)>(&packed)
218            .is_err());
219    }
220
221    #[test]
222    fn subspace_can_use_incomplete_versionstamp() {
223        let subspace: Subspace = Versionstamp::incomplete(0).into();
224        let tup = (1, 2);
225
226        let packed = subspace.pack_with_versionstamp(&tup);
227        let expected = pack_with_versionstamp(&(Versionstamp::incomplete(0), 1, 2));
228        assert_eq!(expected, packed);
229    }
230
231    #[test]
232    fn child_subspace_can_use_incomplete_versionstamp() {
233        let subspace: Subspace = 1.into();
234        let subspace = subspace.subspace(&Versionstamp::incomplete(0));
235        let tup = (1, 2);
236        let packed = subspace.pack_with_versionstamp(&tup);
237        let expected = pack_with_versionstamp(&(1, Versionstamp::incomplete(0), 1, 2));
238        assert_eq!(expected, packed);
239    }
240
241    #[test]
242    #[should_panic]
243    fn subspace_cannot_use_multiple_incomplete_versionstamps() {
244        let subspace: Subspace = Versionstamp::incomplete(0).into();
245        let subspace = subspace.subspace(&Versionstamp::incomplete(0));
246        subspace.pack_with_versionstamp(&1);
247    }
248
249    #[test]
250    #[should_panic]
251    fn subspace_cannot_pack_multiple_incomplete_versionstamps() {
252        let subspace: Subspace = Versionstamp::incomplete(0).into();
253        subspace.pack_with_versionstamp(&Versionstamp::incomplete(0));
254    }
255
256    #[test]
257    fn is_start_of() {
258        let ss0: Subspace = 1.into();
259        let ss1: Subspace = 2.into();
260        let tup = (2, 3);
261
262        assert!(ss0.is_start_of(&ss0.pack(&tup)));
263        assert!(!ss1.is_start_of(&ss0.pack(&tup)));
264        assert!(Subspace::from("start").is_start_of(&pack(&"start")));
265        assert!(Subspace::from("start").is_start_of(&pack(&"start".to_string())));
266        assert!(!Subspace::from("start").is_start_of(&pack(&"starting")));
267        assert!(Subspace::from("start").is_start_of(&pack(&("start", "end"))));
268        assert!(Subspace::from(("start", 42)).is_start_of(&pack(&("start", 42, "end"))));
269    }
270
271    #[test]
272    fn range() {
273        let ss: Subspace = 1.into();
274        let tup = (2, 3);
275        let packed = ss.pack(&tup);
276
277        let (begin, end) = ss.range();
278        assert!(packed >= begin && packed <= end);
279    }
280
281    #[test]
282    fn equality() {
283        let sub1 = Subspace::all().subspace(&"test");
284        let sub2 = Subspace::all().subspace(&"test");
285        let sub3 = Subspace::all().subspace(&"test2");
286        assert_eq!(sub1, sub2);
287        assert_ne!(sub1, sub3);
288    }
289
290    #[test]
291    fn hash() {
292        let sub1 = Subspace::all().subspace(&"test");
293        let sub2 = Subspace::all().subspace(&"test2");
294
295        let map: HashMap<Subspace, u8> = HashMap::from([(sub1, 1), (sub2, 2)]);
296
297        assert_eq!(map.get(&Subspace::all().subspace(&"test")).unwrap(), &1);
298        assert_eq!(map.get(&Subspace::all().subspace(&"test2")).unwrap(), &2);
299    }
300
301    #[test]
302    fn display() {
303        let sub1 = Subspace::all().subspace(&"test");
304        let sub2 = Subspace::all().subspace(&"test2").subspace(&"path1");
305        let sub3 = Subspace::all().subspace(&"test3").subspace(&"__커피__");
306        let sub4 = Subspace::all().subspace(&"test4\\path1\\path2");
307
308        assert_eq!(format!("{sub1}"), r#"\x02test\x00"#);
309        assert_eq!(format!("{sub2}"), r#"\x02test2\x00\x02path1\x00"#);
310        assert_eq!(
311            format!("{sub3}"),
312            r#"\x02test3\x00\x02__\xec\xbb\xa4\xed\x94\xbc__\x00"#
313        );
314        assert_eq!(format!("{sub4}"), r#"\x02test4\\path1\\path2\x00"#);
315    }
316}