Skip to main content

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///
80/// # Warning: Hanging on Network/DNS failures
81///
82/// Directory operations are transactional and rely on the [`Transaction`] passed to them.
83/// By default, the FoundationDB C API will retry indefinitely if it cannot reach the cluster
84/// or if DNS resolution fails. This can cause directory operations to hang forever.
85/// To prevent this, you should set [`crate::options::DatabaseOption::TransactionTimeout`] or
86/// [`crate::options::DatabaseOption::TransactionRetryLimit`] on the [`crate::Database`] object, or
87/// [`crate::options::TransactionOption::Timeout`] or [`crate::options::TransactionOption::RetryLimit`] on the transaction
88/// itself.
89#[async_trait]
90pub trait Directory {
91    /// Creates or opens the subdirectory of this Directory located at path (creating parent directories, if necessary).
92    ///
93    /// # Warning
94    ///
95    /// 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)
96    async fn create_or_open(
97        &self,
98        txn: &Transaction,
99        path: &[String],
100        prefix: Option<&[u8]>,
101        layer: Option<&[u8]>,
102    ) -> Result<DirectoryOutput, DirectoryError>;
103
104    /// Creates a subdirectory of this Directory located at path (creating parent directories if necessary).
105    ///
106    /// # Warning
107    ///
108    /// 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)
109    async fn create(
110        &self,
111        txn: &Transaction,
112        path: &[String],
113        prefix: Option<&[u8]>,
114        layer: Option<&[u8]>,
115    ) -> Result<DirectoryOutput, DirectoryError>;
116
117    /// Opens the subdirectory of this Directory located at path.
118    async fn open(
119        &self,
120        txn: &Transaction,
121        path: &[String],
122        layer: Option<&[u8]>,
123    ) -> Result<DirectoryOutput, DirectoryError>;
124
125    /// Checks if the subdirectory of this Directory located at path exists.
126    async fn exists(&self, trx: &Transaction, path: &[String]) -> Result<bool, DirectoryError>;
127
128    /// Moves this Directory to the specified newAbsolutePath.
129    async fn move_directory(
130        &self,
131        trx: &Transaction,
132        new_path: &[String],
133    ) -> Result<DirectoryOutput, DirectoryError>;
134
135    /// Moves the subdirectory of this Directory located at oldpath to newpath.
136    async fn move_to(
137        &self,
138        trx: &Transaction,
139        old_path: &[String],
140        new_path: &[String],
141    ) -> Result<DirectoryOutput, DirectoryError>;
142
143    /// Removes the subdirectory of this Directory located at path and all of its subdirectories, as well as all of their contents.
144    async fn remove(&self, trx: &Transaction, path: &[String]) -> Result<bool, DirectoryError>;
145
146    /// 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.
147    async fn remove_if_exists(
148        &self,
149        trx: &Transaction,
150        path: &[String],
151    ) -> Result<bool, DirectoryError>;
152
153    /// List the subdirectories of this directory at a given subpath.
154    async fn list(&self, trx: &Transaction, path: &[String])
155        -> Result<Vec<String>, DirectoryError>;
156}
157
158pub(crate) fn compare_slice<T: Ord>(a: &[T], b: &[T]) -> cmp::Ordering {
159    for (ai, bi) in a.iter().zip(b.iter()) {
160        match ai.cmp(bi) {
161            Ordering::Equal => continue,
162            ord => return ord,
163        }
164    }
165
166    // if every single element was equal, compare length
167    a.len().cmp(&b.len())
168}
169
170/// DirectoryOutput represents the different output of a Directory.
171#[derive(Clone, Debug)]
172pub enum DirectoryOutput {
173    /// Under classic usage, you will obtain an `DirectorySubspace`
174    DirectorySubspace(DirectorySubspace),
175    /// You can open an `DirectoryPartition` by using the "partition" layer
176    DirectoryPartition(DirectoryPartition),
177}
178
179impl DirectoryOutput {
180    pub fn subspace<T: TuplePack>(&self, t: &T) -> Result<Subspace, DirectoryError> {
181        match self {
182            DirectoryOutput::DirectorySubspace(d) => Ok(d.subspace(t)),
183            DirectoryOutput::DirectoryPartition(_) => {
184                Err(DirectoryError::CannotOpenDirectoryPartition)
185            }
186        }
187    }
188
189    pub fn bytes(&self) -> Result<&[u8], DirectoryError> {
190        match self {
191            DirectoryOutput::DirectorySubspace(d) => Ok(d.bytes()),
192            DirectoryOutput::DirectoryPartition(_) => {
193                Err(DirectoryError::CannotOpenDirectoryPartition)
194            }
195        }
196    }
197
198    pub fn pack<T: TuplePack>(&self, t: &T) -> Result<Vec<u8>, DirectoryError> {
199        match self {
200            DirectoryOutput::DirectorySubspace(d) => Ok(d.pack(t)),
201            DirectoryOutput::DirectoryPartition(_) => {
202                Err(DirectoryError::CannotPackDirectoryPartition)
203            }
204        }
205    }
206
207    pub fn unpack<'de, T: TupleUnpack<'de>>(
208        &self,
209        key: &'de [u8],
210    ) -> Result<PackResult<T>, DirectoryError> {
211        match self {
212            DirectoryOutput::DirectorySubspace(d) => Ok(d.unpack(key)),
213            DirectoryOutput::DirectoryPartition(_) => {
214                Err(DirectoryError::CannotPackDirectoryPartition)
215            }
216        }
217    }
218
219    pub fn range(&self) -> Result<(Vec<u8>, Vec<u8>), DirectoryError> {
220        match self {
221            DirectoryOutput::DirectorySubspace(d) => Ok(d.range()),
222            DirectoryOutput::DirectoryPartition(_) => {
223                Err(DirectoryError::CannotRangeDirectoryPartition)
224            }
225        }
226    }
227
228    pub fn get_path(&self) -> &[String] {
229        match self {
230            DirectoryOutput::DirectorySubspace(d) => d.get_path(),
231            DirectoryOutput::DirectoryPartition(d) => d.get_path(),
232        }
233    }
234
235    pub fn get_layer(&self) -> &[u8] {
236        match self {
237            DirectoryOutput::DirectorySubspace(d) => d.get_layer(),
238            DirectoryOutput::DirectoryPartition(d) => d.get_layer(),
239        }
240    }
241}
242
243#[async_trait]
244impl Directory for DirectoryOutput {
245    async fn create_or_open(
246        &self,
247        txn: &Transaction,
248        path: &[String],
249        prefix: Option<&[u8]>,
250        layer: Option<&[u8]>,
251    ) -> Result<DirectoryOutput, DirectoryError> {
252        match self {
253            DirectoryOutput::DirectorySubspace(d) => {
254                d.create_or_open(txn, path, prefix, layer).await
255            }
256            DirectoryOutput::DirectoryPartition(d) => {
257                d.create_or_open(txn, path, prefix, layer).await
258            }
259        }
260    }
261
262    async fn create(
263        &self,
264        txn: &Transaction,
265        path: &[String],
266        prefix: Option<&[u8]>,
267        layer: Option<&[u8]>,
268    ) -> Result<DirectoryOutput, DirectoryError> {
269        match self {
270            DirectoryOutput::DirectorySubspace(d) => d.create(txn, path, prefix, layer).await,
271            DirectoryOutput::DirectoryPartition(d) => d.create(txn, path, prefix, layer).await,
272        }
273    }
274
275    async fn open(
276        &self,
277        txn: &Transaction,
278        path: &[String],
279        layer: Option<&[u8]>,
280    ) -> Result<DirectoryOutput, DirectoryError> {
281        match self {
282            DirectoryOutput::DirectorySubspace(d) => d.open(txn, path, layer).await,
283            DirectoryOutput::DirectoryPartition(d) => d.open(txn, path, layer).await,
284        }
285    }
286
287    async fn exists(&self, trx: &Transaction, path: &[String]) -> Result<bool, DirectoryError> {
288        match self {
289            DirectoryOutput::DirectorySubspace(d) => d.exists(trx, path).await,
290            DirectoryOutput::DirectoryPartition(d) => d.exists(trx, path).await,
291        }
292    }
293
294    async fn move_directory(
295        &self,
296        trx: &Transaction,
297        new_path: &[String],
298    ) -> Result<DirectoryOutput, DirectoryError> {
299        match self {
300            DirectoryOutput::DirectorySubspace(d) => d.move_directory(trx, new_path).await,
301            DirectoryOutput::DirectoryPartition(d) => d.move_directory(trx, new_path).await,
302        }
303    }
304
305    async fn move_to(
306        &self,
307        trx: &Transaction,
308        old_path: &[String],
309        new_path: &[String],
310    ) -> Result<DirectoryOutput, DirectoryError> {
311        match self {
312            DirectoryOutput::DirectorySubspace(d) => d.move_to(trx, old_path, new_path).await,
313            DirectoryOutput::DirectoryPartition(d) => d.move_to(trx, old_path, new_path).await,
314        }
315    }
316
317    async fn remove(&self, trx: &Transaction, path: &[String]) -> Result<bool, DirectoryError> {
318        match self {
319            DirectoryOutput::DirectorySubspace(d) => d.remove(trx, path).await,
320            DirectoryOutput::DirectoryPartition(d) => d.remove(trx, path).await,
321        }
322    }
323
324    async fn remove_if_exists(
325        &self,
326        trx: &Transaction,
327        path: &[String],
328    ) -> Result<bool, DirectoryError> {
329        match self {
330            DirectoryOutput::DirectorySubspace(d) => d.remove_if_exists(trx, path).await,
331            DirectoryOutput::DirectoryPartition(d) => d.remove_if_exists(trx, path).await,
332        }
333    }
334
335    async fn list(
336        &self,
337        trx: &Transaction,
338        path: &[String],
339    ) -> Result<Vec<String>, DirectoryError> {
340        match self {
341            DirectoryOutput::DirectorySubspace(d) => d.list(trx, path).await,
342            DirectoryOutput::DirectoryPartition(d) => d.list(trx, path).await,
343        }
344    }
345}
346
347// Strinc returns the first key that would sort outside the range prefixed by prefix.
348pub(crate) fn strinc(key: Vec<u8>) -> Vec<u8> {
349    let mut key = key;
350
351    for i in (0..key.len()).rev() {
352        if key[i] != 0xff {
353            key[i] += 1;
354            break;
355        } else {
356            // stripping key from trailing 0xFF bytes
357            key.remove(i);
358        }
359    }
360    key
361}
362
363#[cfg(test)]
364mod tests {
365    use super::*;
366
367    // https://github.com/apple/foundationdb/blob/e34df983ee8c0db333babf36fb620318d026553d/bindings/c/test/unit/unit_tests.cpp#L95
368    #[test]
369    fn test_strinc() {
370        assert_eq!(strinc(Vec::from("a".as_bytes())), Vec::from("b".as_bytes()));
371        assert_eq!(strinc(Vec::from("y".as_bytes())), Vec::from("z".as_bytes()));
372        assert_eq!(
373            strinc(Vec::from("!".as_bytes())),
374            Vec::from("\"".as_bytes())
375        );
376        assert_eq!(strinc(Vec::from("*".as_bytes())), Vec::from("+".as_bytes()));
377        assert_eq!(
378            strinc(Vec::from("fdb".as_bytes())),
379            Vec::from("fdc".as_bytes())
380        );
381        assert_eq!(
382            strinc(Vec::from("foundation database 6".as_bytes())),
383            Vec::from("foundation database 7".as_bytes())
384        );
385
386        assert_eq!(strinc(vec![61u8, 62u8, 255u8]), vec![61u8, 63u8]);
387        assert_eq!(strinc(vec![253u8, 255u8]), vec![254u8]);
388        assert_eq!(strinc(vec![253u8, 255u8, 255u8]), vec![254u8]);
389        assert_eq!(strinc(vec![255u8, 255u8, 255u8]), Vec::<u8>::new());
390    }
391}