diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..fb45242 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,59 @@ +name: CI + +run-name: "CI run '${{ github.head_ref || github.ref_name }}'" + +on: + workflow_dispatch: + push: + branches: + - main + pull_request: + merge_group: + +defaults: + run: + shell: bash + +jobs: + ci: + strategy: + matrix: + os: ["ubuntu-latest"] + + runs-on: ${{ matrix.os }} + + steps: + - uses: actions/checkout@v2 + - name: Cache + id: rust-cache + uses: actions/cache@v3 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + target/ + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.toml', '.github/workflows/*.yml') }} + + - uses: actions-rs/toolchain@v1 + with: + toolchain: 1.75.0 + components: rustfmt, clippy + default: true + + - name: Install cargo-deny + if: steps.rust-cache.outputs.cache-hit != 'true' + run: rustup run --install 1.70 cargo install --force --version 0.14.3 cargo-deny --locked + + - name: Compile + run: cargo build --all-targets --all-features + + - name: Run tests + run: make test + + - name: Clippy + run: cargo clippy --all --all-targets -- -Dwarnings + + - name: Format + run: cargo fmt --all -- --check diff --git a/Cargo.lock b/Cargo.lock index e39e944..a80638d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -577,7 +577,7 @@ checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" [[package]] name = "vart" -version = "0.1.0" +version = "0.1.1" dependencies = [ "criterion", "hashbrown", diff --git a/Cargo.toml b/Cargo.toml index e5ec107..c39a783 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "vart" publish = true -version = "0.1.0" +version = "0.1.1" edition = "2021" license = "Apache-2.0" readme = "README.md" diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..df39bbb --- /dev/null +++ b/Makefile @@ -0,0 +1,14 @@ +# This command builds the project, including all targets, and generates the documentation. +build: check + cargo build --all-targets + cargo doc + +# This command checks the licenses of all dependencies, formats the code, and runs the Clippy linter. +check: + cargo deny --all-features check licenses + cargo fmt --all -- --check + cargo clippy --all --all-targets + +# This command runs the tests with backtrace enabled. +test: check + RUST_BACKTRACE=1 cargo test diff --git a/deny.toml b/deny.toml new file mode 100644 index 0000000..4af2834 --- /dev/null +++ b/deny.toml @@ -0,0 +1,23 @@ +# Configuration documentation: +#  https://embarkstudios.github.io/cargo-deny/index.html + +[advisories] +vulnerability = "deny" +yanked = "deny" +unmaintained = "warn" +notice = "warn" +ignore = [ +] +git-fetch-with-cli = true + +[licenses] +allow-osi-fsf-free = "either" +copyleft = "warn" +unlicensed = "deny" +default = "deny" +confidence-threshold = 0.8 + + +[bans] +multiple-versions = "warn" +highlight = "all" diff --git a/src/art.rs b/src/art.rs index 8d46178..7105110 100644 --- a/src/art.rs +++ b/src/art.rs @@ -27,7 +27,7 @@ const NODE48MAX: usize = 48; const NODE256MIN: usize = NODE48MAX + 1; // Maximum number of active snapshots -pub(crate) const DEFAULT_MAX_ACTIVE_SNAPSHOTS: u64 = 10000; +pub const DEFAULT_MAX_ACTIVE_SNAPSHOTS: u64 = 10000; /// A struct representing a node in an Adaptive Radix Trie. /// @@ -470,7 +470,7 @@ impl Node { /// /// Returns `true` if the node type is an inner node, otherwise returns `false`. /// - #[inline] + #[allow(dead_code)] pub(crate) fn is_inner(&self) -> bool { !self.is_twig() } @@ -652,7 +652,7 @@ impl Node { commit_version: u64, ts: u64, depth: usize, - ) -> Result<(Arc>, Option), TrieError> { + ) -> Result<(NodeArc, Option), TrieError> { // Obtain the current node's prefix and its length. let cur_node_prefix = cur_node.prefix().clone(); let cur_node_prefix_len = cur_node.prefix().len(); @@ -912,6 +912,9 @@ pub struct KV { pub ts: u64, } +// A type alias for a node reference. +type NodeArc = Arc>; + impl KV { pub fn new(key: P, value: V, version: u64, timestamp: u64) -> Self { KV { @@ -1204,7 +1207,7 @@ impl Tree { /// Returns `Ok(())` if the snapshot is successfully closed and removed. Returns an `Err` /// with `TrieError::SnapshotNotFound` if the snapshot with the given ID is not found. /// - pub(crate) fn close_snapshot(&mut self, snapshot_id: u64) -> Result<(), TrieError> { + pub fn close_snapshot(&mut self, snapshot_id: u64) -> Result<(), TrieError> { // Check if the tree is already closed self.is_closed()?; @@ -1312,7 +1315,7 @@ mod tests { fn read_words_from_file(file_path: &str) -> io::Result> { let file = File::open(file_path)?; let reader = BufReader::new(file); - let words: Vec = reader.lines().filter_map(|line| line.ok()).collect(); + let words: Vec = reader.lines().map_while(Result::ok).collect(); Ok(words) } @@ -1325,7 +1328,7 @@ mod tests { // Insertion phase for word in &words { let key = &VariableSizeKey::from_str(word).unwrap(); - tree.insert(key, 1, 0, 0); + let _ = tree.insert(key, 1, 0, 0); } // Search phase @@ -1357,7 +1360,7 @@ mod tests { ]; for word in &insert_words { - tree.insert(&VariableSizeKey::from_str(word).unwrap(), 1, 0, 0); + let _ = tree.insert(&VariableSizeKey::from_str(word).unwrap(), 1, 0, 0); } // Deletion phase @@ -1380,7 +1383,7 @@ mod tests { ]; for (word, val) in &words_to_insert { - tree.insert(&VariableSizeKey::from_str(word).unwrap(), *val, 0, 0); + let _ = tree.insert(&VariableSizeKey::from_str(word).unwrap(), *val, 0, 0); } // Verification phase @@ -1399,7 +1402,7 @@ mod tests { // Insertion phase let key = VariableSizeKey::from_str("abc").unwrap(); let value = 1; - tree.insert(&key, value, 0, 0); + let _ = tree.insert(&key, value, 0, 0); // Verification phase let (_, val, _ts, _) = tree.get(&key, 0).unwrap(); @@ -1429,7 +1432,7 @@ mod tests { // Insertion let key = VariableSizeKey::from_str("test").unwrap(); let value = 1; - tree.insert(&key, value, 0, 0); + let _ = tree.insert(&key, value, 0, 0); // Removal assert!(tree.remove(&key).unwrap()); @@ -1446,8 +1449,8 @@ mod tests { let mut tree = Tree::::new(); // Insertion - tree.insert(&key1, 1, 0, 0); - tree.insert(&key2, 1, 0, 0); + tree.insert(&key1, 1, 0, 0).unwrap(); + tree.insert(&key2, 1, 0, 0).unwrap(); // Removal assert!(tree.remove(&key1).unwrap()); @@ -1470,8 +1473,8 @@ mod tests { let mut tree = Tree::::new(); // Insertion - tree.insert(&key1, 1, 0, 0); - tree.insert(&key2, 1, 0, 0); + tree.insert(&key1, 1, 0, 0).unwrap(); + tree.insert(&key2, 1, 0, 0).unwrap(); // Removal assert!(tree.remove(&key1).unwrap()); @@ -1514,7 +1517,7 @@ mod tests { // Insertion for i in 0..5u32 { let key = VariableSizeKey::from_slice(&i.to_be_bytes()); - tree.insert(&key, 1, 0, 0); + tree.insert(&key, 1, 0, 0).unwrap(); } // Removal @@ -1562,7 +1565,7 @@ mod tests { // Insertion for i in 0..17u32 { let key = VariableSizeKey::from_slice(&i.to_be_bytes()); - tree.insert(&key, 1, 0, 0); + tree.insert(&key, 1, 0, 0).unwrap(); } // Removal @@ -1585,7 +1588,7 @@ mod tests { // Insertion for i in 0..17u32 { let key = VariableSizeKey::from_slice(&i.to_be_bytes()); - tree.insert(&key, 1, 0, 0); + tree.insert(&key, 1, 0, 0).unwrap(); } // Root verification @@ -1652,7 +1655,7 @@ mod tests { // Insertion for i in 0..49u32 { let key = VariableSizeKey::from_slice(&i.to_be_bytes()); - tree.insert(&key, 1, 0, 0); + tree.insert(&key, 1, 0, 0).unwrap(); } // Root verification @@ -1686,7 +1689,7 @@ mod tests { // // } #[derive(Debug, Clone, PartialEq)] - struct KVT { + struct Kvt { k: Vec, // Key version: u64, // version } @@ -1696,27 +1699,27 @@ mod tests { let mut tree: Tree = Tree::::new(); let kvts = vec![ - KVT { + Kvt { k: b"key1_0".to_vec(), version: 0, }, - KVT { + Kvt { k: b"key2_0".to_vec(), version: 0, }, - KVT { + Kvt { k: b"key3_0".to_vec(), version: 0, }, - KVT { + Kvt { k: b"key4_0".to_vec(), version: 0, }, - KVT { + Kvt { k: b"key5_0".to_vec(), version: 0, }, - KVT { + Kvt { k: b"key6_0".to_vec(), version: 0, }, @@ -1870,7 +1873,7 @@ mod tests { // Insertion for i in 0..u16::MAX { let key: FixedSizeKey<16> = i.into(); - tree.insert(&key, i, 0, i as u64); + tree.insert(&key, i, 0, i as u64).unwrap(); } // Iteration and verification @@ -1898,7 +1901,7 @@ mod tests { // Insertion for i in 0..u8::MAX { let key: FixedSizeKey<32> = i.into(); - tree.insert(&key, i, 0, 0); + tree.insert(&key, i, 0, 0).unwrap(); } // Iteration and verification @@ -1925,7 +1928,7 @@ mod tests { // Insertion for i in 0..=max { let key: FixedSizeKey<8> = i.into(); - tree.insert(&key, i, 0, 0); + tree.insert(&key, i, 0, 0).unwrap(); } // Test inclusive range @@ -1973,7 +1976,7 @@ mod tests { // Insertion for i in 0..=max { let key: FixedSizeKey<16> = i.into(); - tree.insert(&key, i, 0, 0); + tree.insert(&key, i, 0, 0).unwrap(); } let mut len = 0usize; @@ -1993,9 +1996,9 @@ mod tests { // Insertions let key1 = VariableSizeKey::from_str("abc").unwrap(); let key2 = VariableSizeKey::from_str("efg").unwrap(); - tree.insert(&key1, 1, 0, 0); - tree.insert(&key1, 2, 10, 0); - tree.insert(&key2, 3, 11, 0); + tree.insert(&key1, 1, 0, 0).unwrap(); + tree.insert(&key1, 2, 10, 0).unwrap(); + tree.insert(&key2, 3, 11, 0).unwrap(); // Versioned retrievals and assertions let (_, val, _, _) = tree.get(&key1, 1).unwrap(); diff --git a/src/iter.rs b/src/iter.rs index d07abed..9e57b46 100644 --- a/src/iter.rs +++ b/src/iter.rs @@ -8,6 +8,7 @@ use crate::KeyTrait; // TODO: need to add more tests for snapshot readers /// A structure representing a pointer for iterating over the Trie's key-value pairs. pub struct IterationPointer { + #[allow(dead_code)] pub(crate) id: u64, root: Arc>, } @@ -45,9 +46,11 @@ impl IterationPointer { } } +type NodeIterator<'a, P, V> = Box>)> + 'a>; + /// An iterator over the nodes in the Trie. struct NodeIter<'a, P: KeyTrait, V: Clone> { - node: Box>)> + 'a>, + node: NodeIterator<'a, P, V>, } impl<'a, P: KeyTrait, V: Clone> NodeIter<'a, P, V> { diff --git a/src/lib.rs b/src/lib.rs index 9bcc4b2..d5839ae 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,7 +8,6 @@ use std::cmp::{Ord, Ordering, PartialOrd}; use std::error::Error; use std::fmt; use std::fmt::Debug; -use std::mem::MaybeUninit; use std::str::FromStr; // "Partial" in the Adaptive Radix Tree paper refers to "partial keys", a technique employed @@ -23,6 +22,9 @@ pub trait Key { fn prefix_after(&self, start: usize) -> Self; fn longest_common_prefix(&self, slice: &[u8]) -> usize; fn as_slice(&self) -> &[u8]; + fn is_empty(&self) -> bool { + self.len() == 0 + } } pub trait KeyTrait: @@ -319,6 +321,12 @@ pub struct BitSet { bits: [bool; SIZE], } +impl Default for BitSet { + fn default() -> Self { + Self::new() + } +} + impl BitSet { pub fn new() -> Self { Self { diff --git a/src/snapshot.rs b/src/snapshot.rs index 5b47342..f0f1efa 100644 --- a/src/snapshot.rs +++ b/src/snapshot.rs @@ -160,6 +160,14 @@ impl Snapshot { self.root = new_root; Ok(is_deleted) } + + pub fn ts(&self) -> u64 { + self.ts + } + + pub fn id(&self) -> u64 { + self.id + } } #[cfg(test)]