foundationdb/directory/
mod.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
9//! Directory provides a tool for managing related subspaces.
10//!
11//! The FoundationDB API provides directories as a tool for managing related Subspaces.
12//! For general guidance on directory usage, see the discussion in the [Developer Guide](https://apple.github.io/foundationdb/developer-guide.html#directories).
13//!
14//! Directories are identified by hierarchical paths analogous to the paths in a Unix-like file system.
15//! A path is represented as a slice of strings. Each directory has an associated subspace used to
16//! store its content. The directory layer maps each path to a short prefix used for the
17//! corresponding subspace. In effect, directories provide a level of indirection for access to subspaces.
18//! Directory operations are transactional.
19//!
20//! It is a direct backport of the [Flow implementation](https://github.com/apple/foundationdb/tree/master/bindings/flow).
21//!
22//! Examples:
23//!
24//! ```rust
25//! use futures::prelude::*;
26//! use foundationdb::directory::Directory;
27//!
28//! async fn async_main() -> foundationdb::FdbResult<()> {
29//!     let db = foundationdb::Database::default()?;
30//!
31//!     // creates a transaction
32//!     let trx = db.create_trx()?;
33//!
34//!     // creates a directory
35//!     let directory = foundationdb::directory::DirectoryLayer::default();
36//!
37//!     let path = vec![String::from("my-awesome-app"), String::from("my-awesome-user")];
38//!
39//!     // use the directory to create a subspace to use
40//!     let content_subspace = directory.create_or_open(
41//!         // the transaction used to read/write the directory.
42//!         &trx,
43//!         // the path used, which can view as a UNIX path like `/app/my-app`.
44//!         &path,
45//!         // do not use any custom prefix or layer
46//!         None, None,
47//!     ).await;
48//!     assert_eq!(true, content_subspace.is_ok());
49//!
50//!     // Don't forget to commit your transaction to persist the subspace
51//!     trx.commit().await?;
52//!
53//!     Ok(())
54//! }
55//!
56//! // Safe because drop is called before the program exits
57//! let network = unsafe { foundationdb::boot() };
58//! futures::executor::block_on(async_main()).expect("failed to run");
59//! drop(network);
60//! ```
61
62mod directory_layer;
63mod directory_partition;
64mod directory_subspace;
65mod error;
66mod node;
67
68use crate::tuple::{PackResult, Subspace, TuplePack, TupleUnpack};
69use crate::Transaction;
70use async_trait::async_trait;
71use core::cmp;
72pub use directory_layer::DirectoryLayer;
73pub use directory_partition::DirectoryPartition;
74pub use directory_subspace::DirectorySubspace;
75pub use error::DirectoryError;
76use std::cmp::Ordering;
77
78/// `Directory` represents a subspace of keys in a FoundationDB database, identified by a hierarchical path.
79#[async_trait]
80pub trait Directory {
81    /// Creates or opens the subdirectory of this Directory located at path (creating parent directories, if necessary).
82    ///
83    /// # Warning
84    ///
85    /// If you need to create several paths with the same prefix, you **must** use several transactions(See [this link](https://github.com/apple/foundationdb/issues/895#issuecomment-436704180) for context)
86    async fn create_or_open(
87        &self,
88        txn: &Transaction,
89        path: &[String],
90        prefix: Option<&[u8]>,
91        layer: Option<&[u8]>,
92    ) -> Result<DirectoryOutput, DirectoryError>;
93
94    /// Creates a subdirectory of this Directory located at path (creating parent directories if necessary).
95    ///
96    /// # Warning
97    ///
98    /// If you need to create several paths with the same prefix, you **must** use several transactions(See [this link](https://github.com/apple/foundationdb/issues/895#issuecomment-436704180) for context)
99    async fn create(
100        &self,
101        txn: &Transaction,
102        path: &[String],
103        prefix: Option<&[u8]>,
104        layer: Option<&[u8]>,
105    ) -> Result<DirectoryOutput, DirectoryError>;
106
107    /// Opens the subdirectory of this Directory located at path.
108    async fn open(
109        &self,
110        txn: &Transaction,
111        path: &[String],
112        layer: Option<&[u8]>,
113    ) -> Result<DirectoryOutput, DirectoryError>;
114
115    /// Checks if the subdirectory of this Directory located at path exists.
116    async fn exists(&self, trx: &Transaction, path: &[String]) -> Result<bool, DirectoryError>;
117
118    /// Moves this Directory to the specified newAbsolutePath.
119    async fn move_directory(
120        &self,
121        trx: &Transaction,
122        new_path: &[String],
123    ) -> Result<DirectoryOutput, DirectoryError>;
124
125    /// Moves the subdirectory of this Directory located at oldpath to newpath.
126    async fn move_to(
127        &self,
128        trx: &Transaction,
129        old_path: &[String],
130        new_path: &[String],
131    ) -> Result<DirectoryOutput, DirectoryError>;
132
133    /// Removes the subdirectory of this Directory located at path and all of its subdirectories, as well as all of their contents.
134    async fn remove(&self, trx: &Transaction, path: &[String]) -> Result<bool, DirectoryError>;
135
136    /// Removes the subdirectory of this Directory located at path (if the path exists) and all of its subdirectories, as well as all of their contents.
137    async fn remove_if_exists(
138        &self,
139        trx: &Transaction,
140        path: &[String],
141    ) -> Result<bool, DirectoryError>;
142
143    /// List the subdirectories of this directory at a given subpath.
144    async fn list(&self, trx: &Transaction, path: &[String])
145        -> Result<Vec<String>, DirectoryError>;
146}
147
148pub(crate) fn compare_slice<T: Ord>(a: &[T], b: &[T]) -> cmp::Ordering {
149    for (ai, bi) in a.iter().zip(b.iter()) {
150        match ai.cmp(bi) {
151            Ordering::Equal => continue,
152            ord => return ord,
153        }
154    }
155
156    // if every single element was equal, compare length
157    a.len().cmp(&b.len())
158}
159
160/// DirectoryOutput represents the different output of a Directory.
161#[derive(Clone, Debug)]
162pub enum DirectoryOutput {
163    /// Under classic usage, you will obtain an `DirectorySubspace`
164    DirectorySubspace(DirectorySubspace),
165    /// You can open an `DirectoryPartition` by using the "partition" layer
166    DirectoryPartition(DirectoryPartition),
167}
168
169impl DirectoryOutput {
170    pub fn subspace<T: TuplePack>(&self, t: &T) -> Result<Subspace, DirectoryError> {
171        match self {
172            DirectoryOutput::DirectorySubspace(d) => Ok(d.subspace(t)),
173            DirectoryOutput::DirectoryPartition(_) => {
174                Err(DirectoryError::CannotOpenDirectoryPartition)
175            }
176        }
177    }
178
179    pub fn bytes(&self) -> Result<&[u8], DirectoryError> {
180        match self {
181            DirectoryOutput::DirectorySubspace(d) => Ok(d.bytes()),
182            DirectoryOutput::DirectoryPartition(_) => {
183                Err(DirectoryError::CannotOpenDirectoryPartition)
184            }
185        }
186    }
187
188    pub fn pack<T: TuplePack>(&self, t: &T) -> Result<Vec<u8>, DirectoryError> {
189        match self {
190            DirectoryOutput::DirectorySubspace(d) => Ok(d.pack(t)),
191            DirectoryOutput::DirectoryPartition(_) => {
192                Err(DirectoryError::CannotPackDirectoryPartition)
193            }
194        }
195    }
196
197    pub fn unpack<'de, T: TupleUnpack<'de>>(
198        &self,
199        key: &'de [u8],
200    ) -> Result<PackResult<T>, DirectoryError> {
201        match self {
202            DirectoryOutput::DirectorySubspace(d) => Ok(d.unpack(key)),
203            DirectoryOutput::DirectoryPartition(_) => {
204                Err(DirectoryError::CannotPackDirectoryPartition)
205            }
206        }
207    }
208
209    pub fn range(&self) -> Result<(Vec<u8>, Vec<u8>), DirectoryError> {
210        match self {
211            DirectoryOutput::DirectorySubspace(d) => Ok(d.range()),
212            DirectoryOutput::DirectoryPartition(_) => {
213                Err(DirectoryError::CannotRangeDirectoryPartition)
214            }
215        }
216    }
217
218    pub fn get_path(&self) -> &[String] {
219        match self {
220            DirectoryOutput::DirectorySubspace(d) => d.get_path(),
221            DirectoryOutput::DirectoryPartition(d) => d.get_path(),
222        }
223    }
224
225    pub fn get_layer(&self) -> &[u8] {
226        match self {
227            DirectoryOutput::DirectorySubspace(d) => d.get_layer(),
228            DirectoryOutput::DirectoryPartition(d) => d.get_layer(),
229        }
230    }
231}
232
233#[async_trait]
234impl Directory for DirectoryOutput {
235    async fn create_or_open(
236        &self,
237        txn: &Transaction,
238        path: &[String],
239        prefix: Option<&[u8]>,
240        layer: Option<&[u8]>,
241    ) -> Result<DirectoryOutput, DirectoryError> {
242        match self {
243            DirectoryOutput::DirectorySubspace(d) => {
244                d.create_or_open(txn, path, prefix, layer).await
245            }
246            DirectoryOutput::DirectoryPartition(d) => {
247                d.create_or_open(txn, path, prefix, layer).await
248            }
249        }
250    }
251
252    async fn create(
253        &self,
254        txn: &Transaction,
255        path: &[String],
256        prefix: Option<&[u8]>,
257        layer: Option<&[u8]>,
258    ) -> Result<DirectoryOutput, DirectoryError> {
259        match self {
260            DirectoryOutput::DirectorySubspace(d) => d.create(txn, path, prefix, layer).await,
261            DirectoryOutput::DirectoryPartition(d) => d.create(txn, path, prefix, layer).await,
262        }
263    }
264
265    async fn open(
266        &self,
267        txn: &Transaction,
268        path: &[String],
269        layer: Option<&[u8]>,
270    ) -> Result<DirectoryOutput, DirectoryError> {
271        match self {
272            DirectoryOutput::DirectorySubspace(d) => d.open(txn, path, layer).await,
273            DirectoryOutput::DirectoryPartition(d) => d.open(txn, path, layer).await,
274        }
275    }
276
277    async fn exists(&self, trx: &Transaction, path: &[String]) -> Result<bool, DirectoryError> {
278        match self {
279            DirectoryOutput::DirectorySubspace(d) => d.exists(trx, path).await,
280            DirectoryOutput::DirectoryPartition(d) => d.exists(trx, path).await,
281        }
282    }
283
284    async fn move_directory(
285        &self,
286        trx: &Transaction,
287        new_path: &[String],
288    ) -> Result<DirectoryOutput, DirectoryError> {
289        match self {
290            DirectoryOutput::DirectorySubspace(d) => d.move_directory(trx, new_path).await,
291            DirectoryOutput::DirectoryPartition(d) => d.move_directory(trx, new_path).await,
292        }
293    }
294
295    async fn move_to(
296        &self,
297        trx: &Transaction,
298        old_path: &[String],
299        new_path: &[String],
300    ) -> Result<DirectoryOutput, DirectoryError> {
301        match self {
302            DirectoryOutput::DirectorySubspace(d) => d.move_to(trx, old_path, new_path).await,
303            DirectoryOutput::DirectoryPartition(d) => d.move_to(trx, old_path, new_path).await,
304        }
305    }
306
307    async fn remove(&self, trx: &Transaction, path: &[String]) -> Result<bool, DirectoryError> {
308        match self {
309            DirectoryOutput::DirectorySubspace(d) => d.remove(trx, path).await,
310            DirectoryOutput::DirectoryPartition(d) => d.remove(trx, path).await,
311        }
312    }
313
314    async fn remove_if_exists(
315        &self,
316        trx: &Transaction,
317        path: &[String],
318    ) -> Result<bool, DirectoryError> {
319        match self {
320            DirectoryOutput::DirectorySubspace(d) => d.remove_if_exists(trx, path).await,
321            DirectoryOutput::DirectoryPartition(d) => d.remove_if_exists(trx, path).await,
322        }
323    }
324
325    async fn list(
326        &self,
327        trx: &Transaction,
328        path: &[String],
329    ) -> Result<Vec<String>, DirectoryError> {
330        match self {
331            DirectoryOutput::DirectorySubspace(d) => d.list(trx, path).await,
332            DirectoryOutput::DirectoryPartition(d) => d.list(trx, path).await,
333        }
334    }
335}
336
337// Strinc returns the first key that would sort outside the range prefixed by prefix.
338pub(crate) fn strinc(key: Vec<u8>) -> Vec<u8> {
339    let mut key = key;
340
341    for i in (0..key.len()).rev() {
342        if key[i] != 0xff {
343            key[i] += 1;
344            break;
345        } else {
346            // stripping key from trailing 0xFF bytes
347            key.remove(i);
348        }
349    }
350    key
351}
352
353#[cfg(test)]
354mod tests {
355    use super::*;
356
357    // https://github.com/apple/foundationdb/blob/e34df983ee8c0db333babf36fb620318d026553d/bindings/c/test/unit/unit_tests.cpp#L95
358    #[test]
359    fn test_strinc() {
360        assert_eq!(strinc(Vec::from("a".as_bytes())), Vec::from("b".as_bytes()));
361        assert_eq!(strinc(Vec::from("y".as_bytes())), Vec::from("z".as_bytes()));
362        assert_eq!(
363            strinc(Vec::from("!".as_bytes())),
364            Vec::from("\"".as_bytes())
365        );
366        assert_eq!(strinc(Vec::from("*".as_bytes())), Vec::from("+".as_bytes()));
367        assert_eq!(
368            strinc(Vec::from("fdb".as_bytes())),
369            Vec::from("fdc".as_bytes())
370        );
371        assert_eq!(
372            strinc(Vec::from("foundation database 6".as_bytes())),
373            Vec::from("foundation database 7".as_bytes())
374        );
375
376        assert_eq!(strinc(vec![61u8, 62u8, 255u8]), vec![61u8, 63u8]);
377        assert_eq!(strinc(vec![253u8, 255u8]), vec![254u8]);
378        assert_eq!(strinc(vec![253u8, 255u8, 255u8]), vec![254u8]);
379        assert_eq!(strinc(vec![255u8, 255u8, 255u8]), Vec::<u8>::new());
380    }
381}