From eff339170c5602e807d70a1074bd7047798b294d Mon Sep 17 00:00:00 2001 From: Al Liu Date: Tue, 10 Sep 2024 20:57:31 +0800 Subject: [PATCH] Finish basic functionalities implementation (#1) --- .github/workflows/ci.yml | 66 +- .github/workflows/loc.yml | 2 +- Cargo.toml | 37 +- README-zh_CN.md | 28 +- README.md | 30 +- ci/miri.sh | 13 - ci/miri_sb.sh | 13 + ci/miri_sb_generic.sh | 10 + ci/miri_tb.sh | 13 + ci/miri_tb_generic.sh | 10 + ci/sanitizer.sh | 6 +- ci/sanitizer_generic.sh | 14 + src/buffer.rs | 273 +++++ src/error.rs | 104 ++ src/lib.rs | 184 +++- src/options.rs | 314 ++++++ src/swmr.rs | 5 + src/swmr/generic.rs | 1313 +++++++++++++++++++++++ src/swmr/generic/entry.rs | 44 + src/swmr/generic/iter.rs | 155 +++ src/swmr/generic/reader.rs | 141 +++ src/swmr/generic/tests.rs | 135 +++ src/swmr/generic/traits.rs | 112 ++ src/swmr/generic/traits/impls.rs | 21 + src/swmr/generic/traits/impls/bytes.rs | 82 ++ src/swmr/generic/traits/impls/string.rs | 56 + src/swmr/wal.rs | 441 ++++++++ src/swmr/wal/iter.rs | 288 +++++ src/swmr/wal/reader.rs | 178 +++ src/swmr/wal/tests.rs | 19 + src/tests.rs | 83 ++ src/unsync.rs | 396 +++++++ src/unsync/c.rs | 42 + src/unsync/iter.rs | 262 +++++ src/unsync/tests.rs | 35 + src/utils.rs | 75 ++ src/wal.rs | 465 ++++++++ src/wal/builder.rs | 328 ++++++ src/wal/sealed.rs | 153 +++ tests/foo.rs | 1 - 40 files changed, 5862 insertions(+), 85 deletions(-) delete mode 100755 ci/miri.sh create mode 100755 ci/miri_sb.sh create mode 100755 ci/miri_sb_generic.sh create mode 100755 ci/miri_tb.sh create mode 100755 ci/miri_tb_generic.sh create mode 100755 ci/sanitizer_generic.sh create mode 100644 src/buffer.rs create mode 100644 src/error.rs create mode 100644 src/options.rs create mode 100644 src/swmr.rs create mode 100644 src/swmr/generic.rs create mode 100644 src/swmr/generic/entry.rs create mode 100644 src/swmr/generic/iter.rs create mode 100644 src/swmr/generic/reader.rs create mode 100644 src/swmr/generic/tests.rs create mode 100644 src/swmr/generic/traits.rs create mode 100644 src/swmr/generic/traits/impls.rs create mode 100644 src/swmr/generic/traits/impls/bytes.rs create mode 100644 src/swmr/generic/traits/impls/string.rs create mode 100644 src/swmr/wal.rs create mode 100644 src/swmr/wal/iter.rs create mode 100644 src/swmr/wal/reader.rs create mode 100644 src/swmr/wal/tests.rs create mode 100644 src/tests.rs create mode 100644 src/unsync.rs create mode 100644 src/unsync/c.rs create mode 100644 src/unsync/iter.rs create mode 100644 src/unsync/tests.rs create mode 100644 src/utils.rs create mode 100644 src/wal.rs create mode 100644 src/wal/builder.rs create mode 100644 src/wal/sealed.rs delete mode 100644 tests/foo.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 42cb38b..c635413 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,7 +20,7 @@ on: env: CARGO_TERM_COLOR: always - RUSTFLAGS: -Dwarnings + # RUSTFLAGS: -Dwarnings RUST_BACKTRACE: 1 nightly: nightly stable: stable @@ -63,7 +63,7 @@ jobs: - name: Install cargo-hack run: cargo install cargo-hack - name: Apply clippy lints - run: cargo hack clippy --each-feature + run: cargo hack clippy --each-feature --exclude-no-default-features # Run tests on some extra platforms cross: @@ -149,7 +149,7 @@ jobs: path: ~/.cargo key: ${{ runner.os }}-coverage-dotcargo - name: Run build - run: cargo hack build --feature-powerset + run: cargo hack build --feature-powerset --exclude-no-default-features test: name: test @@ -183,7 +183,7 @@ jobs: path: ~/.cargo key: ${{ runner.os }}-coverage-dotcargo - name: Run test - run: cargo hack test --feature-powerset + run: cargo hack test --feature-powerset --exclude-no-default-features sanitizer: name: sanitizer @@ -191,8 +191,8 @@ jobs: matrix: os: - ubuntu-latest - - macos-latest - - windows-latest + # - macos-latest + # - windows-latest runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v3 @@ -212,11 +212,45 @@ jobs: run: rustup component add rust-src - name: Install cargo-hack run: cargo install cargo-hack - - name: ASAN / LSAN / TSAN + - name: ASAN / LSAN / TSAN (Linux) run: ci/sanitizer.sh + if: matrix.os == 'ubuntu-latest' + - name: ASAN / LSAN / TSAN + run: ci/sanitizer_generic.sh + if: matrix.os != 'ubuntu-latest' + + miri-tb: + name: miri-tb + strategy: + matrix: + os: + - ubuntu-latest + - macos-latest + - windows-latest + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v3 + - name: Cache cargo build and registry + uses: actions/cache@v3 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-miri-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-miri- + - name: Install cargo-hack + run: cargo install cargo-hack + - name: Miri (Linux) + run: ci/miri_tb.sh + if: matrix.os == 'ubuntu-latest' + - name: Miri + run: ci/miri_tb_generic.sh + if: matrix.os != 'ubuntu-latest' - miri: - name: miri + miri-sb: + name: miri-sb strategy: matrix: os: @@ -238,8 +272,13 @@ jobs: ${{ runner.os }}-miri- - name: Install cargo-hack run: cargo install cargo-hack + - name: Miri (Linux) + run: ci/miri_sb.sh + if: matrix.os == 'ubuntu-latest' - name: Miri - run: ci/miri.sh + run: ci/miri_sb_generic.sh + if: matrix.os != 'ubuntu-latest' + loom: name: loom strategy: @@ -263,10 +302,8 @@ jobs: ${{ runner.os }}-loom- - name: Install Rust run: rustup update $nightly && rustup default $nightly - - name: Install cargo-hack - run: cargo install cargo-hack - name: Loom tests - run: RUSTFLAGS="--cfg loom -Dwarnings" cargo hack test --test loom + run: cargo test --tests --features loom,memmap # valgrind valgrind: @@ -342,7 +379,8 @@ jobs: command: tarpaulin args: --all-features --run-types tests --run-types doctests --workspace --out xml - name: Upload to codecov.io - uses: codecov/codecov-action@v3.1.1 + uses: codecov/codecov-action@v4 with: token: ${{ secrets.CODECOV_TOKEN }} + slug: ${{ github.repository }} fail_ci_if_error: true diff --git a/.github/workflows/loc.yml b/.github/workflows/loc.yml index 9011e7f..0d85c00 100644 --- a/.github/workflows/loc.yml +++ b/.github/workflows/loc.yml @@ -51,7 +51,7 @@ jobs: await github.rest.gists.update({ gist_id: gistId, files: { - "template-rs": { + "orderwal": { content: output } } diff --git a/Cargo.toml b/Cargo.toml index 34be434..a635d8e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,13 +1,13 @@ [package] -name = "template-rs" -version = "0.1.6" +name = "orderwal" +version = "0.0.0" edition = "2021" -repository = "https://github.com/al8n/template-rs" -homepage = "https://github.com/al8n/template-rs" -documentation = "https://docs.rs/template-rs" -description = "A template for creating Rust open-source repo on GitHub" -license = "MIT/Apache-2.0" -rust-version = "1.73" +repository = "https://github.com/al8n/orderwal" +homepage = "https://github.com/al8n/orderwal" +documentation = "https://docs.rs/orderwal" +description = "An ordered Write-Ahead Log implementation for Rust." +license = "MIT OR Apache-2.0" +rust-version = "1.80" [[bench]] path = "benches/foo.rs" @@ -16,10 +16,27 @@ harness = false [features] default = ["std"] -alloc = [] -std = [] +alloc = ["rarena-allocator/alloc", "crossbeam-skiplist/alloc", "dbutils/alloc"] +std = ["rarena-allocator/default", "crossbeam-skiplist/default", "bitflags/default", "dbutils/default", "among/default", "faststr?/default", "bytes?/default", "smol_str?/default"] + +xxhash3 = ["dbutils/xxhash3"] +xxhash64 = ["dbutils/xxhash64"] [dependencies] +among = { version = "0.1", default-features = false } +bitflags = { version = "1", default-features = false } +dbutils = { version = "0.3", default-features = false, features = ["crc32fast"] } +rarena-allocator = { version = "0.2", default-features = false, features = ["memmap"] } +crossbeam-skiplist = { version = "0.1", default-features = false, package = "crossbeam-skiplist-pr1132" } +paste = "1" +thiserror = "1" + +bytes = { version = "1", default-features = false, optional = true } +smallvec = { version = "1", default-features = false, optional = true, features = ["const_generics"] } +smol_str = { version = "0.3", default-features = false, optional = true } +faststr = { version = "0.2", default-features = false, optional = true } + +tracing = { version = "0.1", optional = true } [dev-dependencies] criterion = "0.5" diff --git a/README-zh_CN.md b/README-zh_CN.md index 7a07f4d..380244b 100644 --- a/README-zh_CN.md +++ b/README-zh_CN.md @@ -1,18 +1,18 @@
-

template-rs

+

orderwal

开源Rust代码库GitHub模版 -[github][Github-url] -LoC -[Build][CI-url] -[codecov][codecov-url] +[github][Github-url] +LoC +[Build][CI-url] +[codecov][codecov-url] -[docs.rs][doc-url] -[crates.io][crates-url] -[crates.io][crates-url] +[docs.rs][doc-url] +[crates.io][crates-url] +[crates.io][crates-url] license [English][en-url] | 简体中文 @@ -32,20 +32,20 @@ template_rs = "0.1" #### License -`Template-rs` is under the terms of both the MIT license and the +`orderwal` is under the terms of both the MIT license and the Apache License (Version 2.0). See [LICENSE-APACHE](LICENSE-APACHE), [LICENSE-MIT](LICENSE-MIT) for details. Copyright (c) 2021 Al Liu. -[Github-url]: https://github.com/al8n/template-rs/ +[Github-url]: https://github.com/al8n/orderwal/ [CI-url]: https://github.com/al8n/template/actions/workflows/template.yml -[doc-url]: https://docs.rs/template-rs -[crates-url]: https://crates.io/crates/template-rs -[codecov-url]: https://app.codecov.io/gh/al8n/template-rs/ +[doc-url]: https://docs.rs/orderwal +[crates-url]: https://crates.io/crates/orderwal +[codecov-url]: https://app.codecov.io/gh/al8n/orderwal/ [license-url]: https://opensource.org/licenses/Apache-2.0 [rustc-url]: https://github.com/rust-lang/rust/blob/master/RELEASES.md [license-apache-url]: https://opensource.org/licenses/Apache-2.0 [license-mit-url]: https://opensource.org/licenses/MIT -[en-url]: https://github.com/al8n/template-rs/tree/main/README.md +[en-url]: https://github.com/al8n/orderwal/tree/main/README.md diff --git a/README.md b/README.md index 1af27e2..da30a67 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,18 @@
-

template-rs

+

orderwal

A template for creating Rust open-source GitHub repo. -[github][Github-url] -LoC -[Build][CI-url] -[codecov][codecov-url] +[github][Github-url] +LoC +[Build][CI-url] +[codecov][codecov-url] -[docs.rs][doc-url] -[crates.io][crates-url] -[crates.io][crates-url] +[docs.rs][doc-url] +[crates.io][crates-url] +[crates.io][crates-url] license English | [简体中文][zh-cn-url] @@ -31,16 +31,16 @@ template_rs = "0.1" #### License -`template-rs` is under the terms of both the MIT license and the +`orderwal` is under the terms of both the MIT license and the Apache License (Version 2.0). See [LICENSE-APACHE](LICENSE-APACHE), [LICENSE-MIT](LICENSE-MIT) for details. Copyright (c) 2021 Al Liu. -[Github-url]: https://github.com/al8n/template-rs/ -[CI-url]: https://github.com/al8n/template-rs/actions/workflows/ci.yml -[doc-url]: https://docs.rs/template-rs -[crates-url]: https://crates.io/crates/template-rs -[codecov-url]: https://app.codecov.io/gh/al8n/template-rs/ -[zh-cn-url]: https://github.com/al8n/template-rs/tree/main/README-zh_CN.md +[Github-url]: https://github.com/al8n/orderwal/ +[CI-url]: https://github.com/al8n/orderwal/actions/workflows/ci.yml +[doc-url]: https://docs.rs/orderwal +[crates-url]: https://crates.io/crates/orderwal +[codecov-url]: https://app.codecov.io/gh/al8n/orderwal/ +[zh-cn-url]: https://github.com/al8n/orderwal/tree/main/README-zh_CN.md diff --git a/ci/miri.sh b/ci/miri.sh deleted file mode 100755 index 35ec5f4..0000000 --- a/ci/miri.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash -set -e - -rustup toolchain install nightly --component miri -rustup override set nightly -cargo miri setup - -export MIRIFLAGS="-Zmiri-strict-provenance -Zmiri-disable-isolation -Zmiri-symbolic-alignment-check" - -cargo miri test --tests --target x86_64-unknown-linux-gnu -cargo miri test --tests --target aarch64-unknown-linux-gnu -cargo miri test --tests --target i686-unknown-linux-gnu -cargo miri test --tests --target powerpc64-unknown-linux-gnu diff --git a/ci/miri_sb.sh b/ci/miri_sb.sh new file mode 100755 index 0000000..0bc803c --- /dev/null +++ b/ci/miri_sb.sh @@ -0,0 +1,13 @@ +#!/bin/bash +set -e + +rustup toolchain install nightly --component miri +rustup override set nightly +cargo miri setup + +export MIRIFLAGS="-Zmiri-strict-provenance -Zmiri-disable-isolation -Zmiri-symbolic-alignment-check" + +cargo miri test --tests --target x86_64-unknown-linux-gnu --all-features +# cargo miri test --tests --target aarch64-unknown-linux-gnu #crossbeam_utils has problem on this platform +cargo miri test --tests --target i686-unknown-linux-gnu --all-features +cargo miri test --tests --target powerpc64-unknown-linux-gnu --all-features diff --git a/ci/miri_sb_generic.sh b/ci/miri_sb_generic.sh new file mode 100755 index 0000000..e47db57 --- /dev/null +++ b/ci/miri_sb_generic.sh @@ -0,0 +1,10 @@ +#!/bin/bash +set -e + +rustup toolchain install nightly --component miri +rustup override set nightly +cargo miri setup + +export MIRIFLAGS="-Zmiri-strict-provenance -Zmiri-disable-isolation -Zmiri-symbolic-alignment-check" + +cargo miri test --tests --all-features diff --git a/ci/miri_tb.sh b/ci/miri_tb.sh new file mode 100755 index 0000000..8048fa6 --- /dev/null +++ b/ci/miri_tb.sh @@ -0,0 +1,13 @@ +#!/bin/bash +set -e + +rustup toolchain install nightly --component miri +rustup override set nightly +cargo miri setup + +export MIRIFLAGS="-Zmiri-strict-provenance -Zmiri-disable-isolation -Zmiri-symbolic-alignment-check -Zmiri-tree-borrows" + +cargo miri test --tests --target x86_64-unknown-linux-gnu --all-features +# cargo miri test --tests --target aarch64-unknown-linux-gnu #crossbeam_utils has problem on this platform +cargo miri test --tests --target i686-unknown-linux-gnu --all-features +cargo miri test --tests --target powerpc64-unknown-linux-gnu --all-features diff --git a/ci/miri_tb_generic.sh b/ci/miri_tb_generic.sh new file mode 100755 index 0000000..591b119 --- /dev/null +++ b/ci/miri_tb_generic.sh @@ -0,0 +1,10 @@ +#!/bin/bash +set -e + +rustup toolchain install nightly --component miri +rustup override set nightly +cargo miri setup + +export MIRIFLAGS="-Zmiri-strict-provenance -Zmiri-disable-isolation -Zmiri-symbolic-alignment-check -Zmiri-tree-borrows" + +cargo miri test --tests --all-features diff --git a/ci/sanitizer.sh b/ci/sanitizer.sh index c8b9eee..eed854e 100755 --- a/ci/sanitizer.sh +++ b/ci/sanitizer.sh @@ -6,12 +6,12 @@ export ASAN_OPTIONS="detect_odr_violation=0 detect_leaks=0" # Run address sanitizer RUSTFLAGS="-Z sanitizer=address" \ -cargo test --tests --target x86_64-unknown-linux-gnu +cargo test --tests --target x86_64-unknown-linux-gnu --all-features # Run leak sanitizer RUSTFLAGS="-Z sanitizer=leak" \ -cargo test --tests --target x86_64-unknown-linux-gnu +cargo test --tests --target x86_64-unknown-linux-gnu --all-features # Run thread sanitizer RUSTFLAGS="-Z sanitizer=thread" \ -cargo -Zbuild-std test --tests --target x86_64-unknown-linux-gnu +cargo -Zbuild-std test --tests --target x86_64-unknown-linux-gnu --all-features diff --git a/ci/sanitizer_generic.sh b/ci/sanitizer_generic.sh new file mode 100755 index 0000000..728321e --- /dev/null +++ b/ci/sanitizer_generic.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +set -ex + +export ASAN_OPTIONS="detect_odr_violation=0 detect_leaks=0" + +# Run address sanitizer +RUSTFLAGS="-Z sanitizer=address" \ +cargo test --tests --all-features + +# Run leak sanitizer +RUSTFLAGS="-Z sanitizer=leak" \ +cargo test --tests --all-features + diff --git a/src/buffer.rs b/src/buffer.rs new file mode 100644 index 0000000..c02476e --- /dev/null +++ b/src/buffer.rs @@ -0,0 +1,273 @@ +use core::{ + marker::PhantomData, + ptr::{self, NonNull}, + slice, +}; + +/// Returns when the bytes are too large to be written to the vacant buffer. +#[derive(Debug, Default, Clone, Copy)] +pub struct TooLarge { + remaining: usize, + write: usize, +} + +impl core::fmt::Display for TooLarge { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!( + f, + "buffer does not have enough space (remaining {}, want {})", + self.remaining, self.write + ) + } +} + +#[cfg(feature = "std")] +impl std::error::Error for TooLarge {} + +/// A vacant buffer in the WAL. +#[must_use = "vacant buffer must be filled with bytes."] +#[derive(Debug)] +pub struct VacantBuffer<'a> { + value: NonNull, + len: usize, + cap: usize, + _m: PhantomData<&'a ()>, +} + +impl<'a> VacantBuffer<'a> { + /// Fill the remaining space with the given byte. + pub fn fill(&mut self, byte: u8) { + if self.cap == 0 { + return; + } + + // SAFETY: the value's ptr is aligned and the cap is the correct. + unsafe { + ptr::write_bytes(self.value.as_ptr(), byte, self.cap); + } + self.len = self.cap; + } + + /// Write bytes to the vacant value. + pub fn write(&mut self, bytes: &[u8]) -> Result<(), TooLarge> { + let len = bytes.len(); + let remaining = self.cap - self.len; + if len > remaining { + return Err(TooLarge { + remaining, + write: len, + }); + } + + // SAFETY: the value's ptr is aligned and the cap is the correct. + unsafe { + self + .value + .as_ptr() + .add(self.len) + .copy_from(bytes.as_ptr(), len); + } + + self.len += len; + Ok(()) + } + + /// Write bytes to the vacant value without bounds checking. + /// + /// # Panics + /// - If a slice is larger than the remaining space. + pub fn write_unchecked(&mut self, bytes: &[u8]) { + let len = bytes.len(); + let remaining = self.cap - self.len; + if len > remaining { + panic!( + "buffer does not have enough space (remaining {}, want {})", + remaining, len + ); + } + + // SAFETY: the value's ptr is aligned and the cap is the correct. + unsafe { + self + .value + .as_ptr() + .add(self.len) + .copy_from(bytes.as_ptr(), len); + } + self.len += len; + } + + /// Returns the capacity of the vacant value. + #[inline] + pub const fn capacity(&self) -> usize { + self.cap + } + + /// Returns the length of the vacant value. + #[inline] + pub const fn len(&self) -> usize { + self.len + } + + /// Returns `true` if the vacant value is empty. + #[inline] + pub const fn is_empty(&self) -> bool { + self.len == 0 + } + + /// Returns the remaining space of the vacant value. + #[inline] + pub const fn remaining(&self) -> usize { + self.cap - self.len + } + + #[inline] + pub(crate) fn new(cap: usize, value: NonNull) -> Self { + Self { + value, + len: 0, + cap, + _m: PhantomData, + } + } +} + +impl<'a> core::ops::Deref for VacantBuffer<'a> { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + if self.cap == 0 { + return &[]; + } + + unsafe { slice::from_raw_parts(self.value.as_ptr(), self.len) } + } +} + +impl<'a> core::ops::DerefMut for VacantBuffer<'a> { + fn deref_mut(&mut self) -> &mut Self::Target { + if self.cap == 0 { + return &mut []; + } + + unsafe { slice::from_raw_parts_mut(self.value.as_ptr(), self.len) } + } +} + +impl<'a> AsRef<[u8]> for VacantBuffer<'a> { + fn as_ref(&self) -> &[u8] { + self + } +} + +impl<'a> AsMut<[u8]> for VacantBuffer<'a> { + fn as_mut(&mut self) -> &mut [u8] { + self + } +} + +impl<'a> PartialEq<[u8]> for VacantBuffer<'a> { + fn eq(&self, other: &[u8]) -> bool { + self.as_ref().eq(other) + } +} + +impl<'a> PartialEq> for [u8] { + fn eq(&self, other: &VacantBuffer<'a>) -> bool { + self.as_ref().eq(other.as_ref()) + } +} + +impl<'a> PartialEq<[u8]> for &VacantBuffer<'a> { + fn eq(&self, other: &[u8]) -> bool { + self.as_ref().eq(other) + } +} + +impl<'a> PartialEq<&VacantBuffer<'a>> for [u8] { + fn eq(&self, other: &&VacantBuffer<'a>) -> bool { + self.eq(other.as_ref()) + } +} + +impl<'a, const N: usize> PartialEq<[u8; N]> for VacantBuffer<'a> { + fn eq(&self, other: &[u8; N]) -> bool { + self.as_ref().eq(other.as_ref()) + } +} + +impl<'a, const N: usize> PartialEq> for [u8; N] { + fn eq(&self, other: &VacantBuffer<'a>) -> bool { + self.as_ref().eq(other.as_ref()) + } +} + +impl<'a, const N: usize> PartialEq<&VacantBuffer<'a>> for [u8; N] { + fn eq(&self, other: &&VacantBuffer<'a>) -> bool { + self.as_ref().eq(other.as_ref()) + } +} + +impl<'a, const N: usize> PartialEq<[u8; N]> for &VacantBuffer<'a> { + fn eq(&self, other: &[u8; N]) -> bool { + self.as_ref().eq(other.as_ref()) + } +} + +impl<'a, const N: usize> PartialEq<&mut VacantBuffer<'a>> for [u8; N] { + fn eq(&self, other: &&mut VacantBuffer<'a>) -> bool { + self.as_ref().eq(other.as_ref()) + } +} + +impl<'a, const N: usize> PartialEq<[u8; N]> for &mut VacantBuffer<'a> { + fn eq(&self, other: &[u8; N]) -> bool { + self.as_ref().eq(other.as_ref()) + } +} + +macro_rules! builder { + ($($name:ident($size:ident)),+ $(,)?) => { + $( + paste::paste! { + #[doc = "A " [< $name: snake>] " builder for the wal, which requires the " [< $name: snake>] " size for accurate allocation and a closure to build the " [< $name: snake>]] + #[derive(Copy, Clone, Debug)] + pub struct [< $name Builder >] { + size: $size, + f: F, + } + + impl [< $name Builder >] { + #[doc = "Creates a new `" [<$name Builder>] "` with the given size and builder closure."] + #[inline] + pub const fn new(size: $size, f: F) -> Self + where + F: for<'a> FnOnce(&mut VacantBuffer<'a>) -> Result<(), E>, + { + Self { size, f } + } + + #[doc = "Returns the required" [< $name: snake>] "size."] + #[inline] + pub const fn size(&self) -> $size { + self.size + } + + #[doc = "Returns the " [< $name: snake>] "builder closure."] + #[inline] + pub const fn builder(&self) -> &F { + &self.f + } + + /// Deconstructs the value builder into the size and the builder closure. + #[inline] + pub fn into_components(self) -> ($size, F) { + (self.size, self.f) + } + } + } + )* + }; +} + +builder!(Value(u32), Key(u32)); diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..be2bb36 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,104 @@ +/// The error type. +#[derive(Debug, thiserror::Error)] +pub enum Error { + /// Insufficient space in the WAL + #[error("insufficient space in the WAL (requested: {requested}, available: {available})")] + InsufficientSpace { + /// The requested size + requested: u32, + /// The remaining size + available: u32, + }, + /// The key is too large. + #[error("the key size is {size} larger than the maximum key size {maximum_key_size}")] + KeyTooLarge { + /// The size of the key. + size: u32, + /// The maximum key size. + maximum_key_size: u32, + }, + /// The value is too large. + #[error("the value size is {size} larger than the maximum value size {maximum_value_size}")] + ValueTooLarge { + /// The size of the value. + size: u32, + /// The maximum value size. + maximum_value_size: u32, + }, + /// The entry is too large. + #[error("the entry size is {size} larger than the maximum entry size {maximum_entry_size}")] + EntryTooLarge { + /// The size of the entry. + size: u64, + /// The maximum entry size. + maximum_entry_size: u64, + }, + /// I/O error. + #[error("{0}")] + IO(#[from] std::io::Error), + /// The WAL is read-only. + #[error("The WAL is read-only")] + ReadOnly, +} + +impl Error { + /// Create a new `Error::InsufficientSpace` instance. + pub(crate) const fn insufficient_space(requested: u32, available: u32) -> Self { + Self::InsufficientSpace { + requested, + available, + } + } + + /// Create a new `Error::KeyTooLarge` instance. + pub(crate) const fn key_too_large(size: u32, maximum_key_size: u32) -> Self { + Self::KeyTooLarge { + size, + maximum_key_size, + } + } + + /// Create a new `Error::ValueTooLarge` instance. + pub(crate) const fn value_too_large(size: u32, maximum_value_size: u32) -> Self { + Self::ValueTooLarge { + size, + maximum_value_size, + } + } + + /// Create a new `Error::EntryTooLarge` instance. + pub(crate) const fn entry_too_large(size: u64, maximum_entry_size: u64) -> Self { + Self::EntryTooLarge { + size, + maximum_entry_size, + } + } + + /// Create a new corrupted error. + #[inline] + pub(crate) fn corrupted() -> Error { + Self::IO(std::io::Error::new( + std::io::ErrorKind::InvalidData, + "corrupted write-ahead log", + )) + } + + /// Create a read-only error. + pub(crate) const fn read_only() -> Self { + Self::ReadOnly + } + + pub(crate) fn magic_text_mismatch() -> Error { + Self::IO(std::io::Error::new( + std::io::ErrorKind::InvalidData, + "magic text of orderwal does not match", + )) + } + + pub(crate) fn magic_version_mismatch() -> Error { + Self::IO(std::io::Error::new( + std::io::ErrorKind::InvalidData, + "magic version of orderwal does not match", + )) + } +} diff --git a/src/lib.rs b/src/lib.rs index d110fcc..107dc20 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,29 +1,185 @@ -//! A template for creating Rust open-source repo on GitHub +//! An ordered Write-Ahead Log implementation for Rust. +#![doc = include_str!("../README.md")] #![cfg_attr(not(any(feature = "std", test)), no_std)] #![cfg_attr(docsrs, feature(doc_cfg))] #![cfg_attr(docsrs, allow(unused_attributes))] #![deny(missing_docs)] +#![allow(clippy::type_complexity)] -#[cfg(all(not(feature = "std"), feature = "alloc"))] +use core::{borrow::Borrow, cmp, marker::PhantomData, mem, slice}; + +use among::Among; +use crossbeam_skiplist::SkipSet; +use error::Error; +use rarena_allocator::{ + either::{self, Either}, + Allocator, ArenaOptions, Freelist, Memory, MmapOptions, OpenOptions, +}; + +#[cfg(not(any(feature = "std", feature = "alloc")))] +compile_error!("`orderwal` requires either the 'std' or 'alloc' feature to be enabled"); + +#[cfg(not(feature = "std"))] extern crate alloc as std; -#[cfg(all(feature = "std", not(feature = "alloc")))] +#[cfg(feature = "std")] extern crate std; -#[cfg(all(feature = "std", feature = "alloc"))] -extern crate std; +pub use dbutils::{Ascend, CheapClone, Checksumer, Comparator, Crc32, Descend}; -/// template -pub fn it_works() -> usize { - 4 -} +#[cfg(feature = "xxhash3")] +pub use dbutils::XxHash3; + +#[cfg(feature = "xxhash64")] +pub use dbutils::XxHash64; + +const STATUS_SIZE: usize = mem::size_of::(); +const CHECKSUM_SIZE: usize = mem::size_of::(); +const CURRENT_VERSION: u16 = 0; +const MAGIC_TEXT: [u8; 6] = *b"ordwal"; +const MAGIC_TEXT_SIZE: usize = MAGIC_TEXT.len(); +const MAGIC_VERSION_SIZE: usize = mem::size_of::(); +const HEADER_SIZE: usize = MAGIC_TEXT_SIZE + MAGIC_VERSION_SIZE; + +/// Error types. +pub mod error; + +mod buffer; +pub use buffer::*; + +mod utils; +use utils::*; + +mod wal; +pub use wal::{Builder, Wal}; + +mod options; +pub use options::Options; + +/// A single writer multiple readers ordered write-ahead Log implementation. +pub mod swmr; + +/// An ordered write-ahead Log implementation. +pub mod unsync; #[cfg(test)] -mod tests { - use super::*; +mod tests; + +bitflags::bitflags! { + /// The flags of the entry. + struct Flags: u8 { + /// First bit: 1 indicates committed, 0 indicates uncommitted + const COMMITTED = 0b00000001; + } +} + +#[doc(hidden)] +pub struct Pointer { + /// The pointer to the start of the entry. + ptr: *const u8, + /// The length of the key. + key_len: usize, + /// The length of the value. + value_len: usize, + cmp: C, +} + +unsafe impl Send for Pointer {} +unsafe impl Sync for Pointer {} + +impl Pointer { + #[inline] + const fn new(key_len: usize, value_len: usize, ptr: *const u8, cmp: C) -> Self { + Self { + ptr, + key_len, + value_len, + cmp, + } + } + + #[inline] + const fn as_key_slice<'a>(&self) -> &'a [u8] { + if self.key_len == 0 { + return &[]; + } + + // SAFETY: `ptr` is a valid pointer to `len` bytes. + unsafe { slice::from_raw_parts(self.ptr, self.key_len) } + } + + #[inline] + const fn as_value_slice<'a, 'b: 'a>(&'a self) -> &'b [u8] { + if self.value_len == 0 { + return &[]; + } + + // SAFETY: `ptr` is a valid pointer to `len` bytes. + unsafe { slice::from_raw_parts(self.ptr.add(self.key_len), self.value_len) } + } +} + +impl PartialEq for Pointer { + fn eq(&self, other: &Self) -> bool { + self + .cmp + .compare(self.as_key_slice(), other.as_key_slice()) + .is_eq() + } +} + +impl Eq for Pointer {} + +impl PartialOrd for Pointer { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Pointer { + fn cmp(&self, other: &Self) -> cmp::Ordering { + self.cmp.compare(self.as_key_slice(), other.as_key_slice()) + } +} + +impl Borrow for Pointer +where + [u8]: Borrow, + Q: ?Sized + Ord, +{ + fn borrow(&self) -> &Q { + self.as_key_slice().borrow() + } +} + +/// Use to avoid the mutable borrow checker, for single writer multiple readers usecase. +struct UnsafeCellChecksumer(core::cell::UnsafeCell); + +impl UnsafeCellChecksumer { + #[inline] + const fn new(checksumer: S) -> Self { + Self(core::cell::UnsafeCell::new(checksumer)) + } +} + +impl UnsafeCellChecksumer +where + S: Checksumer, +{ + #[inline] + fn update(&self, buf: &[u8]) { + // SAFETY: the checksumer will not be invoked concurrently. + unsafe { (*self.0.get()).update(buf) } + } + + #[inline] + fn reset(&self) { + // SAFETY: the checksumer will not be invoked concurrently. + unsafe { (*self.0.get()).reset() } + } - #[test] - fn test_works() { - assert_eq!(it_works(), 4); + #[inline] + fn digest(&self) -> u64 { + unsafe { (*self.0.get()).digest() } } } diff --git a/src/options.rs b/src/options.rs new file mode 100644 index 0000000..cefac44 --- /dev/null +++ b/src/options.rs @@ -0,0 +1,314 @@ +/// Options for the WAL. +#[derive(Debug, Clone)] +pub struct Options { + maximum_key_size: u32, + maximum_value_size: u32, + sync_on_write: bool, + magic_version: u16, + huge: Option, + cap: u32, + reserved: u32, +} + +impl Default for Options { + #[inline] + fn default() -> Self { + Self::new() + } +} + +impl Options { + /// Create a new `Options` instance. + /// + /// + /// # Example + /// + /// **Note:** If you are creating in-memory WAL, then you must specify the capacity. + /// + /// ```rust + /// use orderwal::Options; + /// + /// let options = Options::new().with_capacity(1024 * 1024 * 8); // 8MB in-memory WAL + /// ``` + #[inline] + pub const fn new() -> Self { + Self { + maximum_key_size: u16::MAX as u32, + maximum_value_size: u32::MAX, + sync_on_write: true, + magic_version: 0, + huge: None, + cap: 0, + reserved: 0, + } + } + + /// Set the reserved bytes of the WAL. + /// + /// The `reserved` is used to configure the start position of the WAL. This is useful + /// when you want to add some bytes as your own WAL's header. + /// + /// The default reserved is `0`. + /// + /// # Example + /// + /// ```rust + /// use orderwal::Options; + /// + /// let opts = Options::new().with_reserved(8); + /// ``` + #[inline] + pub const fn with_reserved(mut self, reserved: u32) -> Self { + self.reserved = reserved; + self + } + + /// Get the reserved of the WAL. + /// + /// The `reserved` is used to configure the start position of the WAL. This is useful + /// when you want to add some bytes as your own WAL's header. + /// + /// The default reserved is `0`. + /// + /// # Example + /// + /// ```rust + /// use orderwal::Options; + /// + /// let opts = Options::new().with_reserved(8); + /// + /// assert_eq!(opts.reserved(), 8); + /// ``` + #[inline] + pub const fn reserved(&self) -> u32 { + self.reserved + } + + /// Returns the magic version. + /// + /// The default value is `0`. + /// + /// # Example + /// + /// ```rust + /// use orderwal::Options; + /// + /// let options = Options::new().with_magic_version(1); + /// assert_eq!(options.magic_version(), 1); + /// ``` + #[inline] + pub const fn magic_version(&self) -> u16 { + self.magic_version + } + + /// Returns the capacity of the WAL. + /// + /// The default value is `0`. + /// + /// # Example + /// + /// ```rust + /// use orderwal::Options; + /// + /// let options = Options::new().with_capacity(1000); + /// assert_eq!(options.capacity(), 1000); + /// ``` + #[inline] + pub const fn capacity(&self) -> u32 { + self.cap + } + + /// Returns the maximum key length. + /// + /// The default value is `u16::MAX`. + /// + /// # Example + /// + /// ```rust + /// use orderwal::Options; + /// + /// let options = Options::new().with_maximum_key_size(1024); + /// assert_eq!(options.maximum_key_size(), 1024); + /// ``` + #[inline] + pub const fn maximum_key_size(&self) -> u32 { + self.maximum_key_size + } + + /// Returns the maximum value length. + /// + /// The default value is `u32::MAX`. + /// + /// # Example + /// + /// ```rust + /// use orderwal::Options; + /// + /// let options = Options::new().with_maximum_value_size(1024); + /// assert_eq!(options.maximum_value_size(), 1024); + /// ``` + #[inline] + pub const fn maximum_value_size(&self) -> u32 { + self.maximum_value_size + } + + /// Returns `true` if the WAL syncs on write. + /// + /// The default value is `true`. + /// + /// # Example + /// + /// ```rust + /// use orderwal::Options; + /// + /// let options = Options::new(); + /// assert_eq!(options.sync_on_write(), true); + /// ``` + #[inline] + pub const fn sync_on_write(&self) -> bool { + self.sync_on_write + } + + /// Returns the bits of the page size. + /// + /// Configures the anonymous memory map to be allocated using huge pages. + /// + /// This option corresponds to the `MAP_HUGETLB` flag on Linux. It has no effect on Windows. + /// + /// The size of the requested page can be specified in page bits. + /// If not provided, the system default is requested. + /// The requested length should be a multiple of this, or the mapping will fail. + /// + /// This option has no effect on file-backed memory maps. + /// + /// The default value is `None`. + /// + /// # Example + /// + /// ```rust + /// use orderwal::Options; + /// + /// let options = Options::new().with_huge(64); + /// assert_eq!(options.huge(), Some(64)); + /// ``` + #[inline] + pub const fn huge(&self) -> Option { + self.huge + } + + /// Sets the capacity of the WAL. + /// + /// This configuration will be ignored when using file-backed memory maps. + /// + /// The default value is `0`. + /// + /// # Example + /// + /// ```rust + /// use orderwal::Options; + /// + /// let options = Options::new().with_capacity(100); + /// assert_eq!(options.capacity(), 100); + /// ``` + #[inline] + pub const fn with_capacity(mut self, cap: u32) -> Self { + self.cap = cap; + self + } + + /// Sets the maximum key length. + /// + /// # Example + /// + /// ```rust + /// use orderwal::Options; + /// + /// let options = Options::new().with_maximum_key_size(1024); + /// assert_eq!(options.maximum_key_size(), 1024); + /// ``` + #[inline] + pub const fn with_maximum_key_size(mut self, size: u32) -> Self { + self.maximum_key_size = size; + self + } + + /// Sets the maximum value length. + /// + /// # Example + /// + /// ```rust + /// use orderwal::Options; + /// + /// let options = Options::new().with_maximum_value_size(1024); + /// assert_eq!(options.maximum_value_size(), 1024); + /// ``` + #[inline] + pub const fn with_maximum_value_size(mut self, size: u32) -> Self { + self.maximum_value_size = size; + self + } + + /// Returns the bits of the page size. + /// + /// Configures the anonymous memory map to be allocated using huge pages. + /// + /// This option corresponds to the `MAP_HUGETLB` flag on Linux. It has no effect on Windows. + /// + /// The size of the requested page can be specified in page bits. + /// If not provided, the system default is requested. + /// The requested length should be a multiple of this, or the mapping will fail. + /// + /// This option has no effect on file-backed memory maps. + /// + /// The default value is `None`. + /// + /// # Example + /// + /// ```rust + /// use orderwal::Options; + /// + /// let options = Options::new().with_huge(64); + /// assert_eq!(options.huge(), Some(64)); + /// ``` + #[inline] + pub const fn with_huge(mut self, page_bits: u8) -> Self { + self.huge = Some(page_bits); + self + } + + /// Sets the WAL to sync on write. + /// + /// The default value is `true`. + /// + /// # Example + /// + /// ```rust + /// use orderwal::Options; + /// + /// let options = Options::new().with_sync_on_write(false); + /// assert_eq!(options.sync_on_write(), false); + /// ``` + #[inline] + pub const fn with_sync_on_write(mut self, sync: bool) -> Self { + self.sync_on_write = sync; + self + } + + /// Sets the magic version. + /// + /// The default value is `0`. + /// + /// # Example + /// + /// ```rust + /// use orderwal::Options; + /// + /// let options = Options::new().with_magic_version(1); + /// assert_eq!(options.magic_version(), 1); + /// ``` + #[inline] + pub const fn with_magic_version(mut self, version: u16) -> Self { + self.magic_version = version; + self + } +} diff --git a/src/swmr.rs b/src/swmr.rs new file mode 100644 index 0000000..50ef2b7 --- /dev/null +++ b/src/swmr.rs @@ -0,0 +1,5 @@ +mod wal; +pub use wal::OrderWal; + +mod generic; +pub use generic::GenericOrderWal; diff --git a/src/swmr/generic.rs b/src/swmr/generic.rs new file mode 100644 index 0000000..60b01d8 --- /dev/null +++ b/src/swmr/generic.rs @@ -0,0 +1,1313 @@ +use core::{cmp, marker::PhantomData, ops::Bound, slice}; +use std::{ + path::{Path, PathBuf}, + sync::Arc, +}; + +use among::Among; +use crossbeam_skiplist::{Comparable, Equivalent, SkipSet}; +use dbutils::{Checksumer, Crc32}; +use rarena_allocator::{ + either::Either, sync::Arena, Allocator, ArenaPosition, Error as ArenaError, Memory, MmapOptions, + OpenOptions, +}; + +use crate::{ + arena_options, check, entry_size, + error::{self, Error}, + split_lengths, Flags, Options, UnsafeCellChecksumer, CHECKSUM_SIZE, HEADER_SIZE, MAGIC_TEXT, + STATUS_SIZE, +}; + +mod entry; +pub use entry::*; + +mod traits; +pub use traits::*; + +mod reader; +pub use reader::*; + +mod iter; +pub use iter::*; + +#[cfg(test)] +mod tests; + +#[doc(hidden)] +pub struct Pointer { + /// The pointer to the start of the entry. + ptr: *const u8, + /// The length of the key. + key_len: usize, + /// The length of the value. + value_len: usize, + _m: PhantomData<(K, V)>, +} + +impl PartialEq for Pointer { + fn eq(&self, other: &Self) -> bool { + self.as_key_slice() == other.as_key_slice() + } +} + +impl Eq for Pointer {} + +impl PartialOrd for Pointer +where + K: Type + Ord, + for<'a> K::Ref<'a>: KeyRef<'a, K>, +{ + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Pointer +where + K: Type + Ord, + for<'a> K::Ref<'a>: KeyRef<'a, K>, +{ + fn cmp(&self, other: &Self) -> cmp::Ordering { + as KeyRef>::compare_binary(self.as_key_slice(), other.as_key_slice()) + } +} + +unsafe impl Send for Pointer {} +unsafe impl Sync for Pointer {} + +impl Pointer { + #[inline] + const fn new(key_len: usize, value_len: usize, ptr: *const u8) -> Self { + Self { + ptr, + key_len, + value_len, + _m: PhantomData, + } + } + + #[inline] + const fn as_key_slice<'a>(&self) -> &'a [u8] { + if self.key_len == 0 { + return &[]; + } + + // SAFETY: `ptr` is a valid pointer to `len` bytes. + unsafe { slice::from_raw_parts(self.ptr, self.key_len) } + } + + #[inline] + const fn as_value_slice<'a, 'b: 'a>(&'a self) -> &'b [u8] { + if self.value_len == 0 { + return &[]; + } + + // SAFETY: `ptr` is a valid pointer to `len` bytes. + unsafe { slice::from_raw_parts(self.ptr.add(self.key_len), self.value_len) } + } +} + +struct PartialPointer { + key_len: usize, + ptr: *const u8, + _k: PhantomData, +} + +impl PartialEq for PartialPointer { + fn eq(&self, other: &Self) -> bool { + self.as_key_slice() == other.as_key_slice() + } +} + +impl Eq for PartialPointer {} + +impl PartialOrd for PartialPointer +where + K: Type + Ord, + for<'a> K::Ref<'a>: KeyRef<'a, K>, +{ + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for PartialPointer +where + K: Type + Ord, + for<'a> K::Ref<'a>: KeyRef<'a, K>, +{ + fn cmp(&self, other: &Self) -> cmp::Ordering { + as KeyRef>::compare_binary(self.as_key_slice(), other.as_key_slice()) + } +} + +impl PartialPointer { + #[inline] + const fn new(key_len: usize, ptr: *const u8) -> Self { + Self { + key_len, + ptr, + _k: PhantomData, + } + } + + #[inline] + fn as_key_slice<'a>(&self) -> &'a [u8] { + if self.key_len == 0 { + return &[]; + } + + // SAFETY: `ptr` is a valid pointer to `len` bytes. + unsafe { slice::from_raw_parts(self.ptr, self.key_len) } + } +} + +impl<'a, K, V> Equivalent> for PartialPointer +where + K: Type + Ord, + K::Ref<'a>: KeyRef<'a, K>, +{ + fn equivalent(&self, key: &Pointer) -> bool { + self.compare(key).is_eq() + } +} + +impl<'a, K, V> Comparable> for PartialPointer +where + K: Type + Ord, + K::Ref<'a>: KeyRef<'a, K>, +{ + fn compare(&self, p: &Pointer) -> cmp::Ordering { + let kr: K::Ref<'_> = TypeRef::from_slice(p.as_key_slice()); + let or: K::Ref<'_> = TypeRef::from_slice(self.as_key_slice()); + KeyRef::compare(&kr, &or) + } +} + +#[derive(PartialEq, Eq, PartialOrd, Ord)] +struct Ref<'a, K, Q: ?Sized> { + key: &'a Q, + _k: PhantomData, +} + +impl<'a, K, Q: ?Sized> Ref<'a, K, Q> { + #[inline] + const fn new(key: &'a Q) -> Self { + Self { + key, + _k: PhantomData, + } + } +} + +impl<'a, K, Q, V> Equivalent> for Ref<'a, K, Q> +where + K: Type + Ord, + K::Ref<'a>: KeyRef<'a, K>, + Q: ?Sized + Ord + Comparable>, +{ + fn equivalent(&self, key: &Pointer) -> bool { + self.compare(key).is_eq() + } +} + +impl<'a, K, Q, V> Comparable> for Ref<'a, K, Q> +where + K: Type + Ord, + K::Ref<'a>: KeyRef<'a, K>, + Q: ?Sized + Ord + Comparable>, +{ + fn compare(&self, p: &Pointer) -> cmp::Ordering { + let kr = TypeRef::from_slice(p.as_key_slice()); + KeyRef::compare(&kr, self.key) + } +} + +#[derive(PartialEq, Eq, PartialOrd, Ord)] +struct Owned<'a, K, Q: ?Sized> { + key: &'a Q, + _k: PhantomData, +} + +impl<'a, K, Q: ?Sized> Owned<'a, K, Q> { + #[inline] + const fn new(key: &'a Q) -> Self { + Self { + key, + _k: PhantomData, + } + } +} + +impl<'a, K, Q, V> Equivalent> for Owned<'a, K, Q> +where + K: Type + Ord, + K::Ref<'a>: KeyRef<'a, K>, + Q: ?Sized + Ord + Comparable + Comparable>, +{ + fn equivalent(&self, key: &Pointer) -> bool { + self.compare(key).is_eq() + } +} + +impl<'a, K, Q, V> Comparable> for Owned<'a, K, Q> +where + K: Type + Ord, + K::Ref<'a>: KeyRef<'a, K>, + Q: ?Sized + Ord + Comparable + Comparable>, +{ + fn compare(&self, p: &Pointer) -> cmp::Ordering { + let kr = as TypeRef<'_>>::from_slice(p.as_key_slice()); + KeyRef::compare(&kr, self.key).reverse() + } +} + +struct GenericOrderWalCore { + arena: Arena, + map: SkipSet>, + reserved: u32, +} + +impl GenericOrderWalCore { + #[inline] + fn len(&self) -> usize { + self.map.len() + } + + #[inline] + fn is_empty(&self) -> bool { + self.map.is_empty() + } + + #[inline] + fn new(arena: Arena, magic_version: u16, flush: bool, reserved: u32) -> Result { + unsafe { + let slice = arena.reserved_slice_mut(); + slice[0..6].copy_from_slice(&MAGIC_TEXT); + slice[6..8].copy_from_slice(&magic_version.to_le_bytes()); + } + + if !flush { + return Ok(Self::construct(arena, SkipSet::new(), reserved)); + } + + arena + .flush_range(0, HEADER_SIZE) + .map(|_| Self::construct(arena, SkipSet::new(), reserved)) + .map_err(Into::into) + } + + #[inline] + fn first(&self) -> Option> + where + K: Type + Ord, + for<'b> K::Ref<'b>: KeyRef<'b, K>, + { + self.map.front().map(EntryRef::new) + } + + #[inline] + fn last(&self) -> Option> + where + K: Type + Ord, + for<'b> K::Ref<'b>: KeyRef<'b, K>, + { + self.map.back().map(EntryRef::new) + } + + #[inline] + fn iter(&self) -> Iter + where + K: Type + Ord, + for<'b> K::Ref<'b>: KeyRef<'b, K>, + { + Iter::new(self.map.iter()) + } + + #[inline] + fn range_by_ref<'a, Q>( + &'a self, + start_bound: Bound<&'a Q>, + end_bound: Bound<&'a Q>, + ) -> RefRange<'a, Q, K, V> + where + K: Type + Ord, + for<'b> K::Ref<'b>: KeyRef<'b, K>, + Q: Ord + ?Sized + Comparable>, + { + RefRange::new( + self + .map + .range((start_bound.map(Ref::new), end_bound.map(Ref::new))), + ) + } + + #[inline] + fn range<'a, Q>( + &'a self, + start_bound: Bound<&'a Q>, + end_bound: Bound<&'a Q>, + ) -> Range<'a, Q, K, V> + where + K: Type + Ord, + for<'b> K::Ref<'b>: KeyRef<'b, K>, + Q: Ord + ?Sized + Comparable + Comparable>, + { + Range::new( + self + .map + .range((start_bound.map(Owned::new), end_bound.map(Owned::new))), + ) + } + + #[inline] + fn construct(arena: Arena, set: SkipSet>, reserved: u32) -> Self { + Self { + arena, + map: set, + reserved, + } + } +} + +impl GenericOrderWalCore +where + K: Type + Ord + 'static, + for<'a> ::Ref<'a>: KeyRef<'a, K>, + V: Type + 'static, +{ + fn replay( + arena: Arena, + opts: &Options, + ro: bool, + checksumer: &mut S, + ) -> Result { + let slice = arena.reserved_slice(); + let magic_text = &slice[0..6]; + let magic_version = u16::from_le_bytes(slice[6..8].try_into().unwrap()); + + if magic_text != MAGIC_TEXT { + return Err(Error::magic_text_mismatch()); + } + + if magic_version != opts.magic_version() { + return Err(Error::magic_version_mismatch()); + } + + let map = SkipSet::new(); + + let mut cursor = arena.data_offset(); + let allocated = arena.allocated(); + + loop { + unsafe { + // we reached the end of the arena, if we have any remaining, then if means two possibilities: + // 1. the remaining is a partial entry, but it does not be persisted to the disk, so following the write-ahead log principle, we should discard it. + // 2. our file may be corrupted, so we discard the remaining. + if cursor + STATUS_SIZE > allocated { + if !ro && cursor < allocated { + arena.rewind(ArenaPosition::Start(cursor as u32)); + arena.flush()?; + } + + break; + } + + let header = arena.get_u8(cursor).unwrap(); + let flag = Flags::from_bits_unchecked(header); + + let (kvsize, encoded_len) = arena.get_u64_varint(cursor + STATUS_SIZE).map_err(|_e| { + #[cfg(feature = "tracing")] + tracing::error!(err=%_e); + + Error::corrupted() + })?; + let (key_len, value_len) = split_lengths(encoded_len); + let key_len = key_len as usize; + let value_len = value_len as usize; + + // Same as above, if we reached the end of the arena, we should discard the remaining. + let cks_offset = STATUS_SIZE + kvsize + key_len + value_len; + if cks_offset + CHECKSUM_SIZE > allocated { + if !ro { + arena.rewind(ArenaPosition::Start(cursor as u32)); + arena.flush()?; + } + + break; + } + + let cks = arena.get_u64_le(cursor + cks_offset).unwrap(); + + if cks != checksumer.checksum(arena.get_bytes(cursor, cks_offset)) { + return Err(Error::corrupted()); + } + + // If the entry is not committed, we should not rewind + if !flag.contains(Flags::COMMITTED) { + if !ro { + arena.rewind(ArenaPosition::Start(cursor as u32)); + arena.flush()?; + } + + break; + } + + map.insert(Pointer::new( + key_len, + value_len, + arena.get_pointer(cursor + STATUS_SIZE + kvsize), + )); + cursor += cks_offset + CHECKSUM_SIZE; + } + } + + Ok(Self::construct(arena, map, opts.reserved())) + } +} + +impl GenericOrderWalCore +where + K: Type + Ord, + for<'a> ::Ref<'a>: KeyRef<'a, K>, + V: Type, +{ + #[inline] + fn contains_key<'a, 'b: 'a, Q>(&'a self, key: &'b Q) -> bool + where + Q: ?Sized + Ord + Comparable> + Comparable, + { + self.map.get::>(&Owned::new(key)).is_some() + } + + #[inline] + fn contains_key_by_ref<'a, 'b: 'a, Q>(&'a self, key: &'b Q) -> bool + where + Q: ?Sized + Ord + Comparable>, + { + self.map.get::>(&Ref::new(key)).is_some() + } + + #[inline] + fn get<'a, 'b: 'a, Q>(&'a self, key: &'b Q) -> Option> + where + Q: ?Sized + Ord + Comparable> + Comparable, + { + self + .map + .get::>(&Owned::new(key)) + .map(EntryRef::new) + } + + #[inline] + fn get_by_ref<'a, 'b: 'a, Q>(&'a self, key: &'b Q) -> Option> + where + Q: ?Sized + Ord + Comparable>, + { + self.map.get::>(&Ref::new(key)).map(EntryRef::new) + } +} + +/// Generic ordered write-ahead log implementation, which supports structured keys and values. +/// +/// Only the first instance of the WAL can write to the log, while the rest can only read from the log. +pub struct GenericOrderWal { + core: Arc>, + opts: Options, + cks: UnsafeCellChecksumer, + ro: bool, +} + +impl GenericOrderWal { + /// Creates a new in-memory write-ahead log backed by an aligned vec with the given capacity and options. + /// + /// # Example + /// + /// ```rust + /// use orderwal::{swmr::GenericOrderWal, Options}; + /// + /// let wal = GenericOrderWal::::new(Options::new().with_capacity(1024)).unwrap(); + /// ``` + #[inline] + pub fn new(opts: Options) -> Result { + Self::with_checksumer(opts, Crc32::default()) + } + + /// Creates a new in-memory write-ahead log backed by an anonymous memory map with the given options. + /// + /// # Example + /// + /// ```rust + /// use orderwal::{swmr::GenericOrderWal, Options}; + /// + /// let wal = GenericOrderWal::::map_anon(Options::new().with_capacity(1024)).unwrap(); + /// ``` + #[inline] + pub fn map_anon(opts: Options) -> Result { + Self::map_anon_with_checksumer(opts, Crc32::default()) + } +} + +impl GenericOrderWal +where + K: Type + Ord + 'static, + for<'a> ::Ref<'a>: KeyRef<'a, K>, +{ + /// Returns the first key-value pair in the map. The key in this pair is the minimum key in the wal. + #[inline] + pub fn first(&self) -> Option> { + self.core.first() + } + + /// Returns the last key-value pair in the map. The key in this pair is the maximum key in the wal. + #[inline] + pub fn last(&self) -> Option> { + self.core.last() + } + + /// Returns an iterator over the entries in the WAL. + #[inline] + pub fn iter(&self) -> Iter { + self.core.iter() + } + + /// Returns an iterator over a subset of the entries in the WAL. + #[inline] + pub fn range_by_ref<'a, Q>( + &'a self, + start_bound: Bound<&'a Q>, + end_bound: Bound<&'a Q>, + ) -> RefRange<'a, Q, K, V> + where + Q: Ord + ?Sized + Comparable>, + { + self.core.range_by_ref(start_bound, end_bound) + } + + /// Returns an iterator over a subset of the entries in the WAL. + #[inline] + pub fn range<'a, Q>( + &'a self, + start_bound: Bound<&'a Q>, + end_bound: Bound<&'a Q>, + ) -> Range<'a, Q, K, V> + where + Q: Ord + ?Sized + Comparable + Comparable>, + { + self.core.range(start_bound, end_bound) + } +} + +impl GenericOrderWal +where + K: Type + Ord + 'static, + for<'a> ::Ref<'a>: KeyRef<'a, K>, + V: Type + 'static, +{ + /// Creates a new write-ahead log backed by a file backed memory map with the given options. + /// + /// # Example + /// + /// ```rust + /// use orderwal::{swmr::GenericOrderWal, Options}; + /// + /// ``` + #[inline] + pub fn map_mut>( + path: P, + opts: Options, + open_options: OpenOptions, + ) -> Result { + Self::map_mut_with_path_builder::<_, ()>(|| Ok(path.as_ref().to_path_buf()), opts, open_options) + .map_err(|e| e.unwrap_right()) + } + + /// Creates a new write-ahead log backed by a file backed memory map with the given options. + #[inline] + pub fn map_mut_with_path_builder( + pb: PB, + opts: Options, + open_options: OpenOptions, + ) -> Result> + where + PB: FnOnce() -> Result, + { + Self::map_mut_with_path_builder_and_checksumer(pb, opts, open_options, Crc32::default()) + } + + /// Open a write-ahead log backed by a file backed memory map in read only mode. + #[inline] + pub fn map>(path: P, opts: Options) -> Result { + Self::map_with_path_builder::<_, ()>(|| Ok(path.as_ref().to_path_buf()), opts) + .map_err(|e| e.unwrap_right()) + } + + /// Open a write-ahead log backed by a file backed memory map in read only mode. + #[inline] + pub fn map_with_path_builder(pb: PB, opts: Options) -> Result> + where + PB: FnOnce() -> Result, + { + Self::map_with_path_builder_and_checksumer(pb, opts, Crc32::default()) + } +} + +impl GenericOrderWal { + /// Returns a read-only WAL instance. + #[inline] + pub fn reader(&self) -> GenericWalReader { + GenericWalReader::new(self.core.clone()) + } + + /// Returns the path of the WAL if it is backed by a file. + #[inline] + pub fn path(&self) -> Option<&std::sync::Arc> { + self.core.arena.path() + } + + /// Returns the reserved space in the WAL. + /// + /// # Safety + /// - The writer must ensure that the returned slice is not modified. + /// - This method is not thread-safe, so be careful when using it. + #[inline] + pub unsafe fn reserved_slice(&self) -> &[u8] { + if self.opts.reserved() == 0 { + return &[]; + } + + &self.core.arena.reserved_slice()[HEADER_SIZE..] + } + + /// Returns the mutable reference to the reserved slice. + /// + /// # Safety + /// - The caller must ensure that the there is no others accessing reserved slice for either read or write. + /// - This method is not thread-safe, so be careful when using it. + #[inline] + pub unsafe fn reserved_slice_mut(&mut self) -> &mut [u8] { + if self.opts.reserved() == 0 { + return &mut []; + } + + &mut self.core.arena.reserved_slice_mut()[HEADER_SIZE..] + } + + /// Returns number of entries in the WAL. + #[inline] + pub fn len(&self) -> usize { + self.core.len() + } + + /// Returns `true` if the WAL is empty. + #[inline] + pub fn is_empty(&self) -> bool { + self.core.is_empty() + } + + /// Creates a new in-memory write-ahead log backed by an aligned vec with the given options and [`Checksumer`]. + /// + /// # Example + /// + /// ```rust + /// use orderwal::{swmr::GenericOrderWal, Options, Crc32}; + /// + /// let wal = GenericOrderWal::::with_checksumer(Options::new().with_capacity(1024), Crc32::default()); + /// ``` + pub fn with_checksumer(opts: Options, cks: S) -> Result { + let arena = Arena::new(arena_options(opts.reserved()).with_capacity(opts.capacity())).map_err( + |e| match e { + ArenaError::InsufficientSpace { + requested, + available, + } => Error::insufficient_space(requested, available), + _ => unreachable!(), + }, + )?; + + GenericOrderWalCore::new(arena, opts.magic_version(), false, opts.reserved()) + .map(|core| Self::from_core(core, opts, cks, false)) + } + + /// Creates a new in-memory write-ahead log backed by an anonymous memory map with the given options and [`Checksumer`]. + /// + /// # Example + /// + /// ```rust + /// use orderwal::{swmr::GenericOrderWal, Options, Crc32}; + /// + /// let wal = GenericOrderWal::::map_anon_with_checksumer(Options::new().with_capacity(1024), Crc32::default()).unwrap(); + /// ``` + pub fn map_anon_with_checksumer(opts: Options, cks: S) -> Result { + let arena = Arena::map_anon( + arena_options(opts.reserved()), + MmapOptions::new().len(opts.capacity()), + )?; + + GenericOrderWalCore::new(arena, opts.magic_version(), true, opts.reserved()) + .map(|core| Self::from_core(core, opts, cks, false)) + } + + #[inline] + fn from_core(core: GenericOrderWalCore, opts: Options, cks: S, ro: bool) -> Self { + Self { + core: Arc::new(core), + ro, + opts, + cks: UnsafeCellChecksumer::new(cks), + } + } +} + +impl GenericOrderWal +where + K: Type + Ord + 'static, + for<'a> ::Ref<'a>: KeyRef<'a, K>, + V: Type + 'static, + S: Checksumer, +{ + /// Returns a write-ahead log backed by a file backed memory map with the given options and [`Checksumer`]. + /// + /// # Example + /// + /// ```rust + /// use orderwal::{swmr::GenericOrderWal, Options, Crc32}; + /// + /// + /// ``` + #[inline] + pub fn map_mut_with_checksumer>( + path: P, + opts: Options, + open_options: OpenOptions, + cks: S, + ) -> Result { + Self::map_mut_with_path_builder_and_checksumer::<_, ()>( + || Ok(path.as_ref().to_path_buf()), + opts, + open_options, + cks, + ) + .map_err(|e| e.unwrap_right()) + } + + /// Returns a write-ahead log backed by a file backed memory map with the given options and [`Checksumer`]. + /// + /// # Example + /// + /// ```rust + /// use orderwal::{swmr::GenericOrderWal, Options, Crc32}; + /// + /// ``` + pub fn map_mut_with_path_builder_and_checksumer( + path_builder: PB, + opts: Options, + open_options: OpenOptions, + mut cks: S, + ) -> Result> + where + PB: FnOnce() -> Result, + { + let path = path_builder().map_err(Either::Left)?; + let exist = path.exists(); + let arena = Arena::map_mut_with_path_builder( + || Ok(path), + arena_options(opts.reserved()), + open_options, + MmapOptions::new(), + ) + .map_err(|e| e.map_right(Into::into))?; + + if !exist { + return GenericOrderWalCore::new(arena, opts.magic_version(), true, opts.reserved()) + .map(|core| Self::from_core(core, opts, cks, false)) + .map_err(Either::Right); + } + + GenericOrderWalCore::replay(arena, &opts, false, &mut cks) + .map(|core| Self::from_core(core, opts, cks, false)) + .map_err(Either::Right) + } + + /// Open a write-ahead log backed by a file backed memory map in read only mode with the given [`Checksumer`]. + #[inline] + pub fn map_with_checksumer>( + path: P, + opts: Options, + cks: S, + ) -> Result { + Self::map_with_path_builder_and_checksumer::<_, ()>( + || Ok(path.as_ref().to_path_buf()), + opts, + cks, + ) + .map_err(|e| e.unwrap_right()) + } + + /// Open a write-ahead log backed by a file backed memory map in read only mode with the given [`Checksumer`]. + #[inline] + pub fn map_with_path_builder_and_checksumer( + path_builder: PB, + opts: Options, + mut cks: S, + ) -> Result> + where + PB: FnOnce() -> Result, + { + let open_options = OpenOptions::default().read(true); + let arena = Arena::map_with_path_builder( + path_builder, + arena_options(opts.reserved()), + open_options, + MmapOptions::new(), + ) + .map_err(|e| e.map_right(Into::into))?; + + GenericOrderWalCore::replay(arena, &opts, true, &mut cks) + .map(|core| Self::from_core(core, opts, cks, true)) + .map_err(Either::Right) + } +} + +impl GenericOrderWal +where + K: Type + Ord, + for<'a> K::Ref<'a>: KeyRef<'a, K>, + V: Type, +{ + /// Returns `true` if the key exists in the WAL. + #[inline] + pub fn contains_key<'a, 'b: 'a, Q>(&'a self, key: &'b Q) -> bool + where + Q: ?Sized + Ord + Comparable> + Comparable, + { + self.core.contains_key(key) + } + + /// Returns `true` if the key exists in the WAL. + #[inline] + pub fn contains_key_by_ref<'a, 'b: 'a, Q>(&'a self, key: &'b Q) -> bool + where + Q: ?Sized + Ord + Comparable>, + { + self.core.contains_key_by_ref(key) + } + + /// Gets the value associated with the key. + #[inline] + pub fn get<'a, 'b: 'a, Q>(&'a self, key: &'b Q) -> Option> + where + Q: ?Sized + Ord + Comparable> + Comparable, + { + self.core.get(key) + } + + /// Gets the value associated with the key. + #[inline] + pub fn get_by_ref<'a, 'b: 'a, Q>(&'a self, key: &'b Q) -> Option> + where + Q: ?Sized + Ord + Comparable>, + { + self.core.get_by_ref(key) + } +} + +impl GenericOrderWal +where + K: Type + Ord + for<'a> Comparable> + 'static, + for<'a> K::Ref<'a>: KeyRef<'a, K>, + V: Type + 'static, + S: Checksumer, +{ + /// Gets or insert the key value pair. + #[inline] + pub fn get_or_insert( + &mut self, + key: &K, + value: &V, + ) -> Either, Result<(), Among>> { + let ent = self + .core + .map + .get(&Owned::new(key)) + .map(|e| Either::Left(EntryRef::new(e))); + + match ent { + Some(e) => e, + None => { + let p = self.insert_in(Among::Middle(key), Among::Middle(value)); + Either::Right(p) + } + } + } + + /// Gets or insert the key value pair. + #[inline] + pub fn get_or_insert_with( + &mut self, + key: &K, + value: impl FnOnce() -> V, + ) -> Either, Result<(), Among>> { + let ent = self + .core + .map + .get(&Ref::new(key)) + .map(|e| Either::Left(EntryRef::new(e))); + + match ent { + Some(e) => e, + None => { + let p = self.insert_in(Among::Middle(key), Among::Left(value())); + Either::Right(p) + } + } + } + + /// Gets or insert the key value pair. + /// + /// # Safety + /// - The given `key` and `value` must be valid to construct to `K::Ref` and `V::Ref` without remaining. + #[inline] + pub unsafe fn get_by_bytes_or_insert_value_bytes( + &mut self, + key: &[u8], + value: &[u8], + ) -> Either, Result<(), Error>> { + let ent = self + .core + .map + .get(&PartialPointer::new(key.len(), key.as_ptr())) + .map(|e| Either::Left(EntryRef::new(e))); + + match ent { + Some(e) => e, + None => match self.insert_in(Among::Right(key), Among::Right(value)) { + Ok(_) => Either::Right(Ok(())), + Err(Among::Right(e)) => Either::Right(Err(e)), + _ => unreachable!(), + }, + } + } + + /// Gets or insert the key value pair. + /// + /// # Safety + /// - The given `value` must be valid to construct to `V::Ref` without remaining. + #[inline] + pub unsafe fn get_or_insert_value_bytes( + &mut self, + key: &K, + value: &[u8], + ) -> Either, Result<(), Error>> { + let ent = self + .core + .map + .get(&Owned::new(key)) + .map(|e| Either::Left(EntryRef::new(e))); + + match ent { + Some(e) => e, + None => match self.insert_in(Among::Middle(key), Among::Right(value)) { + Ok(_) => Either::Right(Ok(())), + Err(Among::Right(e)) => Either::Right(Err(e)), + _ => unreachable!(), + }, + } + } + + /// Gets or insert the key value pair. + /// + /// # Safety + /// - The given `key` must be valid to construct to `K::Ref` without remaining. + #[inline] + pub unsafe fn get_by_key_bytes_or_insert( + &mut self, + key: &[u8], + value: &V, + ) -> Either, Result<(), Error>> { + let ent = self + .core + .map + .get(&PartialPointer::new(key.len(), key.as_ptr())) + .map(|e| Either::Left(EntryRef::new(e))); + + match ent { + Some(e) => e, + None => match self.insert_in(Among::Right(key), Among::Middle(value)) { + Ok(_) => Either::Right(Ok(())), + Err(Among::Right(e)) => Either::Right(Err(e)), + _ => unreachable!(), + }, + } + } + + /// Gets or insert the key value pair. + /// + /// # Safety + /// - The given `key` must be valid to construct to `K::Ref` without remaining. + #[inline] + pub unsafe fn get_by_key_bytes_or_insert_with( + &mut self, + key: &[u8], + value: impl FnOnce() -> V, + ) -> Either, Result<(), Error>> { + let ent = self + .core + .map + .get(&PartialPointer::new(key.len(), key.as_ptr())) + .map(|e| Either::Left(EntryRef::new(e))); + + match ent { + Some(e) => e, + None => match self.insert_in(Among::Right(key), Among::Left(value())) { + Ok(_) => Either::Right(Ok(())), + Err(Among::Right(e)) => Either::Right(Err(e)), + _ => unreachable!(), + }, + } + } +} + +impl GenericOrderWal +where + K: Type + Ord + 'static, + for<'a> K::Ref<'a>: KeyRef<'a, K>, + V: Type + 'static, + S: Checksumer, +{ + /// Inserts a key-value pair into the write-ahead log. If `cache_key` or `cache_value` is enabled, the key or value will be cached + /// in memory for faster access. + /// + /// For `cache_key` or `cache_value`, see [`Options::with_cache_key`](Options::with_cache_key) and [`Options::with_cache_value`](Options::with_cache_value). + #[inline] + pub fn insert(&mut self, key: &K, val: &V) -> Result<(), Among> { + self.insert_in(Among::Middle(key), Among::Middle(val)) + } + + /// Inserts a bytes format key-value pair into the write-ahead log directly. + /// + /// This method is useful when you have `K::Ref` and `V::Ref` and they can be easily converted to bytes format. + /// + /// # Safety + /// - The given key and value must be valid to construct to `K::Ref` and `V::Ref` without remaining. + /// + /// # Example + /// + /// TODO: ignore for now + /// ```no_compile + /// use orderwal::{swmr::{GenericOrderWal, Comparable}, Options, Crc32}; + /// + /// #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] + /// struct MyKey { + /// id: u32, + /// data: Vec, + /// } + /// + /// impl Type for MyKey { + /// type Ref<'a> = MyKeyRef<'a>; + /// type Error = (); + /// + /// fn encoded_len(&self) -> usize { + /// 4 + self.data.len() + /// } + /// + /// fn encode(&self, buf: &mut [u8]) -> Result<(), Self::Error> { + /// buf[..4].copy_from_slice(&self.id.to_le_bytes()); + /// buf[4..].copy_from_slice(&self.data); + /// } + /// } + /// + /// #[derive(Debug, Clone, Copy, PartialEq, Eq)] + /// struct MyKeyRef<'a> { + /// buf: &'a [u8], + /// } + /// + /// impl<'a> PartialOrd for MyKeyRef<'a> { + /// fn partial_cmp(&self, other: &Self) -> Option { + /// Some(self.cmp(other)) + /// } + /// } + /// + /// impl<'a> Ord for MyKeyRef<'a> { + /// fn cmp(&self, other: &Self) -> std::cmp::Ordering { + /// let sid = u32::from_le_bytes(self.buf[..4].try_into().unwrap()); + /// let oid = u32::from_le_bytes(other.buf[..4].try_into().unwrap()); + /// + /// sid.cmp(&oid).then_with(|| self.buf[4..].cmp(&other.buf[4..])) + /// } + /// } + /// + /// impl<'a> TypeRef<'a> for MyKeyRef<'a> { + /// fn from_slice(src: &'a [u8]) -> Self { + /// Self { buf: src } + /// } + /// } + /// + /// impl<'a> KeyRef<'a, MyKey> for MyKeyRef<'a> { + /// fn compare_binary(a: &[u8], b: &[u8]) -> std::cmp::Ordering { + /// let aid = u32::from_le_bytes(a[..4].try_into().unwrap()); + /// let bid = u32::from_le_bytes(b[..4].try_into().unwrap()); + /// + /// aid.cmp(&bid).then_with(|| a[4..].cmp(&b[4..])) + /// } + /// + /// fn compare(&self, a: &Q) -> std::cmp::Ordering + /// where + /// Q: ?Sized + Ord + Comparable, + /// { + /// Comparable::compare(a, self) + /// } + /// } + /// + /// let wal = GenericOrderWal::new(Options::new().with_capacity(1024)); + /// + /// let key = MyKey { id: 1, data: vec![1, 2, 3, 4] }; + /// let value = b"Hello, world!".to_vec(); + /// + /// wal.insert(key, value).unwrap(); + /// + /// let ent = wal.get(&key).unwrap(); + /// + /// let wal2 = GenericOrderWal::new(Options::new().with_capacity(1024)); + /// + /// // Insert the key-value pair in bytes format directly. + /// unsafe { wal2.insert_value_bytes(ent.key(), ent.value().as_ref()).unwrap(); } + /// ``` + #[inline] + pub unsafe fn insert_value_bytes(&mut self, key: &[u8], val: &[u8]) -> Result<(), Error> { + self + .insert_in(Among::Right(key), Among::Right(val)) + .map_err(|e| match e { + Among::Right(e) => e, + _ => unreachable!(), + }) + } + + /// Inserts a key in structured format and value in bytes format into the write-ahead log directly. + /// + /// # Safety + /// - The given `value` must be valid to construct to `V::Ref` without remaining. + /// + /// # Example + /// + /// See [`insert_value_bytes`](GenericOrderWal::insert_value_bytes) for more details. + #[inline] + pub unsafe fn insert_key_with_value_bytes(&mut self, key: &K, value: &[u8]) -> Result<(), Error> { + self + .insert_in(Among::Middle(key), Among::Right(value)) + .map_err(|e| match e { + Among::Right(e) => e, + _ => unreachable!(), + }) + } + + /// Inserts a key in bytes format and value in structured format into the write-ahead log directly. + /// + /// # Safety + /// - The given `key` must be valid to construct to `K::Ref` without remaining. + /// + /// # Example + /// + /// See [`insert_value_bytes`](GenericOrderWal::insert_value_bytes) for more details. + #[inline] + pub unsafe fn insert_key_bytes_with_value(&mut self, key: &[u8], value: &V) -> Result<(), Error> { + self + .insert_in(Among::Right(key), Among::Middle(value)) + .map_err(|e| match e { + Among::Right(e) => e, + _ => unreachable!(), + }) + } + + fn insert_in( + &self, + key: Among, + val: Among, + ) -> Result<(), Among> { + if self.ro { + return Err(Among::Right(Error::read_only())); + } + + let klen = key.encoded_len(); + let vlen = val.encoded_len(); + + self.check(klen, vlen).map_err(Among::Right)?; + + let (len_size, kvlen, elen) = entry_size(klen as u32, vlen as u32); + + let buf = self.core.arena.alloc_bytes(elen); + + match buf { + Err(e) => { + let e = match e { + ArenaError::InsufficientSpace { + requested, + available, + } => error::Error::insufficient_space(requested, available), + ArenaError::ReadOnly => error::Error::read_only(), + _ => unreachable!(), + }; + Err(Among::Right(e)) + } + Ok(mut buf) => { + unsafe { + // We allocate the buffer with the exact size, so it's safe to write to the buffer. + let flag = Flags::COMMITTED.bits(); + + self.cks.reset(); + self.cks.update(&[flag]); + + buf.put_u8_unchecked(Flags::empty().bits()); + let written = buf.put_u64_varint_unchecked(kvlen); + debug_assert_eq!( + written, len_size, + "the precalculated size should be equal to the written size" + ); + + let ko = STATUS_SIZE + written; + buf.set_len(ko + klen + vlen); + + let key_buf = slice::from_raw_parts_mut(buf.as_mut_ptr().add(ko), klen); + key.encode(key_buf).map_err(Among::Left)?; + + let vo = STATUS_SIZE + written + klen; + let value_buf = slice::from_raw_parts_mut(buf.as_mut_ptr().add(vo), vlen); + val.encode(value_buf).map_err(Among::Middle)?; + + let cks = { + self.cks.update(&buf[1..]); + self.cks.digest() + }; + buf.put_u64_le_unchecked(cks); + + // commit the entry + buf[0] |= Flags::COMMITTED.bits(); + + if self.opts.sync_on_write() && self.core.arena.is_ondisk() { + self + .core + .arena + .flush_range(buf.offset(), elen as usize) + .map_err(|e| Among::Right(e.into()))?; + } + buf.detach(); + + let p = Pointer::new(klen, vlen, buf.as_ptr().add(ko)); + self.core.map.insert(p); + Ok(()) + } + } + } + } + + #[inline] + fn check(&self, klen: usize, vlen: usize) -> Result<(), error::Error> { + check( + klen, + vlen, + self.opts.maximum_key_size(), + self.opts.maximum_value_size(), + ) + } +} diff --git a/src/swmr/generic/entry.rs b/src/swmr/generic/entry.rs new file mode 100644 index 0000000..2cd774d --- /dev/null +++ b/src/swmr/generic/entry.rs @@ -0,0 +1,44 @@ +use crossbeam_skiplist::set::Entry; + +use super::{Pointer, Type, TypeRef}; + +/// The reference to an entry in the [`GenericOrderWal`](super::GenericOrderWal). +pub struct EntryRef<'a, K, V> { + ent: Entry<'a, Pointer>, +} + +impl<'a, K, V> Clone for EntryRef<'a, K, V> { + #[inline] + fn clone(&self) -> Self { + Self { + ent: self.ent.clone(), + } + } +} + +impl<'a, K, V> EntryRef<'a, K, V> { + #[inline] + pub(super) fn new(ent: Entry<'a, Pointer>) -> Self { + Self { ent } + } +} + +impl<'a, K, V> EntryRef<'a, K, V> +where + K: Type, + V: Type, +{ + /// Returns the key of the entry. + #[inline] + pub fn key(&self) -> K::Ref<'a> { + let p = self.ent.value(); + TypeRef::from_slice(p.as_key_slice()) + } + + /// Returns the value of the entry. + #[inline] + pub fn value(&self) -> V::Ref<'a> { + let p = self.ent.value(); + TypeRef::from_slice(p.as_value_slice()) + } +} diff --git a/src/swmr/generic/iter.rs b/src/swmr/generic/iter.rs new file mode 100644 index 0000000..f0f80d7 --- /dev/null +++ b/src/swmr/generic/iter.rs @@ -0,0 +1,155 @@ +use core::ops::Bound; + +use crossbeam_skiplist::Comparable; + +use super::{EntryRef, KeyRef, Owned, Pointer, Ref, Type}; + +type SetRefRange<'a, Q, K, V> = crossbeam_skiplist::set::Range< + 'a, + Ref<'a, K, Q>, + (Bound>, Bound>), + Pointer, +>; +type SetRange<'a, Q, K, V> = crossbeam_skiplist::set::Range< + 'a, + Owned<'a, K, Q>, + (Bound>, Bound>), + Pointer, +>; + +/// An iterator over the entries in the WAL. +pub struct Iter<'a, K, V> { + iter: crossbeam_skiplist::set::Iter<'a, Pointer>, +} + +impl<'a, K, V> Iter<'a, K, V> { + #[inline] + pub(super) fn new(iter: crossbeam_skiplist::set::Iter<'a, Pointer>) -> Self { + Self { iter } + } +} + +impl<'a, K, V> Iterator for Iter<'a, K, V> +where + K: Type + Ord, + for<'b> K::Ref<'b>: KeyRef<'b, K>, +{ + type Item = EntryRef<'a, K, V>; + + #[inline] + fn next(&mut self) -> Option { + self.iter.next().map(|ptr| EntryRef::new(ptr)) + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.iter.size_hint() + } +} + +impl<'a, K, V> DoubleEndedIterator for Iter<'a, K, V> +where + K: Type + Ord, + for<'b> K::Ref<'b>: KeyRef<'b, K>, +{ + #[inline] + fn next_back(&mut self) -> Option { + self.iter.next_back().map(|ptr| EntryRef::new(ptr)) + } +} + +/// An iterator over a subset of the entries in the WAL. +pub struct RefRange<'a, Q, K, V> +where + K: Type + Ord, + for<'b> K::Ref<'b>: KeyRef<'b, K>, + Q: Ord + ?Sized + Comparable>, +{ + iter: SetRefRange<'a, Q, K, V>, +} + +impl<'a, Q, K, V> RefRange<'a, Q, K, V> +where + K: Type + Ord, + for<'b> K::Ref<'b>: KeyRef<'b, K>, + Q: Ord + ?Sized + Comparable>, +{ + #[inline] + pub(super) fn new(iter: SetRefRange<'a, Q, K, V>) -> Self { + Self { iter } + } +} + +impl<'a, Q, K, V> Iterator for RefRange<'a, Q, K, V> +where + K: Type + Ord, + for<'b> K::Ref<'b>: KeyRef<'b, K>, + Q: Ord + ?Sized + Comparable>, +{ + type Item = EntryRef<'a, K, V>; + + #[inline] + fn next(&mut self) -> Option { + self.iter.next().map(|ptr| EntryRef::new(ptr)) + } +} + +impl<'a, Q, K, V> DoubleEndedIterator for RefRange<'a, Q, K, V> +where + K: Type + Ord, + for<'b> K::Ref<'b>: KeyRef<'b, K>, + Q: Ord + ?Sized + Comparable>, +{ + #[inline] + fn next_back(&mut self) -> Option { + self.iter.next_back().map(|ptr| EntryRef::new(ptr)) + } +} + +/// An iterator over a subset of the entries in the WAL. +pub struct Range<'a, Q, K, V> +where + K: Type + Ord, + for<'b> K::Ref<'b>: KeyRef<'b, K>, + Q: Ord + ?Sized + Comparable> + Comparable, +{ + iter: SetRange<'a, Q, K, V>, +} + +impl<'a, Q, K, V> Range<'a, Q, K, V> +where + K: Type + Ord, + for<'b> K::Ref<'b>: KeyRef<'b, K>, + Q: Ord + ?Sized + Comparable> + Comparable, +{ + #[inline] + pub(super) fn new(iter: SetRange<'a, Q, K, V>) -> Self { + Self { iter } + } +} + +impl<'a, Q, K, V> Iterator for Range<'a, Q, K, V> +where + K: Type + Ord, + for<'b> K::Ref<'b>: KeyRef<'b, K>, + Q: Ord + ?Sized + Comparable> + Comparable, +{ + type Item = EntryRef<'a, K, V>; + + #[inline] + fn next(&mut self) -> Option { + self.iter.next().map(|ptr| EntryRef::new(ptr)) + } +} + +impl<'a, Q, K, V> DoubleEndedIterator for Range<'a, Q, K, V> +where + K: Type + Ord, + for<'b> K::Ref<'b>: KeyRef<'b, K>, + Q: Ord + ?Sized + Comparable> + Comparable, +{ + #[inline] + fn next_back(&mut self) -> Option { + self.iter.next_back().map(|ptr| EntryRef::new(ptr)) + } +} diff --git a/src/swmr/generic/reader.rs b/src/swmr/generic/reader.rs new file mode 100644 index 0000000..5ddb446 --- /dev/null +++ b/src/swmr/generic/reader.rs @@ -0,0 +1,141 @@ +use super::*; + +/// A read-only view of a generic single-writer, multi-reader WAL. +pub struct GenericWalReader(Arc>); + +impl Clone for GenericWalReader { + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} + +impl GenericWalReader { + pub(super) fn new(wal: Arc>) -> Self { + Self(wal) + } + + /// Returns the path of the WAL if it is backed by a file. + #[inline] + pub fn path(&self) -> Option<&std::sync::Arc> { + self.0.arena.path() + } + + /// Returns the reserved space in the WAL. + /// + /// # Safety + /// - The writer must ensure that the returned slice is not modified. + /// - This method is not thread-safe, so be careful when using it. + #[inline] + pub unsafe fn reserved_slice(&self) -> &[u8] { + if self.0.reserved == 0 { + return &[]; + } + + &self.0.arena.reserved_slice()[HEADER_SIZE..] + } + + /// Returns number of entries in the WAL. + #[inline] + pub fn len(&self) -> usize { + self.0.len() + } + + /// Returns `true` if the WAL is empty. + #[inline] + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } +} + +impl GenericWalReader +where + K: Type + Ord, + for<'a> K::Ref<'a>: KeyRef<'a, K>, +{ + /// Returns the first key-value pair in the map. The key in this pair is the minimum key in the wal. + #[inline] + pub fn first(&self) -> Option> { + self.0.first() + } + + /// Returns the last key-value pair in the map. The key in this pair is the maximum key in the wal. + #[inline] + pub fn last(&self) -> Option> { + self.0.last() + } + + /// Returns an iterator over the entries in the WAL. + #[inline] + pub fn iter(&self) -> Iter { + self.0.iter() + } + + /// Returns an iterator over a subset of the entries in the WAL. + #[inline] + pub fn range_by_ref<'a, Q>( + &'a self, + start_bound: Bound<&'a Q>, + end_bound: Bound<&'a Q>, + ) -> RefRange<'a, Q, K, V> + where + Q: Ord + ?Sized + Comparable>, + { + self.0.range_by_ref(start_bound, end_bound) + } + + /// Returns an iterator over a subset of the entries in the WAL. + #[inline] + pub fn range<'a, Q>( + &'a self, + start_bound: Bound<&'a Q>, + end_bound: Bound<&'a Q>, + ) -> Range<'a, Q, K, V> + where + Q: Ord + ?Sized + Comparable + Comparable>, + { + self.0.range(start_bound, end_bound) + } +} + +impl GenericWalReader +where + K: Type + Ord, + for<'a> K::Ref<'a>: KeyRef<'a, K>, + V: Type, +{ + /// Returns `true` if the key exists in the WAL. + #[inline] + pub fn contains_key<'a, 'b: 'a, Q>(&'a self, key: &'b Q) -> bool + where + Q: ?Sized + Ord + Comparable> + Comparable, + { + self.0.contains_key(key) + } + + /// Returns `true` if the key exists in the WAL. + #[inline] + pub fn contains_key_by_ref<'a, 'b: 'a, Q>(&'a self, key: &'b Q) -> bool + where + Q: ?Sized + Ord + Comparable>, + { + self.0.contains_key_by_ref(key) + } + + /// Gets the value associated with the key. + #[inline] + pub fn get<'a, 'b: 'a, Q>(&'a self, key: &'b Q) -> Option> + where + Q: ?Sized + Ord + Comparable> + Comparable, + { + self.0.get(key) + } + + /// Gets the value associated with the key. + #[inline] + pub fn get_by_ref<'a, 'b: 'a, Q>(&'a self, key: &'b Q) -> Option> + where + Q: ?Sized + Ord + Comparable>, + { + self.0.get_by_ref(key) + } +} diff --git a/src/swmr/generic/tests.rs b/src/swmr/generic/tests.rs new file mode 100644 index 0000000..583c63b --- /dev/null +++ b/src/swmr/generic/tests.rs @@ -0,0 +1,135 @@ +use core::borrow::Borrow; + +use super::*; + +#[derive(PartialEq, Eq, PartialOrd, Ord)] +struct Foo { + a: u32, + b: u64, +} + +struct FooRef<'a> { + data: &'a [u8], +} + +impl<'a> PartialEq for FooRef<'a> { + fn eq(&self, other: &Self) -> bool { + self.data == other.data + } +} + +impl<'a> Eq for FooRef<'a> {} + +impl<'a> PartialOrd for FooRef<'a> { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl<'a> Ord for FooRef<'a> { + fn cmp(&self, other: &Self) -> cmp::Ordering { + let a = u32::from_le_bytes(self.data[0..4].try_into().unwrap()); + let b = u64::from_le_bytes(self.data[4..12].try_into().unwrap()); + let other_a = u32::from_le_bytes(other.data[0..4].try_into().unwrap()); + let other_b = u64::from_le_bytes(other.data[4..12].try_into().unwrap()); + + Foo { a, b }.cmp(&Foo { + a: other_a, + b: other_b, + }) + } +} + +impl Equivalent for FooRef<'_> { + fn equivalent(&self, key: &Foo) -> bool { + let a = u32::from_be_bytes(self.data[..8].try_into().unwrap()); + let b = u64::from_be_bytes(self.data[8..].try_into().unwrap()); + a == key.a && b == key.b + } +} + +impl Comparable for FooRef<'_> { + fn compare(&self, key: &Foo) -> std::cmp::Ordering { + let a = u32::from_be_bytes(self.data[..8].try_into().unwrap()); + let b = u64::from_be_bytes(self.data[8..].try_into().unwrap()); + Foo { a, b }.cmp(key) + } +} + +impl Equivalent> for Foo { + fn equivalent(&self, key: &FooRef<'_>) -> bool { + let a = u32::from_be_bytes(key.data[..8].try_into().unwrap()); + let b = u64::from_be_bytes(key.data[8..].try_into().unwrap()); + self.a == a && self.b == b + } +} + +impl Comparable> for Foo { + fn compare(&self, key: &FooRef<'_>) -> std::cmp::Ordering { + let a = u32::from_be_bytes(key.data[..8].try_into().unwrap()); + let b = u64::from_be_bytes(key.data[8..].try_into().unwrap()); + self.cmp(&Foo { a, b }) + } +} + +impl<'a> KeyRef<'a, Foo> for FooRef<'a> { + fn compare(&self, a: &Q) -> cmp::Ordering + where + Q: ?Sized + Ord + Comparable, + { + Comparable::compare(a, self) + } + + fn compare_binary(this: &[u8], other: &[u8]) -> cmp::Ordering { + let a = u32::from_le_bytes(this[0..4].try_into().unwrap()); + let b = u64::from_le_bytes(this[4..12].try_into().unwrap()); + let other_a = u32::from_le_bytes(other[0..4].try_into().unwrap()); + let other_b = u64::from_le_bytes(other[4..12].try_into().unwrap()); + + Foo { a, b }.cmp(&Foo { + a: other_a, + b: other_b, + }) + } +} + +impl Type for Foo { + type Ref<'a> = FooRef<'a>; + type Error = (); + + fn encoded_len(&self) -> usize { + 12 + } + + fn encode(&self, buf: &mut [u8]) -> Result<(), Self::Error> { + buf[0..4].copy_from_slice(&self.a.to_le_bytes()); + buf[4..12].copy_from_slice(&self.b.to_le_bytes()); + Ok(()) + } +} + +impl<'a> TypeRef<'a> for FooRef<'a> { + fn from_slice(src: &'a [u8]) -> Self { + FooRef { data: src } + } +} + +impl<'a> Borrow<[u8]> for FooRef<'a> { + fn borrow(&self) -> &[u8] { + self.data + } +} + +#[test] +fn generic_order_wal_flexible_lookup() { + let wal = GenericOrderWal::::new(Options::new().with_capacity(1000)).unwrap(); + assert!(wal + .get(&FooRef { + data: &[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + }) + .is_none()); + assert!(wal.get(&Foo { a: 0, b: 0 }).is_none()); + assert!(wal + .get_by_ref([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0].as_slice()) + .is_none()); +} diff --git a/src/swmr/generic/traits.rs b/src/swmr/generic/traits.rs new file mode 100644 index 0000000..bd8ce16 --- /dev/null +++ b/src/swmr/generic/traits.rs @@ -0,0 +1,112 @@ +use core::cmp; + +use among::Among; +use crossbeam_skiplist::Comparable; +use rarena_allocator::either::Either; + +mod impls; + +/// The type trait for limiting the types that can be used as keys and values in the [`GenericOrderWal`]. +/// +/// This trait and its implementors can only be used with the [`GenericOrderWal`] type, otherwise +/// the correctness of the implementations is not guaranteed. +pub trait Type { + /// The reference type for the type. + type Ref<'a>: TypeRef<'a>; + + /// The error type for encoding the type into a binary format. + type Error; + + /// Returns the length of the encoded type size. + fn encoded_len(&self) -> usize; + + /// Encodes the type into a binary slice, you can assume that the buf length is equal to the value returned by [`encoded_len`](Type::encoded_len). + fn encode(&self, buf: &mut [u8]) -> Result<(), Self::Error>; +} + +impl Type for Either { + type Ref<'a> = T::Ref<'a>; + type Error = T::Error; + + #[inline] + fn encoded_len(&self) -> usize { + match self { + Either::Left(t) => t.encoded_len(), + Either::Right(t) => t.encoded_len(), + } + } + + #[inline] + fn encode(&self, buf: &mut [u8]) -> Result<(), Self::Error> { + match self { + Either::Left(t) => t.encode(buf), + Either::Right(t) => t.encode(buf), + } + } +} + +impl Type for Either<&T, T> { + type Ref<'a> = T::Ref<'a>; + type Error = T::Error; + + #[inline] + fn encoded_len(&self) -> usize { + match self { + Either::Left(t) => t.encoded_len(), + Either::Right(t) => t.encoded_len(), + } + } + + #[inline] + fn encode(&self, buf: &mut [u8]) -> Result<(), Self::Error> { + match self { + Either::Left(t) => t.encode(buf), + Either::Right(t) => t.encode(buf), + } + } +} + +pub(super) trait InsertAmongExt { + fn encoded_len(&self) -> usize; + fn encode(&self, buf: &mut [u8]) -> Result<(), T::Error>; +} + +impl InsertAmongExt for Among { + #[inline] + fn encoded_len(&self) -> usize { + match self { + Among::Left(t) => t.encoded_len(), + Among::Middle(t) => t.encoded_len(), + Among::Right(t) => t.len(), + } + } + + #[inline] + fn encode(&self, buf: &mut [u8]) -> Result<(), T::Error> { + match self { + Among::Left(t) => t.encode(buf), + Among::Middle(t) => t.encode(buf), + Among::Right(t) => { + buf.copy_from_slice(t); + Ok(()) + } + } + } +} + +pub trait TypeRef<'a> { + /// Creates a reference type from a binary slice, when using it with [`GenericOrderWal`], + /// you can assume that the slice is the same as the one returned by [`encode`](Type::encode). + fn from_slice(src: &'a [u8]) -> Self; +} + +/// The key reference trait for comparing `K` in the [`GenericOrderWal`]. +pub trait KeyRef<'a, K>: Ord + Comparable { + /// Compares with a type `Q` which can be borrowed from [`T::Ref`](Type::Ref). + fn compare(&self, a: &Q) -> cmp::Ordering + where + Q: ?Sized + Ord + Comparable; + + /// Compares two binary formats of the `K` directly. + fn compare_binary(a: &[u8], b: &[u8]) -> cmp::Ordering; +} diff --git a/src/swmr/generic/traits/impls.rs b/src/swmr/generic/traits/impls.rs new file mode 100644 index 0000000..dea1c4e --- /dev/null +++ b/src/swmr/generic/traits/impls.rs @@ -0,0 +1,21 @@ +use super::*; + +mod bytes; +mod string; + +impl Type for () { + type Ref<'a> = (); + type Error = (); + + fn encoded_len(&self) -> usize { + 0 + } + + fn encode(&self, _buf: &mut [u8]) -> Result<(), Self::Error> { + Ok(()) + } +} + +impl TypeRef<'_> for () { + fn from_slice(_buf: &[u8]) -> Self {} +} diff --git a/src/swmr/generic/traits/impls/bytes.rs b/src/swmr/generic/traits/impls/bytes.rs new file mode 100644 index 0000000..2f0bf51 --- /dev/null +++ b/src/swmr/generic/traits/impls/bytes.rs @@ -0,0 +1,82 @@ +use std::{borrow::Cow, sync::Arc}; + +use super::*; + +macro_rules! impls { + ($( $(#[cfg($cfg:meta)])? $ty:ty),+ $(,)?) => { + $( + $(#[cfg($cfg)])? + impl Type for $ty { + type Ref<'a> = &'a [u8]; + type Error = (); + + fn encoded_len(&self) -> usize { + self.len() + } + + fn encode(&self, buf: &mut [u8]) -> Result<(), Self::Error> { + buf.copy_from_slice(self.as_ref()); + Ok(()) + } + } + + $(#[cfg($cfg)])? + impl<'a> KeyRef<'a, $ty> for [u8] { + fn compare(&self, a: &Q) -> cmp::Ordering + where + Q: ?Sized + Ord + Comparable, + { + Comparable::compare(a, self).reverse() + } + + fn compare_binary(a: &[u8], b: &[u8]) -> cmp::Ordering { + a.cmp(b) + } + } + )* + }; +} + +impl<'a> TypeRef<'a> for &'a [u8] { + fn from_slice(src: &'a [u8]) -> Self { + src + } +} + +impls! { + Cow<'_, [u8]>, + Vec, + Box<[u8]>, + Arc<[u8]>, + #[cfg(feature = "bytes")] + ::bytes::Bytes, +} + +#[cfg(feature = "smallvec")] +impl Type for ::smallvec::SmallVec<[u8; N]> { + type Ref<'a> = &'a [u8]; + type Error = (); + + fn encoded_len(&self) -> usize { + self.len() + } + + fn encode(&self, buf: &mut [u8]) -> Result<(), Self::Error> { + buf.copy_from_slice(self.as_ref()); + Ok(()) + } +} + +#[cfg(feature = "smallvec")] +impl<'a, const N: usize> KeyRef<'a, ::smallvec::SmallVec<[u8; N]>> for [u8] { + fn compare(&self, a: &Q) -> cmp::Ordering + where + Q: ?Sized + Ord + Comparable, + { + Comparable::compare(a, self).reverse() + } + + fn compare_binary(a: &[u8], b: &[u8]) -> cmp::Ordering { + a.cmp(b) + } +} diff --git a/src/swmr/generic/traits/impls/string.rs b/src/swmr/generic/traits/impls/string.rs new file mode 100644 index 0000000..6295914 --- /dev/null +++ b/src/swmr/generic/traits/impls/string.rs @@ -0,0 +1,56 @@ +use std::{borrow::Cow, sync::Arc}; + +use super::*; + +macro_rules! impls { + ($( $(#[cfg($cfg:meta)])? $ty:ty),+ $(,)?) => { + $( + $(#[cfg($cfg)])? + impl Type for $ty { + type Ref<'a> = &'a str; + type Error = (); + + fn encoded_len(&self) -> usize { + self.len() + } + + fn encode(&self, buf: &mut [u8]) -> Result<(), Self::Error> { + buf.copy_from_slice(self.as_bytes()); + Ok(()) + } + } + + $(#[cfg($cfg)])? + impl<'a> KeyRef<'a, $ty> for str { + fn compare(&self, a: &Q) -> cmp::Ordering + where + Q: ?Sized + Ord + Comparable, + { + Comparable::compare(a, self).reverse() + } + + fn compare_binary(a: &[u8], b: &[u8]) -> cmp::Ordering { + a.cmp(b) + } + } + )* + }; +} + +impl<'a> TypeRef<'a> for &'a str { + fn from_slice(src: &'a [u8]) -> Self { + core::str::from_utf8(src).unwrap() + } +} + +impls! { + Cow<'_, str>, + &'static str, + String, + Arc, + Box, + #[cfg(feature = "smol_str")] + ::smol_str::SmolStr, + #[cfg(feature = "faststr")] + ::faststr::FastStr, +} diff --git a/src/swmr/wal.rs b/src/swmr/wal.rs new file mode 100644 index 0000000..b90782a --- /dev/null +++ b/src/swmr/wal.rs @@ -0,0 +1,441 @@ +use super::super::*; + +use among::Among; +use either::Either; +use error::Error; +use wal::{ + sealed::{Base, Constructor, Sealed, WalCore}, + ImmutableWal, +}; + +use core::ptr::NonNull; +use rarena_allocator::{sync::Arena, Error as ArenaError}; +use std::sync::Arc; + +mod reader; +pub use reader::*; + +mod iter; +pub use iter::*; + +#[cfg(test)] +mod tests; + +pub struct OrderWalCore { + arena: Arena, + map: SkipSet>, + opts: Options, + cmp: C, + cks: UnsafeCellChecksumer, +} + +impl OrderWalCore { + #[inline] + fn iter(&self) -> Iter { + Iter::new(self.map.iter()) + } +} + +impl Base for SkipSet> { + fn insert(&mut self, ele: Pointer) + where + C: Comparator, + { + SkipSet::insert(self, ele); + } +} + +impl WalCore for OrderWalCore { + type Allocator = Arena; + type Base = SkipSet>; + + #[inline] + fn construct( + arena: Arena, + set: SkipSet>, + opts: Options, + cmp: C, + checksumer: S, + ) -> Self { + Self { + arena, + map: set, + cmp, + opts, + cks: UnsafeCellChecksumer::new(checksumer), + } + } +} + +/// A single writer multiple readers ordered write-ahead log implementation. +/// +/// Only the first instance of the WAL can write to the log, while the rest can only read from the log. +// ```text +// +----------------------+--------------------------+--------------------+ +// | magic text (6 bytes) | magic version (2 bytes) | header (8 bytes) | +// +----------------------+--------------------------+--------------------+-----------------+--------------------+ +// | flag (1 byte) | klen & vlen (1-10 bytes) | key (n bytes) | value (n bytes) | checksum (8 bytes) | +// +----------------------+--------------------------+--------------------+-----------------|--------------------+ +// | flag (1 byte) | klen & vlen (1-10 bytes) | key (n bytes) | value (n bytes) | checksum (8 bytes) | +// +----------------------+--------------------------+--------------------+-----------------+--------------------+ +// | flag (1 byte) | klen & vlen (1-10 bytes) | key (n bytes) | value (n bytes) | checksum (8 bytes) | +// +----------------------+--------------------------+--------------------+-----------------+-----------------+--------------------+ +// | ... | ... | ... | ... | ... | ... | +// +----------------------+--------------------------+--------------------+-----------------+-----------------+--------------------+ +// | ... | ... | ... | ... | ... | ... | +// +----------------------+--------------------------+--------------------+-----------------+-----------------+--------------------+ +// ``` +pub struct OrderWal { + core: Arc>, + ro: bool, + _s: PhantomData, +} + +impl Constructor for OrderWal +where + C: Send + 'static, +{ + type Allocator = Arena; + type Core = OrderWalCore; + + #[inline] + fn from_core(core: Self::Core, ro: bool) -> Self { + Self { + core: Arc::new(core), + ro, + _s: PhantomData, + } + } +} + +impl Sealed for OrderWal +where + C: Send + 'static, +{ + fn insert_with_in( + &mut self, + kb: KeyBuilder) -> Result<(), KE>>, + vb: ValueBuilder) -> Result<(), VE>>, + ) -> Result<(), Among> + where + C: Comparator + CheapClone, + S: Checksumer, + { + let (klen, kf) = kb.into_components(); + let (vlen, vf) = vb.into_components(); + let (len_size, kvlen, elen) = entry_size(klen, vlen); + let klen = klen as usize; + let vlen = vlen as usize; + let buf = self.core.arena.alloc_bytes(elen); + + match buf { + Err(e) => { + let e = match e { + ArenaError::InsufficientSpace { + requested, + available, + } => error::Error::insufficient_space(requested, available), + ArenaError::ReadOnly => error::Error::read_only(), + _ => unreachable!(), + }; + Err(Among::Right(e)) + } + Ok(mut buf) => { + unsafe { + // We allocate the buffer with the exact size, so it's safe to write to the buffer. + let flag = Flags::COMMITTED.bits(); + + self.core.cks.reset(); + self.core.cks.update(&[flag]); + + buf.put_u8_unchecked(Flags::empty().bits()); + let written = buf.put_u64_varint_unchecked(kvlen); + debug_assert_eq!( + written, len_size, + "the precalculated size should be equal to the written size" + ); + + let ko = STATUS_SIZE + written; + buf.set_len(ko + klen + vlen); + + kf(&mut VacantBuffer::new( + klen, + NonNull::new_unchecked(buf.as_mut_ptr().add(ko)), + )) + .map_err(Among::Left)?; + + let vo = ko + klen; + vf(&mut VacantBuffer::new( + vlen, + NonNull::new_unchecked(buf.as_mut_ptr().add(vo)), + )) + .map_err(Among::Middle)?; + + let cks = { + self.core.cks.update(&buf[1..]); + self.core.cks.digest() + }; + buf.put_u64_le_unchecked(cks); + + // commit the entry + buf[0] |= Flags::COMMITTED.bits(); + + if self.core.opts.sync_on_write() && self.core.arena.is_ondisk() { + self + .core + .arena + .flush_range(buf.offset(), elen as usize) + .map_err(|e| Among::Right(e.into()))?; + } + buf.detach(); + self.core.map.insert(Pointer::new( + klen, + vlen, + buf.as_ptr().add(ko), + self.core.cmp.cheap_clone(), + )); + Ok(()) + } + } + } + } +} + +impl OrderWal { + /// Returns the read-only view for the WAL. + #[inline] + pub fn reader(&self) -> OrderWalReader { + OrderWalReader::new(self.core.clone()) + } + + /// Returns the path of the WAL if it is backed by a file. + pub fn path(&self) -> Option<&std::sync::Arc> { + self.core.arena.path() + } +} + +impl ImmutableWal for OrderWal +where + C: Send + 'static, +{ + type Iter<'a> = Iter<'a, C> where Self: 'a, C: Comparator; + type Range<'a, Q, R> = Range<'a, Q, R, C> + where + R: core::ops::RangeBounds, + [u8]: Borrow, + Q: Ord + ?Sized, + Self: 'a, + C: Comparator; + type Keys<'a> = Keys<'a, C> where Self: 'a, C: Comparator; + + type RangeKeys<'a, Q, R> = RangeKeys<'a, Q, R, C> + where + R: core::ops::RangeBounds, + [u8]: Borrow, + Q: Ord + ?Sized, + Self: 'a, + C: Comparator; + + type Values<'a> = Values<'a, C> where Self: 'a, C: Comparator; + + type RangeValues<'a, Q, R> = RangeValues<'a, Q, R, C> + where + R: core::ops::RangeBounds, + [u8]: Borrow, + Q: Ord + ?Sized, + Self: 'a, + C: Comparator; + + #[inline] + unsafe fn reserved_slice(&self) -> &[u8] { + if self.core.opts.reserved() == 0 { + return &[]; + } + + &self.core.arena.reserved_slice()[HEADER_SIZE..] + } + + #[inline] + fn read_only(&self) -> bool { + self.ro + } + + #[inline] + fn len(&self) -> usize { + self.core.map.len() + } + + #[inline] + fn maximum_key_size(&self) -> u32 { + self.core.opts.maximum_key_size() + } + + #[inline] + fn maximum_value_size(&self) -> u32 { + self.core.opts.maximum_value_size() + } + + #[inline] + fn contains_key(&self, key: &Q) -> bool + where + [u8]: Borrow, + Q: ?Sized + Ord, + C: Comparator, + { + self.core.map.contains(key) + } + + #[inline] + fn iter(&self) -> Self::Iter<'_> + where + C: Comparator, + { + self.core.iter() + } + + #[inline] + fn range(&self, range: R) -> Self::Range<'_, Q, R> + where + R: core::ops::RangeBounds, + [u8]: Borrow, + Q: Ord + ?Sized + crossbeam_skiplist::Comparable<[u8]>, + C: Comparator, + { + Range::new(self.core.map.range(range)) + } + + #[inline] + fn keys(&self) -> Self::Keys<'_> + where + C: Comparator, + { + Keys::new(self.core.map.iter()) + } + + #[inline] + fn range_keys(&self, range: R) -> Self::RangeKeys<'_, Q, R> + where + R: core::ops::RangeBounds, + [u8]: Borrow, + Q: Ord + ?Sized, + C: Comparator, + { + RangeKeys::new(self.core.map.range(range)) + } + + #[inline] + fn values(&self) -> Self::Values<'_> + where + C: Comparator, + { + Values::new(self.core.map.iter()) + } + + #[inline] + fn range_values(&self, range: R) -> Self::RangeValues<'_, Q, R> + where + R: core::ops::RangeBounds, + [u8]: Borrow, + Q: Ord + ?Sized, + C: Comparator, + { + RangeValues::new(self.core.map.range(range)) + } + + #[inline] + fn first(&self) -> Option<(&[u8], &[u8])> + where + C: Comparator, + { + self + .core + .map + .front() + .map(|ent| (ent.as_key_slice(), ent.as_value_slice())) + } + + #[inline] + fn last(&self) -> Option<(&[u8], &[u8])> + where + C: Comparator, + { + self + .core + .map + .back() + .map(|ent| (ent.as_key_slice(), ent.as_value_slice())) + } + + #[inline] + fn get(&self, key: &Q) -> Option<&[u8]> + where + [u8]: Borrow, + Q: ?Sized + Ord, + C: Comparator, + { + self.core.map.get(key).map(|ent| ent.as_value_slice()) + } +} + +impl Wal for OrderWal +where + C: Send + 'static, +{ + type Reader = OrderWalReader; + + #[inline] + fn flush(&self) -> Result<(), Error> { + if self.ro { + return Err(error::Error::read_only()); + } + + self.core.arena.flush().map_err(Into::into) + } + + #[inline] + fn flush_async(&self) -> Result<(), Error> { + if self.ro { + return Err(error::Error::read_only()); + } + + self.core.arena.flush_async().map_err(Into::into) + } + + #[inline] + unsafe fn reserved_slice_mut(&mut self) -> &mut [u8] { + if self.core.opts.reserved() == 0 { + return &mut []; + } + + &mut self.core.arena.reserved_slice_mut()[HEADER_SIZE..] + } + + fn get_or_insert_with_value_builder( + &mut self, + key: &[u8], + vb: ValueBuilder) -> Result<(), E>>, + ) -> Result, Either> + where + C: Comparator + CheapClone, + S: Checksumer, + { + if self.read_only() { + return Err(Either::Right(Error::read_only())); + } + + self + .check( + key.len(), + vb.size() as usize, + self.maximum_key_size(), + self.maximum_value_size(), + ) + .map_err(Either::Right)?; + + if let Some(ent) = self.core.map.get(key) { + return Ok(Some(ent.as_value_slice())); + } + + self.insert_with_value_builder::(key, vb).map(|_| None) + } +} diff --git a/src/swmr/wal/iter.rs b/src/swmr/wal/iter.rs new file mode 100644 index 0000000..ddc8244 --- /dev/null +++ b/src/swmr/wal/iter.rs @@ -0,0 +1,288 @@ +use core::{borrow::Borrow, ops::RangeBounds}; + +use crossbeam_skiplist::Comparable; +use dbutils::Comparator; + +use super::Pointer; + +/// An iterator over the entries in the WAL. +pub struct Iter<'a, C> { + iter: crossbeam_skiplist::set::Iter<'a, Pointer>, +} + +impl<'a, C> Iter<'a, C> { + #[inline] + pub(super) fn new(iter: crossbeam_skiplist::set::Iter<'a, Pointer>) -> Self { + Self { iter } + } +} + +impl<'a, C: Comparator> Iterator for Iter<'a, C> { + type Item = (&'a [u8], &'a [u8]); + + #[inline] + fn next(&mut self) -> Option { + self + .iter + .next() + .map(|ptr| (ptr.as_key_slice(), ptr.as_value_slice())) + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.iter.size_hint() + } +} + +impl<'a, C: Comparator> DoubleEndedIterator for Iter<'a, C> { + #[inline] + fn next_back(&mut self) -> Option { + self + .iter + .next_back() + .map(|ptr| (ptr.as_key_slice(), ptr.as_value_slice())) + } +} + +/// An iterator over the keys in the WAL. +pub struct Keys<'a, C> { + iter: crossbeam_skiplist::set::Iter<'a, Pointer>, +} + +impl<'a, C> Keys<'a, C> { + #[inline] + pub(super) fn new(iter: crossbeam_skiplist::set::Iter<'a, Pointer>) -> Self { + Self { iter } + } +} + +impl<'a, C: Comparator> Iterator for Keys<'a, C> { + type Item = &'a [u8]; + + #[inline] + fn next(&mut self) -> Option { + self.iter.next().map(|ptr| ptr.as_key_slice()) + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.iter.size_hint() + } +} + +impl<'a, C: Comparator> DoubleEndedIterator for Keys<'a, C> { + #[inline] + fn next_back(&mut self) -> Option { + self.iter.next_back().map(|ptr| ptr.as_key_slice()) + } +} + +/// An iterator over the values in the WAL. +pub struct Values<'a, C> { + iter: crossbeam_skiplist::set::Iter<'a, Pointer>, +} + +impl<'a, C> Values<'a, C> { + #[inline] + pub(super) fn new(iter: crossbeam_skiplist::set::Iter<'a, Pointer>) -> Self { + Self { iter } + } +} + +impl<'a, C: Comparator> Iterator for Values<'a, C> { + type Item = &'a [u8]; + + #[inline] + fn next(&mut self) -> Option { + self.iter.next().map(|ptr| ptr.as_value_slice()) + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.iter.size_hint() + } +} + +impl<'a, C: Comparator> DoubleEndedIterator for Values<'a, C> { + #[inline] + fn next_back(&mut self) -> Option { + self.iter.next_back().map(|ptr| ptr.as_value_slice()) + } +} + +/// An iterator over a subset of the entries in the WAL. +pub struct Range<'a, Q, R, C> +where + C: Comparator, + R: RangeBounds, + [u8]: Borrow, + Q: Ord + ?Sized + Comparable<[u8]>, +{ + iter: crossbeam_skiplist::set::Range<'a, Q, R, Pointer>, +} + +impl<'a, Q, R, C> Range<'a, Q, R, C> +where + C: Comparator, + R: RangeBounds, + [u8]: Borrow, + Q: Ord + ?Sized + Comparable<[u8]>, +{ + #[inline] + pub(super) fn new(iter: crossbeam_skiplist::set::Range<'a, Q, R, Pointer>) -> Self { + Self { iter } + } +} + +impl<'a, Q, R, C> Iterator for Range<'a, Q, R, C> +where + C: Comparator, + R: RangeBounds, + [u8]: Borrow, + Q: Ord + ?Sized + Comparable<[u8]>, +{ + type Item = (&'a [u8], &'a [u8]); + + #[inline] + fn next(&mut self) -> Option { + self + .iter + .next() + .map(|ptr| (ptr.as_key_slice(), ptr.as_value_slice())) + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.iter.size_hint() + } +} + +impl<'a, Q, R, C> DoubleEndedIterator for Range<'a, Q, R, C> +where + C: Comparator, + R: RangeBounds, + [u8]: Borrow, + Q: Ord + ?Sized + Comparable<[u8]>, +{ + #[inline] + fn next_back(&mut self) -> Option { + self + .iter + .next_back() + .map(|ptr| (ptr.as_key_slice(), ptr.as_value_slice())) + } +} + +/// An iterator over the keys in a subset of the entries in the WAL. +pub struct RangeKeys<'a, Q, R, C> +where + C: Comparator, + R: RangeBounds, + [u8]: Borrow, + Q: Ord + ?Sized + Comparable<[u8]>, +{ + iter: crossbeam_skiplist::set::Range<'a, Q, R, Pointer>, +} + +impl<'a, Q, R, C> RangeKeys<'a, Q, R, C> +where + C: Comparator, + R: RangeBounds, + [u8]: Borrow, + Q: Ord + ?Sized + Comparable<[u8]>, +{ + #[inline] + pub(super) fn new(iter: crossbeam_skiplist::set::Range<'a, Q, R, Pointer>) -> Self { + Self { iter } + } +} + +impl<'a, Q, R, C> Iterator for RangeKeys<'a, Q, R, C> +where + C: Comparator, + R: RangeBounds, + [u8]: Borrow, + Q: Ord + ?Sized + Comparable<[u8]>, +{ + type Item = &'a [u8]; + + #[inline] + fn next(&mut self) -> Option { + self.iter.next().map(|ptr| ptr.as_key_slice()) + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.iter.size_hint() + } +} + +impl<'a, Q, R, C> DoubleEndedIterator for RangeKeys<'a, Q, R, C> +where + C: Comparator, + R: RangeBounds, + [u8]: Borrow, + Q: Ord + ?Sized + Comparable<[u8]>, +{ + #[inline] + fn next_back(&mut self) -> Option { + self.iter.next_back().map(|ptr| ptr.as_key_slice()) + } +} + +/// An iterator over the values in a subset of the entries in the WAL. +pub struct RangeValues<'a, Q, R, C> +where + C: Comparator, + R: RangeBounds, + [u8]: Borrow, + Q: Ord + ?Sized + Comparable<[u8]>, +{ + iter: crossbeam_skiplist::set::Range<'a, Q, R, Pointer>, +} + +impl<'a, Q, R, C> RangeValues<'a, Q, R, C> +where + C: Comparator, + R: RangeBounds, + [u8]: Borrow, + Q: Ord + ?Sized + Comparable<[u8]>, +{ + #[inline] + pub(super) fn new(iter: crossbeam_skiplist::set::Range<'a, Q, R, Pointer>) -> Self { + Self { iter } + } +} + +impl<'a, Q, R, C> Iterator for RangeValues<'a, Q, R, C> +where + C: Comparator, + R: RangeBounds, + [u8]: Borrow, + Q: Ord + ?Sized + Comparable<[u8]>, +{ + type Item = &'a [u8]; + + #[inline] + fn next(&mut self) -> Option { + self.iter.next().map(|ptr| ptr.as_value_slice()) + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.iter.size_hint() + } +} + +impl<'a, Q, R, C> DoubleEndedIterator for RangeValues<'a, Q, R, C> +where + C: Comparator, + R: RangeBounds, + [u8]: Borrow, + Q: Ord + ?Sized + Comparable<[u8]>, +{ + #[inline] + fn next_back(&mut self) -> Option { + self.iter.next_back().map(|ptr| ptr.as_value_slice()) + } +} diff --git a/src/swmr/wal/reader.rs b/src/swmr/wal/reader.rs new file mode 100644 index 0000000..b80ee6e --- /dev/null +++ b/src/swmr/wal/reader.rs @@ -0,0 +1,178 @@ +use super::*; + +/// An [`OrderWal`] reader. +pub struct OrderWalReader(OrderWal); + +impl OrderWalReader { + /// Creates a new read-only WAL reader. + #[inline] + pub(super) fn new(wal: Arc>) -> Self { + Self(OrderWal { + core: wal.clone(), + ro: true, + _s: PhantomData, + }) + } +} + +impl Constructor for OrderWalReader { + type Allocator = Arena; + + type Core = OrderWalCore; + + fn from_core(core: Self::Core, _ro: bool) -> Self { + Self(OrderWal { + core: Arc::new(core), + ro: true, + _s: PhantomData, + }) + } +} + +impl ImmutableWal for OrderWalReader { + type Iter<'a> = Iter<'a, C> where Self: 'a, C: Comparator; + type Range<'a, Q, R> = Range<'a, Q, R, C> + where + R: core::ops::RangeBounds, + [u8]: Borrow, + Q: Ord + ?Sized, + Self: 'a, + C: Comparator; + type Keys<'a> = Keys<'a, C> where Self: 'a, C: Comparator; + + type RangeKeys<'a, Q, R> = RangeKeys<'a, Q, R, C> + where + R: core::ops::RangeBounds, + [u8]: Borrow, + Q: Ord + ?Sized, + Self: 'a, + C: Comparator; + + type Values<'a> = Values<'a, C> where Self: 'a, C: Comparator; + + type RangeValues<'a, Q, R> = RangeValues<'a, Q, R, C> + where + R: core::ops::RangeBounds, + [u8]: Borrow, + Q: Ord + ?Sized, + Self: 'a, + C: Comparator; + + #[inline] + unsafe fn reserved_slice(&self) -> &[u8] { + self.0.reserved_slice() + } + + #[inline] + fn read_only(&self) -> bool { + self.0.read_only() + } + + #[inline] + fn len(&self) -> usize { + self.0.len() + } + + #[inline] + fn maximum_key_size(&self) -> u32 { + self.0.maximum_key_size() + } + + #[inline] + fn maximum_value_size(&self) -> u32 { + self.0.maximum_value_size() + } + + #[inline] + fn contains_key(&self, key: &Q) -> bool + where + [u8]: Borrow, + Q: ?Sized + Ord, + C: Comparator, + { + self.0.contains_key(key) + } + + #[inline] + fn iter(&self) -> Self::Iter<'_> + where + C: Comparator, + { + self.0.iter() + } + + #[inline] + fn range(&self, range: R) -> Self::Range<'_, Q, R> + where + R: core::ops::RangeBounds, + [u8]: Borrow, + Q: Ord + ?Sized, + C: Comparator, + { + self.0.range(range) + } + + #[inline] + fn keys(&self) -> Self::Keys<'_> + where + C: Comparator, + { + self.0.keys() + } + + #[inline] + fn range_keys(&self, range: R) -> Self::RangeKeys<'_, Q, R> + where + R: core::ops::RangeBounds, + [u8]: Borrow, + Q: Ord + ?Sized, + C: Comparator, + { + self.0.range_keys(range) + } + + #[inline] + fn values(&self) -> Self::Values<'_> + where + C: Comparator, + { + self.0.values() + } + + #[inline] + fn range_values(&self, range: R) -> Self::RangeValues<'_, Q, R> + where + R: core::ops::RangeBounds, + [u8]: Borrow, + Q: Ord + ?Sized, + C: Comparator, + { + self.0.range_values(range) + } + + #[inline] + fn first(&self) -> Option<(&[u8], &[u8])> + where + C: Comparator, + { + self.0.first() + } + + #[inline] + fn last(&self) -> Option<(&[u8], &[u8])> + where + C: Comparator, + { + self.0.last() + } + + #[inline] + fn get(&self, key: &Q) -> Option<&[u8]> + where + [u8]: Borrow, + Q: ?Sized + Ord, + C: Comparator, + { + self.0.get(key) + } +} diff --git a/src/swmr/wal/tests.rs b/src/swmr/wal/tests.rs new file mode 100644 index 0000000..ed49ee5 --- /dev/null +++ b/src/swmr/wal/tests.rs @@ -0,0 +1,19 @@ +use crate::tests::*; + +use super::*; + +#[test] +fn test_construct_inmemory() { + construct_inmemory::>(); +} + +#[test] +fn test_construct_map_anon() { + construct_map_anon::>(); +} + +#[test] +#[cfg_attr(miri, ignore)] +fn test_construct_map_file() { + construct_map_file::>("swmr"); +} diff --git a/src/tests.rs b/src/tests.rs new file mode 100644 index 0000000..b812d2c --- /dev/null +++ b/src/tests.rs @@ -0,0 +1,83 @@ +use super::*; +use tempfile::tempdir; +use wal::ImmutableWal; + +const MB: usize = 1024 * 1024; + +pub(crate) fn construct_inmemory>() { + let mut wal = W::new(Builder::new().with_capacity(MB as u32)).unwrap(); + let wal = &mut wal; + wal.insert(b"key1", b"value1").unwrap(); +} + +pub(crate) fn construct_map_anon>() { + let mut wal = W::map_anon(Builder::new().with_capacity(MB as u32)).unwrap(); + let wal = &mut wal; + wal.insert(b"key1", b"value1").unwrap(); +} + +pub(crate) fn construct_map_file>(prefix: &str) { + let dir = tempdir().unwrap(); + let path = dir.path().join(format!("{prefix}_construct_map_file")); + + { + let mut wal = W::map_mut( + &path, + Builder::new(), + OpenOptions::new() + .create_new(Some(MB as u32)) + .write(true) + .read(true), + ) + .unwrap(); + + let wal = &mut wal; + wal.insert(b"key1", b"value1").unwrap(); + assert_eq!(wal.get(b"key1").unwrap(), b"value1"); + } + + let wal = W::map(&path, Builder::new()).unwrap(); + assert_eq!(wal.get(b"key1").unwrap(), b"value1"); +} + +pub(crate) fn construct_with_small_capacity_inmemory>() { + let wal = W::new(Builder::new().with_capacity(1)); + + assert!(wal.is_err()); + match wal { + Err(e) => println!("error: {:?}", e), + _ => panic!("unexpected error"), + } +} + +pub(crate) fn construct_with_small_capacity_map_anon>() { + let wal = W::map_anon(Builder::new().with_capacity(1)); + + assert!(wal.is_err()); + match wal { + Err(e) => println!("error: {:?}", e), + _ => panic!("unexpected error"), + } +} + +pub(crate) fn construct_with_small_capacity_map_file>(prefix: &str) { + let dir = tempdir().unwrap(); + let path = dir + .path() + .join(format!("{prefix}_construct_with_small_capacity_map_file")); + + let wal = W::map_mut( + &path, + Builder::new(), + OpenOptions::new() + .create_new(Some(1)) + .write(true) + .read(true), + ); + + assert!(wal.is_err()); + match wal { + Err(e) => println!("{:?}", e), + _ => panic!("unexpected error"), + } +} diff --git a/src/unsync.rs b/src/unsync.rs new file mode 100644 index 0000000..fa5a55d --- /dev/null +++ b/src/unsync.rs @@ -0,0 +1,396 @@ +use super::*; + +use among::Among; +use either::Either; +use error::Error; +use wal::{ + sealed::{Constructor, Sealed}, + ImmutableWal, +}; + +use core::ptr::NonNull; +use rarena_allocator::{unsync::Arena, Error as ArenaError}; +use std::collections::BTreeSet; + +/// Iterators for the `OrderWal`. +pub mod iter; +use iter::*; + +mod c; +use c::*; + +#[cfg(test)] +mod tests; + +/// An ordered write-ahead log implementation for single thread environments. +/// +/// Only the first instance of the WAL can write to the log, while the rest can only read from the log. +// ```text +// +----------------------+-------------------------+--------------------+ +// | magic text (6 bytes) | magic version (2 bytes) | header (8 bytes) | +// +----------------------+-------------------------+--------------------+---------------------+-----------------+--------------------+ +// | flag (1 byte) | key len (4 bytes) | key (n bytes) | value len (4 bytes) | value (n bytes) | checksum (8 bytes) | +// +----------------------+-------------------------+--------------------+---------------------+-----------------|--------------------+ +// | flag (1 byte) | key len (4 bytes) | key (n bytes) | value len (4 bytes) | value (n bytes) | checksum (8 bytes) | +// +----------------------+-------------------------+--------------------+---------------------+-----------------+--------------------+ +// | flag (1 byte) | key len (4 bytes) | key (n bytes) | value len (4 bytes) | value (n bytes) | checksum (8 bytes) | +// +----------------------+-------------------------+--------------------+---------------------+-----------------+--------------------+ +// | ... | ... | ... | ... | ... | ... | +// +----------------------+-------------------------+--------------------+---------------------+-----------------+--------------------+ +// | ... | ... | ... | ... | ... | ... | +// +----------------------+-------------------------+--------------------+---------------------+-----------------+--------------------+ +// ``` +pub struct OrderWal { + core: OrderWalCore, + ro: bool, + _s: PhantomData, +} + +impl Constructor for OrderWal +where + C: 'static, +{ + type Allocator = Arena; + type Core = OrderWalCore; + + #[inline] + fn from_core(core: Self::Core, ro: bool) -> Self { + Self { + core, + ro, + _s: PhantomData, + } + } +} + +impl OrderWal { + /// Returns the path of the WAL if it is backed by a file. + pub fn path(&self) -> Option<&std::rc::Rc> { + self.core.arena.path() + } +} + +impl Sealed for OrderWal +where + C: 'static, +{ + fn insert_with_in( + &mut self, + kb: KeyBuilder) -> Result<(), KE>>, + vb: ValueBuilder) -> Result<(), VE>>, + ) -> Result<(), Among> + where + C: Comparator + CheapClone, + S: Checksumer, + { + let (klen, kf) = kb.into_components(); + let (vlen, vf) = vb.into_components(); + let (len_size, kvlen, elen) = entry_size(klen, vlen); + let klen = klen as usize; + let vlen = vlen as usize; + let buf = self.core.arena.alloc_bytes(elen); + + match buf { + Err(e) => { + let e = match e { + ArenaError::InsufficientSpace { + requested, + available, + } => error::Error::insufficient_space(requested, available), + ArenaError::ReadOnly => error::Error::read_only(), + _ => unreachable!(), + }; + Err(Among::Right(e)) + } + Ok(mut buf) => { + unsafe { + // We allocate the buffer with the exact size, so it's safe to write to the buffer. + let flag = Flags::COMMITTED.bits(); + + self.core.cks.reset(); + self.core.cks.update(&[flag]); + + buf.put_u8_unchecked(Flags::empty().bits()); + let written = buf.put_u64_varint_unchecked(kvlen); + debug_assert_eq!( + written, len_size, + "the precalculated size should be equal to the written size" + ); + + let ko = STATUS_SIZE + written; + buf.set_len(ko + klen + vlen); + + kf(&mut VacantBuffer::new( + klen, + NonNull::new_unchecked(buf.as_mut_ptr().add(ko)), + )) + .map_err(Among::Left)?; + + let vo = ko + klen; + vf(&mut VacantBuffer::new( + vlen, + NonNull::new_unchecked(buf.as_mut_ptr().add(vo)), + )) + .map_err(Among::Middle)?; + + let cks = { + self.core.cks.update(&buf[1..]); + self.core.cks.digest() + }; + buf.put_u64_le_unchecked(cks); + + // commit the entry + buf[0] |= Flags::COMMITTED.bits(); + + if self.core.opts.sync_on_write() && self.core.arena.is_ondisk() { + self + .core + .arena + .flush_range(buf.offset(), elen as usize) + .map_err(|e| Among::Right(e.into()))?; + } + buf.detach(); + self.core.map.insert(Pointer::new( + klen, + vlen, + buf.as_ptr().add(ko), + self.core.cmp.cheap_clone(), + )); + Ok(()) + } + } + } + } +} + +impl ImmutableWal for OrderWal +where + C: 'static, +{ + type Iter<'a> = Iter<'a, C> where Self: 'a, C: Comparator; + type Range<'a, Q, R> = Range<'a, C> + where + R: core::ops::RangeBounds, + [u8]: Borrow, + Q: Ord + ?Sized + crossbeam_skiplist::Comparable<[u8]>, + Self: 'a, + C: Comparator; + type Keys<'a> = Keys<'a, C> where Self: 'a, C: Comparator; + + type RangeKeys<'a, Q, R> = RangeKeys<'a, C> + where + R: core::ops::RangeBounds, + [u8]: Borrow, + Q: Ord + ?Sized, + Self: 'a, + C: Comparator; + + type Values<'a> = Values<'a, C> where Self: 'a, C: Comparator; + + type RangeValues<'a, Q, R> = RangeValues<'a, C> + where + R: core::ops::RangeBounds, + [u8]: Borrow, + Q: Ord + ?Sized, + Self: 'a, + C: Comparator; + + unsafe fn reserved_slice(&self) -> &[u8] { + if self.core.opts.reserved() == 0 { + return &[]; + } + + &self.core.arena.reserved_slice()[HEADER_SIZE..] + } + + #[inline] + fn read_only(&self) -> bool { + self.ro + } + + /// Returns the number of entries in the WAL. + #[inline] + fn len(&self) -> usize { + self.core.map.len() + } + + /// Returns `true` if the WAL is empty. + #[inline] + fn is_empty(&self) -> bool { + self.core.map.is_empty() + } + + #[inline] + fn maximum_key_size(&self) -> u32 { + self.core.opts.maximum_key_size() + } + + #[inline] + fn maximum_value_size(&self) -> u32 { + self.core.opts.maximum_value_size() + } + + #[inline] + fn contains_key(&self, key: &Q) -> bool + where + [u8]: Borrow, + Q: ?Sized + Ord, + C: Comparator, + { + self.core.map.contains(key) + } + + #[inline] + fn iter(&self) -> Self::Iter<'_> + where + C: Comparator, + { + Iter::new(self.core.map.iter()) + } + + #[inline] + fn range(&self, range: R) -> Self::Range<'_, Q, R> + where + R: core::ops::RangeBounds, + [u8]: Borrow, + Q: Ord + ?Sized, + C: Comparator, + { + Range::new(self.core.map.range(range)) + } + + #[inline] + fn keys(&self) -> Self::Keys<'_> + where + C: Comparator, + { + Keys::new(self.core.map.iter()) + } + + #[inline] + fn range_keys(&self, range: R) -> Self::RangeKeys<'_, Q, R> + where + R: core::ops::RangeBounds, + [u8]: Borrow, + Q: Ord + ?Sized, + C: Comparator, + { + RangeKeys::new(self.core.map.range(range)) + } + + #[inline] + fn values(&self) -> Self::Values<'_> + where + C: Comparator, + { + Values::new(self.core.map.iter()) + } + + #[inline] + fn range_values(&self, range: R) -> Self::RangeValues<'_, Q, R> + where + R: core::ops::RangeBounds, + [u8]: Borrow, + Q: Ord + ?Sized, + C: Comparator, + { + RangeValues::new(self.core.map.range(range)) + } + + #[inline] + fn first(&self) -> Option<(&[u8], &[u8])> + where + C: Comparator, + { + self + .core + .map + .first() + .map(|ent| (ent.as_key_slice(), ent.as_value_slice())) + } + + #[inline] + fn last(&self) -> Option<(&[u8], &[u8])> + where + C: Comparator, + { + self + .core + .map + .last() + .map(|ent| (ent.as_key_slice(), ent.as_value_slice())) + } + + #[inline] + fn get(&self, key: &Q) -> Option<&[u8]> + where + [u8]: Borrow, + Q: ?Sized + Ord, + C: Comparator, + { + self.core.map.get(key).map(|ent| ent.as_value_slice()) + } +} + +impl Wal for OrderWal +where + C: 'static, +{ + type Reader = Self; + + #[inline] + unsafe fn reserved_slice_mut(&mut self) -> &mut [u8] { + if self.core.opts.reserved() == 0 { + return &mut []; + } + + &mut self.core.arena.reserved_slice_mut()[HEADER_SIZE..] + } + + #[inline] + fn flush(&self) -> Result<(), Error> { + if self.ro { + return Err(error::Error::read_only()); + } + + self.core.arena.flush().map_err(Into::into) + } + + #[inline] + fn flush_async(&self) -> Result<(), Error> { + if self.ro { + return Err(error::Error::read_only()); + } + + self.core.arena.flush_async().map_err(Into::into) + } + + fn get_or_insert_with_value_builder( + &mut self, + key: &[u8], + vb: ValueBuilder) -> Result<(), E>>, + ) -> Result, Either> + where + C: Comparator + CheapClone, + S: Checksumer, + { + if self.read_only() { + return Err(Either::Right(Error::read_only())); + } + + self + .check( + key.len(), + vb.size() as usize, + self.maximum_key_size(), + self.maximum_value_size(), + ) + .map_err(Either::Right)?; + + if let Some(ent) = self.core.map.get(key) { + return Ok(Some(ent.as_value_slice())); + } + + self.insert_with_value_builder::(key, vb).map(|_| None) + } +} diff --git a/src/unsync/c.rs b/src/unsync/c.rs new file mode 100644 index 0000000..46037bb --- /dev/null +++ b/src/unsync/c.rs @@ -0,0 +1,42 @@ +use wal::sealed::{Base, WalCore}; + +use super::*; + +pub struct OrderWalCore { + pub(super) arena: Arena, + pub(super) map: BTreeSet>, + pub(super) opts: Options, + pub(super) cmp: C, + pub(super) cks: S, +} + +impl Base for BTreeSet> { + fn insert(&mut self, ele: Pointer) + where + C: Comparator, + { + BTreeSet::insert(self, ele); + } +} + +impl WalCore for OrderWalCore { + type Allocator = Arena; + type Base = BTreeSet>; + + #[inline] + fn construct( + arena: Arena, + set: BTreeSet>, + opts: Options, + cmp: C, + checksumer: S, + ) -> Self { + Self { + arena, + map: set, + cmp, + opts, + cks: checksumer, + } + } +} diff --git a/src/unsync/iter.rs b/src/unsync/iter.rs new file mode 100644 index 0000000..c0626da --- /dev/null +++ b/src/unsync/iter.rs @@ -0,0 +1,262 @@ +use core::iter::FusedIterator; +use std::collections::btree_set; + +use super::*; + +/// Iterator over the entries in the WAL. +pub struct Iter<'a, C> { + iter: btree_set::Iter<'a, Pointer>, +} + +impl<'a, C> Iter<'a, C> { + #[inline] + pub(super) fn new(iter: btree_set::Iter<'a, Pointer>) -> Self { + Self { iter } + } +} + +impl<'a, C> Iterator for Iter<'a, C> { + type Item = (&'a [u8], &'a [u8]); + + #[inline] + fn next(&mut self) -> Option { + self.iter.next().map(|ptr| { + let k = ptr.as_key_slice(); + let v = ptr.as_value_slice(); + (k, v) + }) + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.iter.size_hint() + } +} + +impl<'a, C> DoubleEndedIterator for Iter<'a, C> { + #[inline] + fn next_back(&mut self) -> Option { + self.iter.next_back().map(|ptr| { + let k = ptr.as_key_slice(); + let v = ptr.as_value_slice(); + (k, v) + }) + } +} + +impl<'a, C> FusedIterator for Iter<'a, C> {} + +/// Iterator over the keys in the WAL. +pub struct Keys<'a, C> { + iter: btree_set::Iter<'a, Pointer>, +} + +impl<'a, C> Keys<'a, C> { + #[inline] + pub(super) fn new(iter: btree_set::Iter<'a, Pointer>) -> Self { + Self { iter } + } +} + +impl<'a, C> Iterator for Keys<'a, C> { + type Item = &'a [u8]; + + #[inline] + fn next(&mut self) -> Option { + self.iter.next().map(|ptr| ptr.as_key_slice()) + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.iter.size_hint() + } +} + +impl<'a, C> DoubleEndedIterator for Keys<'a, C> { + #[inline] + fn next_back(&mut self) -> Option { + self.iter.next_back().map(|ptr| ptr.as_key_slice()) + } +} + +impl<'a, C> FusedIterator for Keys<'a, C> {} + +/// Iterator over the values in the WAL. +pub struct Values<'a, C> { + iter: btree_set::Iter<'a, Pointer>, +} + +impl<'a, C> Values<'a, C> { + #[inline] + pub(super) fn new(iter: btree_set::Iter<'a, Pointer>) -> Self { + Self { iter } + } +} + +impl<'a, C> Iterator for Values<'a, C> { + type Item = &'a [u8]; + + #[inline] + fn next(&mut self) -> Option { + self.iter.next().map(|ptr| ptr.as_value_slice()) + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.iter.size_hint() + } +} + +impl<'a, C> DoubleEndedIterator for Values<'a, C> { + #[inline] + fn next_back(&mut self) -> Option { + self.iter.next_back().map(|ptr| ptr.as_value_slice()) + } +} + +impl<'a, C> FusedIterator for Values<'a, C> {} + +/// An iterator over a subset of the entries in the WAL. +pub struct Range<'a, C> +where + C: Comparator, +{ + iter: btree_set::Range<'a, Pointer>, +} + +impl<'a, C> Range<'a, C> +where + C: Comparator, +{ + #[inline] + pub(super) fn new(iter: btree_set::Range<'a, Pointer>) -> Self { + Self { iter } + } +} + +impl<'a, C> Iterator for Range<'a, C> +where + C: Comparator, +{ + type Item = (&'a [u8], &'a [u8]); + + #[inline] + fn next(&mut self) -> Option { + self + .iter + .next() + .map(|ptr| (ptr.as_key_slice(), ptr.as_value_slice())) + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.iter.size_hint() + } +} + +impl<'a, C> DoubleEndedIterator for Range<'a, C> +where + C: Comparator, +{ + #[inline] + fn next_back(&mut self) -> Option { + self + .iter + .next_back() + .map(|ptr| (ptr.as_key_slice(), ptr.as_value_slice())) + } +} + +impl<'a, C> FusedIterator for Range<'a, C> where C: Comparator {} + +/// An iterator over the keys in a subset of the entries in the WAL. +pub struct RangeKeys<'a, C> +where + C: Comparator, +{ + iter: btree_set::Range<'a, Pointer>, +} + +impl<'a, C> RangeKeys<'a, C> +where + C: Comparator, +{ + #[inline] + pub(super) fn new(iter: btree_set::Range<'a, Pointer>) -> Self { + Self { iter } + } +} + +impl<'a, C> Iterator for RangeKeys<'a, C> +where + C: Comparator, +{ + type Item = &'a [u8]; + + #[inline] + fn next(&mut self) -> Option { + self.iter.next().map(|ptr| ptr.as_key_slice()) + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.iter.size_hint() + } +} + +impl<'a, C> DoubleEndedIterator for RangeKeys<'a, C> +where + C: Comparator, +{ + #[inline] + fn next_back(&mut self) -> Option { + self.iter.next_back().map(|ptr| ptr.as_key_slice()) + } +} + +impl<'a, C> FusedIterator for RangeKeys<'a, C> where C: Comparator {} + +/// An iterator over the values in a subset of the entries in the WAL. +pub struct RangeValues<'a, C> +where + C: Comparator, +{ + iter: btree_set::Range<'a, Pointer>, +} + +impl<'a, C> RangeValues<'a, C> +where + C: Comparator, +{ + #[inline] + pub(super) fn new(iter: btree_set::Range<'a, Pointer>) -> Self { + Self { iter } + } +} + +impl<'a, C> Iterator for RangeValues<'a, C> +where + C: Comparator, +{ + type Item = &'a [u8]; + + #[inline] + fn next(&mut self) -> Option { + self.iter.next().map(|ptr| ptr.as_value_slice()) + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.iter.size_hint() + } +} + +impl<'a, C> DoubleEndedIterator for RangeValues<'a, C> +where + C: Comparator, +{ + #[inline] + fn next_back(&mut self) -> Option { + self.iter.next_back().map(|ptr| ptr.as_value_slice()) + } +} diff --git a/src/unsync/tests.rs b/src/unsync/tests.rs new file mode 100644 index 0000000..f43f424 --- /dev/null +++ b/src/unsync/tests.rs @@ -0,0 +1,35 @@ +use crate::tests::*; + +use super::*; + +#[test] +fn test_construct_inmemory() { + construct_inmemory::>(); +} + +#[test] +fn test_construct_map_anon() { + construct_map_anon::>(); +} + +#[test] +#[cfg_attr(miri, ignore)] +fn test_construct_map_file() { + construct_map_file::>("swmr"); +} + +#[test] +fn test_construct_with_small_capacity_inmemory() { + construct_with_small_capacity_inmemory::>(); +} + +#[test] +fn test_construct_with_small_capacity_map_anon() { + construct_with_small_capacity_map_anon::>(); +} + +#[test] +#[cfg_attr(miri, ignore)] +fn test_construct_with_small_capacity_map_file() { + construct_with_small_capacity_map_file::>("swmr"); +} diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..845dfbd --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,75 @@ +use dbutils::leb128; + +use super::*; + +#[inline] +pub(crate) const fn merge_lengths(klen: u32, vlen: u32) -> u64 { + (klen as u64) << 32 | vlen as u64 +} + +#[inline] +pub(crate) const fn split_lengths(len: u64) -> (u32, u32) { + ((len >> 32) as u32, len as u32) +} + +/// - The first `usize` is the length of the encoded `klen + vlen` +/// - The second `u64` is encoded `klen + vlen` +/// - The third `u32` is the full entry size +#[inline] +pub(crate) const fn entry_size(key_len: u32, value_len: u32) -> (usize, u64, u32) { + let len = merge_lengths(key_len, value_len); + let len_size = leb128::encoded_len_varint(len); + let elen = STATUS_SIZE as u32 + len_size as u32 + key_len + value_len + CHECKSUM_SIZE as u32; + + (len_size, len, elen) +} + +#[inline] +pub(crate) const fn arena_options(reserved: u32) -> ArenaOptions { + ArenaOptions::new() + .with_magic_version(CURRENT_VERSION) + .with_freelist(Freelist::None) + .with_reserved((HEADER_SIZE + reserved as usize) as u32) + // clear capacity + .with_capacity(0) + .with_unify(true) +} + +#[inline] +pub(crate) const fn min_u64(a: u64, b: u64) -> u64 { + if a < b { + a + } else { + b + } +} + +#[inline] +pub(crate) const fn check( + klen: usize, + vlen: usize, + max_key_size: u32, + max_value_size: u32, +) -> Result<(), error::Error> { + let max_ksize = min_u64(max_key_size as u64, u32::MAX as u64); + let max_vsize = min_u64(max_value_size as u64, u32::MAX as u64); + + if max_ksize < klen as u64 { + return Err(error::Error::key_too_large(klen as u32, max_key_size)); + } + + if max_vsize < vlen as u64 { + return Err(error::Error::value_too_large(vlen as u32, max_value_size)); + } + + let (_, _, elen) = entry_size(klen as u32, vlen as u32); + + if elen == u32::MAX { + return Err(error::Error::entry_too_large( + elen as u64, + min_u64(max_key_size as u64 + max_value_size as u64, u32::MAX as u64), + )); + } + + Ok(()) +} diff --git a/src/wal.rs b/src/wal.rs new file mode 100644 index 0000000..aa13292 --- /dev/null +++ b/src/wal.rs @@ -0,0 +1,465 @@ +use core::ops::RangeBounds; + +use rarena_allocator::Error as ArenaError; + +use super::*; + +mod builder; +pub use builder::*; + +pub(crate) mod sealed; + +pub trait ImmutableWal: sealed::Constructor { + /// The iterator type. + type Iter<'a>: Iterator + where + Self: 'a, + C: Comparator; + + /// The iterator type over a subset of entries in the WAL. + type Range<'a, Q, R>: Iterator + where + R: RangeBounds, + [u8]: Borrow, + Q: Ord + ?Sized, + Self: 'a, + C: Comparator; + + /// The keys iterator type. + type Keys<'a>: Iterator + where + Self: 'a, + C: Comparator; + + /// The iterator type over a subset of keys in the WAL. + type RangeKeys<'a, Q, R>: Iterator + where + R: RangeBounds, + [u8]: Borrow, + Q: Ord + ?Sized, + Self: 'a, + C: Comparator; + + /// The values iterator type. + type Values<'a>: Iterator + where + Self: 'a, + C: Comparator; + + /// The iterator type over a subset of values in the WAL. + type RangeValues<'a, Q, R>: Iterator + where + R: RangeBounds, + [u8]: Borrow, + Q: Ord + ?Sized, + Self: 'a, + C: Comparator; + + /// Returns the reserved space in the WAL. + /// + /// # Safety + /// - The writer must ensure that the returned slice is not modified. + /// - This method is not thread-safe, so be careful when using it. + unsafe fn reserved_slice(&self) -> &[u8]; + + /// Returns `true` if this WAL instance is read-only. + fn read_only(&self) -> bool; + + /// Returns the number of entries in the WAL. + fn len(&self) -> usize; + + /// Returns `true` if the WAL is empty. + fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Returns the maximum key size allowed in the WAL. + fn maximum_key_size(&self) -> u32; + + /// Returns the maximum value size allowed in the WAL. + fn maximum_value_size(&self) -> u32; + + /// Returns `true` if the WAL contains the specified key. + fn contains_key(&self, key: &Q) -> bool + where + [u8]: Borrow, + Q: ?Sized + Ord, + C: Comparator; + + /// Returns an iterator over the entries in the WAL. + fn iter(&self) -> Self::Iter<'_> + where + C: Comparator; + + /// Returns an iterator over a subset of entries in the WAL. + fn range(&self, range: R) -> Self::Range<'_, Q, R> + where + R: RangeBounds, + [u8]: Borrow, + Q: Ord + ?Sized, + C: Comparator; + + /// Returns an iterator over the keys in the WAL. + fn keys(&self) -> Self::Keys<'_> + where + C: Comparator; + + /// Returns an iterator over a subset of keys in the WAL. + fn range_keys(&self, range: R) -> Self::RangeKeys<'_, Q, R> + where + R: RangeBounds, + [u8]: Borrow, + Q: Ord + ?Sized, + C: Comparator; + + /// Returns an iterator over the values in the WAL. + fn values(&self) -> Self::Values<'_> + where + C: Comparator; + + /// Returns an iterator over a subset of values in the WAL. + fn range_values(&self, range: R) -> Self::RangeValues<'_, Q, R> + where + R: RangeBounds, + [u8]: Borrow, + Q: Ord + ?Sized, + C: Comparator; + + /// Returns the first key-value pair in the map. The key in this pair is the minimum key in the wal. + fn first(&self) -> Option<(&[u8], &[u8])> + where + C: Comparator; + + /// Returns the last key-value pair in the map. The key in this pair is the maximum key in the wal. + fn last(&self) -> Option<(&[u8], &[u8])> + where + C: Comparator; + + /// Returns the value associated with the key. + fn get(&self, key: &Q) -> Option<&[u8]> + where + [u8]: Borrow, + Q: ?Sized + Ord, + C: Comparator; +} + +/// An abstract layer for the write-ahead log. +pub trait Wal: sealed::Sealed + ImmutableWal { + /// The read only reader type for this wal. + type Reader: ImmutableWal; + + /// Creates a new in-memory write-ahead log backed by an aligned vec. + /// + /// # Example + /// + /// ```rust + /// use orderwal::{swmr::OrderWal, Builder, Options, Wal}; + /// + /// let wal = OrderWal::new(Builder::new().with_capacity(1024)).unwrap(); + /// ``` + fn new(b: Builder) -> Result { + let Builder { opts, cmp, cks } = b; + let arena = ::new( + arena_options(opts.reserved()).with_capacity(opts.capacity()), + ) + .map_err(|e| match e { + ArenaError::InsufficientSpace { + requested, + available, + } => Error::insufficient_space(requested, available), + _ => unreachable!(), + })?; + >::new_in(arena, opts, cmp, cks) + .map(|core| Self::from_core(core, false)) + } + + /// Creates a new in-memory write-ahead log but backed by an anonymous mmap. + /// + /// # Example + /// + /// ```rust + /// use orderwal::{swmr::OrderWal, Builder, Wal}; + /// + /// let wal = OrderWal::map_anon(Builder::new().with_capacity(1024)).unwrap(); + /// ``` + fn map_anon(b: Builder) -> Result { + let Builder { opts, cmp, cks } = b; + let mmap_opts = MmapOptions::new().len(opts.capacity()).huge(opts.huge()); + ::map_anon(arena_options(opts.reserved()), mmap_opts) + .map_err(Into::into) + .and_then(|arena| { + >::new_in(arena, opts, cmp, cks) + .map(|core| Self::from_core(core, false)) + }) + } + + /// Opens a write-ahead log backed by a file backed memory map in read-only mode. + fn map

(path: P, b: Builder) -> Result + where + C: Comparator + CheapClone, + S: Checksumer, + P: AsRef, + { + >::map_with_path_builder::<_, ()>(|| Ok(path.as_ref().to_path_buf()), b) + .map_err(|e| e.unwrap_right()) + } + + /// Opens a write-ahead log backed by a file backed memory map in read-only mode. + fn map_with_path_builder( + path_builder: PB, + b: Builder, + ) -> Result> + where + PB: FnOnce() -> Result, + C: Comparator + CheapClone, + S: Checksumer, + { + let open_options = OpenOptions::default().read(true); + + let Builder { opts, cmp, cks } = b; + + <>::Allocator as Allocator>::map_with_path_builder( + path_builder, + arena_options(opts.reserved()), + open_options, + MmapOptions::new(), + ) + .map_err(|e| e.map_right(Into::into)) + .and_then(|arena| { + >::replay(arena, Options::new(), true, cmp, cks) + .map(|core| >::from_core(core, true)) + .map_err(Either::Right) + }) + } + + /// Opens a write-ahead log backed by a file backed memory map. + fn map_mut

(path: P, b: Builder, open_opts: OpenOptions) -> Result + where + C: Comparator + CheapClone, + S: Checksumer, + P: AsRef, + { + >::map_mut_with_path_builder::<_, ()>( + || Ok(path.as_ref().to_path_buf()), + b, + open_opts, + ) + .map_err(|e| e.unwrap_right()) + } + + /// Opens a write-ahead log backed by a file backed memory map. + fn map_mut_with_path_builder( + path_builder: PB, + b: Builder, + open_options: OpenOptions, + ) -> Result> + where + PB: FnOnce() -> Result, + C: Comparator + CheapClone, + S: Checksumer, + { + let path = path_builder().map_err(Either::Left)?; + + let exist = path.exists(); + + let Builder { opts, cmp, cks } = b; + + ::map_mut( + path, + arena_options(opts.reserved()), + open_options, + MmapOptions::new(), + ) + .map_err(Into::into) + .and_then(|arena| { + if !exist { + >::new_in(arena, opts, cmp, cks) + .map(|core| Self::from_core(core, false)) + } else { + >::replay(arena, opts, false, cmp, cks) + .map(|core| Self::from_core(core, false)) + } + }) + .map_err(Either::Right) + } + + /// Returns the mutable reference to the reserved slice. + /// + /// # Safety + /// - The caller must ensure that the there is no others accessing reserved slice for either read or write. + /// - This method is not thread-safe, so be careful when using it. + unsafe fn reserved_slice_mut(&mut self) -> &mut [u8]; + + /// Flushes the to disk. + fn flush(&self) -> Result<(), Error>; + + /// Flushes the to disk. + fn flush_async(&self) -> Result<(), Error>; + + /// Get or insert a new entry into the WAL. + fn get_or_insert(&mut self, key: &[u8], value: &[u8]) -> Result, Error> + where + C: Comparator + CheapClone, + S: Checksumer, + { + self + .get_or_insert_with_value_builder::<()>( + key, + ValueBuilder::new(value.len() as u32, |buf| { + buf.write(value).unwrap(); + Ok(()) + }), + ) + .map_err(|e| e.unwrap_right()) + } + + /// Get or insert a new entry into the WAL. + fn get_or_insert_with_value_builder( + &mut self, + key: &[u8], + vb: ValueBuilder) -> Result<(), E>>, + ) -> Result, Either> + where + C: Comparator + CheapClone, + S: Checksumer; + + /// Inserts a key-value pair into the WAL. This method + /// allows the caller to build the key in place. + /// + /// See also [`insert_with_value_builder`](Wal::insert_with_value_builder) and [`insert_with_builders`](Wal::insert_with_builders). + fn insert_with_key_builder( + &mut self, + kb: KeyBuilder) -> Result<(), E>>, + value: &[u8], + ) -> Result<(), Either> + where + C: Comparator + CheapClone, + S: Checksumer, + { + if self.read_only() { + return Err(Either::Right(Error::read_only())); + } + + self + .check( + kb.size() as usize, + value.len(), + self.maximum_key_size(), + self.maximum_value_size(), + ) + .map_err(Either::Right)?; + + self + .insert_with_in::( + kb, + ValueBuilder::new(value.len() as u32, |buf| { + buf.write(value).unwrap(); + Ok(()) + }), + ) + .map_err(|e| match e { + Among::Left(e) => Either::Left(e), + Among::Middle(_) => unreachable!(), + Among::Right(e) => Either::Right(e), + }) + } + + /// Inserts a key-value pair into the WAL. This method + /// allows the caller to build the value in place. + /// + /// See also [`insert_with_key_builder`](Wal::insert_with_key_builder) and [`insert_with_builders`](Wal::insert_with_builders). + fn insert_with_value_builder( + &mut self, + key: &[u8], + vb: ValueBuilder) -> Result<(), E>>, + ) -> Result<(), Either> + where + C: Comparator + CheapClone, + S: Checksumer, + { + if self.read_only() { + return Err(Either::Right(Error::read_only())); + } + + self + .check( + key.len(), + vb.size() as usize, + self.maximum_key_size(), + self.maximum_value_size(), + ) + .map_err(Either::Right)?; + + self + .insert_with_in::<(), E>( + KeyBuilder::new(key.len() as u32, |buf| { + buf.write(key).unwrap(); + Ok(()) + }), + vb, + ) + .map_err(|e| match e { + Among::Left(_) => unreachable!(), + Among::Middle(e) => Either::Left(e), + Among::Right(e) => Either::Right(e), + }) + } + + /// Inserts a key-value pair into the WAL. This method + /// allows the caller to build the key and value in place. + fn insert_with_builders( + &mut self, + kb: KeyBuilder) -> Result<(), KE>>, + vb: ValueBuilder) -> Result<(), VE>>, + ) -> Result<(), Among> + where + C: Comparator + CheapClone, + S: Checksumer, + { + if self.read_only() { + return Err(Among::Right(Error::read_only())); + } + + self + .check( + kb.size() as usize, + vb.size() as usize, + self.maximum_key_size(), + self.maximum_value_size(), + ) + .map_err(Among::Right)?; + + self.insert_with_in(kb, vb) + } + + /// Inserts a key-value pair into the WAL. + fn insert(&mut self, key: &[u8], value: &[u8]) -> Result<(), Error> + where + C: Comparator + CheapClone, + S: Checksumer, + { + if self.read_only() { + return Err(Error::read_only()); + } + + self.check( + key.len(), + value.len(), + self.maximum_key_size(), + self.maximum_value_size(), + )?; + + self + .insert_with_in::<(), ()>( + KeyBuilder::new(key.len() as u32, |buf| { + buf.write(key).unwrap(); + Ok(()) + }), + ValueBuilder::new(value.len() as u32, |buf| { + buf.write(value).unwrap(); + Ok(()) + }), + ) + .map_err(Among::unwrap_right) + } +} diff --git a/src/wal/builder.rs b/src/wal/builder.rs new file mode 100644 index 0000000..a77b1ab --- /dev/null +++ b/src/wal/builder.rs @@ -0,0 +1,328 @@ +use super::*; + +/// A write-ahead log builder. +pub struct Builder { + pub(super) opts: Options, + pub(super) cmp: C, + pub(super) cks: S, +} + +impl Default for Builder { + #[inline] + fn default() -> Self { + Self::new() + } +} + +impl Builder { + /// Returns a new write-ahead log builder with the given options. + #[inline] + pub fn new() -> Self { + Self { + opts: Options::default(), + cmp: Ascend, + cks: Crc32::default(), + } + } +} + +impl Builder { + /// Returns a new write-ahead log builder with the new comparator + #[inline] + pub fn with_comparator(self, cmp: NC) -> Builder { + Builder { + opts: self.opts, + cmp, + cks: self.cks, + } + } + + /// Returns a new write-ahead log builder with the new checksumer + #[inline] + pub fn with_checksumer(self, cks: NS) -> Builder { + Builder { + opts: self.opts, + cmp: self.cmp, + cks, + } + } + + /// Returns a new write-ahead log builder with the new options + #[inline] + pub fn with_options(self, opts: Options) -> Self { + Builder { + opts, + cmp: self.cmp, + cks: self.cks, + } + } + + /// Set the reserved bytes of the WAL. + /// + /// The `reserved` is used to configure the start position of the WAL. This is useful + /// when you want to add some bytes as your own WAL's header. + /// + /// The default reserved is `0`. + /// + /// # Example + /// + /// ```rust + /// use orderwal::Builder; + /// + /// let opts = Builder::new().with_reserved(8); + /// ``` + #[inline] + pub const fn with_reserved(mut self, reserved: u32) -> Self { + self.opts = self.opts.with_reserved(reserved); + self + } + + /// Get the reserved of the WAL. + /// + /// The `reserved` is used to configure the start position of the WAL. This is useful + /// when you want to add some bytes as your own WAL's header. + /// + /// The default reserved is `0`. + /// + /// # Example + /// + /// ```rust + /// use orderwal::Builder; + /// + /// let opts = Builder::new().with_reserved(8); + /// + /// assert_eq!(opts.reserved(), 8); + /// ``` + #[inline] + pub const fn reserved(&self) -> u32 { + self.opts.reserved() + } + + /// Returns the magic version. + /// + /// The default value is `0`. + /// + /// # Example + /// + /// ```rust + /// use orderwal::Builder; + /// + /// let options = Builder::new().with_magic_version(1); + /// assert_eq!(options.magic_version(), 1); + /// ``` + #[inline] + pub const fn magic_version(&self) -> u16 { + self.opts.magic_version() + } + + /// Returns the capacity of the WAL. + /// + /// The default value is `0`. + /// + /// # Example + /// + /// ```rust + /// use orderwal::Builder; + /// + /// let options = Builder::new().with_capacity(1000); + /// assert_eq!(options.capacity(), 1000); + /// ``` + #[inline] + pub const fn capacity(&self) -> u32 { + self.opts.capacity() + } + + /// Returns the maximum key length. + /// + /// The default value is `u16::MAX`. + /// + /// # Example + /// + /// ```rust + /// use orderwal::Builder; + /// + /// let options = Builder::new().with_maximum_key_size(1024); + /// assert_eq!(options.maximum_key_size(), 1024); + /// ``` + #[inline] + pub const fn maximum_key_size(&self) -> u32 { + self.opts.maximum_key_size() + } + + /// Returns the maximum value length. + /// + /// The default value is `u32::MAX`. + /// + /// # Example + /// + /// ```rust + /// use orderwal::Builder; + /// + /// let options = Builder::new().with_maximum_value_size(1024); + /// assert_eq!(options.maximum_value_size(), 1024); + /// ``` + #[inline] + pub const fn maximum_value_size(&self) -> u32 { + self.opts.maximum_value_size() + } + + /// Returns `true` if the WAL syncs on write. + /// + /// The default value is `true`. + /// + /// # Example + /// + /// ```rust + /// use orderwal::Builder; + /// + /// let options = Builder::new(); + /// assert_eq!(options.sync_on_write(), true); + /// ``` + #[inline] + pub const fn sync_on_write(&self) -> bool { + self.opts.sync_on_write() + } + + /// Returns the bits of the page size. + /// + /// Configures the anonymous memory map to be allocated using huge pages. + /// + /// This option corresponds to the `MAP_HUGETLB` flag on Linux. It has no effect on Windows. + /// + /// The size of the requested page can be specified in page bits. + /// If not provided, the system default is requested. + /// The requested length should be a multiple of this, or the mapping will fail. + /// + /// This option has no effect on file-backed memory maps. + /// + /// The default value is `None`. + /// + /// # Example + /// + /// ```rust + /// use orderwal::Builder; + /// + /// let options = Builder::new().with_huge(64); + /// assert_eq!(options.huge(), Some(64)); + /// ``` + #[inline] + pub const fn huge(&self) -> Option { + self.opts.huge() + } + + /// Sets the capacity of the WAL. + /// + /// This configuration will be ignored when using file-backed memory maps. + /// + /// The default value is `0`. + /// + /// # Example + /// + /// ```rust + /// use orderwal::Builder; + /// + /// let options = Builder::new().with_capacity(100); + /// assert_eq!(options.capacity(), 100); + /// ``` + #[inline] + pub const fn with_capacity(mut self, cap: u32) -> Self { + self.opts = self.opts.with_capacity(cap); + self + } + + /// Sets the maximum key length. + /// + /// # Example + /// + /// ```rust + /// use orderwal::Builder; + /// + /// let options = Builder::new().with_maximum_key_size(1024); + /// assert_eq!(options.maximum_key_size(), 1024); + /// ``` + #[inline] + pub const fn with_maximum_key_size(mut self, size: u32) -> Self { + self.opts = self.opts.with_maximum_key_size(size); + self + } + + /// Sets the maximum value length. + /// + /// # Example + /// + /// ```rust + /// use orderwal::Builder; + /// + /// let options = Builder::new().with_maximum_value_size(1024); + /// assert_eq!(options.maximum_value_size(), 1024); + /// ``` + #[inline] + pub const fn with_maximum_value_size(mut self, size: u32) -> Self { + self.opts = self.opts.with_maximum_value_size(size); + self + } + + /// Returns the bits of the page size. + /// + /// Configures the anonymous memory map to be allocated using huge pages. + /// + /// This option corresponds to the `MAP_HUGETLB` flag on Linux. It has no effect on Windows. + /// + /// The size of the requested page can be specified in page bits. + /// If not provided, the system default is requested. + /// The requested length should be a multiple of this, or the mapping will fail. + /// + /// This option has no effect on file-backed memory maps. + /// + /// The default value is `None`. + /// + /// # Example + /// + /// ```rust + /// use orderwal::Builder; + /// + /// let options = Builder::new().with_huge(64); + /// assert_eq!(options.huge(), Some(64)); + /// ``` + #[inline] + pub const fn with_huge(mut self, page_bits: u8) -> Self { + self.opts = self.opts.with_huge(page_bits); + self + } + + /// Sets the WAL to sync on write. + /// + /// The default value is `true`. + /// + /// # Example + /// + /// ```rust + /// use orderwal::Builder; + /// + /// let options = Builder::new().with_sync_on_write(false); + /// assert_eq!(options.sync_on_write(), false); + /// ``` + #[inline] + pub const fn with_sync_on_write(mut self, sync: bool) -> Self { + self.opts = self.opts.with_sync_on_write(sync); + self + } + + /// Sets the magic version. + /// + /// The default value is `0`. + /// + /// # Example + /// + /// ```rust + /// use orderwal::Builder; + /// + /// let options = Builder::new().with_magic_version(1); + /// assert_eq!(options.magic_version(), 1); + /// ``` + #[inline] + pub const fn with_magic_version(mut self, version: u16) -> Self { + self.opts = self.opts.with_magic_version(version); + self + } +} diff --git a/src/wal/sealed.rs b/src/wal/sealed.rs new file mode 100644 index 0000000..7ab2c61 --- /dev/null +++ b/src/wal/sealed.rs @@ -0,0 +1,153 @@ +use rarena_allocator::ArenaPosition; + +use super::*; + +pub trait Base: Default { + fn insert(&mut self, ele: Pointer) + where + C: Comparator; +} + +pub trait WalCore { + type Allocator: Allocator; + type Base: Base; + + fn construct(arena: Self::Allocator, base: Self::Base, opts: Options, cmp: C, cks: S) -> Self; +} + +pub trait Sealed: Constructor { + fn check( + &self, + klen: usize, + vlen: usize, + max_key_size: u32, + max_value_size: u32, + ) -> Result<(), Error> { + crate::check(klen, vlen, max_key_size, max_value_size) + } + + fn insert_with_in( + &mut self, + kb: KeyBuilder) -> Result<(), KE>>, + vb: ValueBuilder) -> Result<(), VE>>, + ) -> Result<(), Among> + where + C: Comparator + CheapClone, + S: Checksumer; +} + +pub trait Constructor: Sized { + type Allocator: Allocator; + type Core: WalCore; + + fn new_in(arena: Self::Allocator, opts: Options, cmp: C, cks: S) -> Result { + unsafe { + let slice = arena.reserved_slice_mut(); + slice[0..6].copy_from_slice(&MAGIC_TEXT); + slice[6..8].copy_from_slice(&opts.magic_version().to_le_bytes()); + } + + arena + .flush_range(0, HEADER_SIZE) + .map(|_| >::construct(arena, Default::default(), opts, cmp, cks)) + .map_err(Into::into) + } + + fn replay( + arena: Self::Allocator, + opts: Options, + ro: bool, + cmp: C, + checksumer: S, + ) -> Result + where + C: Comparator + CheapClone, + S: Checksumer, + { + let slice = arena.reserved_slice(); + let magic_text = &slice[0..6]; + let magic_version = u16::from_le_bytes(slice[6..8].try_into().unwrap()); + + if magic_text != MAGIC_TEXT { + return Err(Error::magic_text_mismatch()); + } + + if magic_version != opts.magic_version() { + return Err(Error::magic_version_mismatch()); + } + + let mut set = >::Base::default(); + + let mut cursor = arena.data_offset(); + let allocated = arena.allocated(); + + loop { + unsafe { + // we reached the end of the arena, if we have any remaining, then if means two possibilities: + // 1. the remaining is a partial entry, but it does not be persisted to the disk, so following the write-ahead log principle, we should discard it. + // 2. our file may be corrupted, so we discard the remaining. + if cursor + STATUS_SIZE > allocated { + if !ro && cursor < allocated { + arena.rewind(ArenaPosition::Start(cursor as u32)); + arena.flush()?; + } + break; + } + + let header = arena.get_u8(cursor).unwrap(); + let flag = Flags::from_bits_unchecked(header); + + let (kvsize, encoded_len) = arena.get_u64_varint(cursor + STATUS_SIZE).map_err(|_e| { + #[cfg(feature = "tracing")] + tracing::error!(err=%_e); + + Error::corrupted() + })?; + + let (key_len, value_len) = split_lengths(encoded_len); + let key_len = key_len as usize; + let value_len = value_len as usize; + // Same as above, if we reached the end of the arena, we should discard the remaining. + let cks_offset = STATUS_SIZE + kvsize + key_len + value_len; + if cks_offset + CHECKSUM_SIZE > allocated { + if !ro { + arena.rewind(ArenaPosition::Start(cursor as u32)); + arena.flush()?; + } + + break; + } + + let cks = arena.get_u64_le(cursor + cks_offset).unwrap(); + + if cks != checksumer.checksum(arena.get_bytes(cursor, cks_offset)) { + return Err(Error::corrupted()); + } + + // If the entry is not committed, we should not rewind + if !flag.contains(Flags::COMMITTED) { + if !ro { + arena.rewind(ArenaPosition::Start(cursor as u32)); + arena.flush()?; + } + + break; + } + + set.insert(Pointer::new( + key_len, + value_len, + arena.get_pointer(cursor + STATUS_SIZE + kvsize), + cmp.cheap_clone(), + )); + cursor += cks_offset + CHECKSUM_SIZE; + } + } + + Ok(>::construct( + arena, set, opts, cmp, checksumer, + )) + } + + fn from_core(core: Self::Core, ro: bool) -> Self; +} diff --git a/tests/foo.rs b/tests/foo.rs deleted file mode 100644 index 8b13789..0000000 --- a/tests/foo.rs +++ /dev/null @@ -1 +0,0 @@ -