Compare commits
No commits in common. "main" and "v0.1.0" have entirely different histories.
1
.gitignore
vendored
1
.gitignore
vendored
@ -2,4 +2,3 @@
|
|||||||
Cargo.lock
|
Cargo.lock
|
||||||
|
|
||||||
.idea
|
.idea
|
||||||
.direnv
|
|
15
Cargo.toml
15
Cargo.toml
@ -1,10 +1,10 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "fancy_mdbx"
|
name = "fancy_mdbx"
|
||||||
description = "Fancy wrapper for libMDBX"
|
description = "Fancy wrapper for libMDBX"
|
||||||
repository = "https://git.emunest.net/reivilibre/fancy_mdbx.git"
|
repository = "https://bics.ga/reivilibre/fancy_mdbx.git"
|
||||||
authors = ["Olivier 'reivilibre'"]
|
authors = ["Olivier 'reivilibre'"]
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
version = "0.3.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
@ -16,13 +16,16 @@ log = "0.4.16"
|
|||||||
|
|
||||||
thiserror = "1.0.30"
|
thiserror = "1.0.30"
|
||||||
|
|
||||||
libmdbx = "0.3.3"
|
libmdbx = "0.1.1"
|
||||||
|
|
||||||
|
# 0.11.1+zstd.1.5.2
|
||||||
|
zstd = "0.11.1"
|
||||||
|
|
||||||
|
serde = "1.0.136"
|
||||||
|
serde_bare = "0.5.0"
|
||||||
|
|
||||||
ouroboros = "0.14.2"
|
ouroboros = "0.14.2"
|
||||||
|
|
||||||
byte_lamination = { version = "0.1.1", features = ["bare", "cbor", "zstd"] }
|
|
||||||
|
|
||||||
serde = { version = "1.0.162", features = ["derive"] }
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempfile = "3.3.0"
|
tempfile = "3.3.0"
|
||||||
|
58
flake.lock
generated
58
flake.lock
generated
@ -1,58 +0,0 @@
|
|||||||
{
|
|
||||||
"nodes": {
|
|
||||||
"nixpkgs": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1677075010,
|
|
||||||
"narHash": "sha256-X+UmR1AkdR//lPVcShmLy8p1n857IGf7y+cyCArp8bU=",
|
|
||||||
"path": "/nix/store/b1vy558z7lxph5mbg7n50b5njp393ia9-source",
|
|
||||||
"rev": "c95bf18beba4290af25c60cbaaceea1110d0f727",
|
|
||||||
"type": "path"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"id": "nixpkgs",
|
|
||||||
"type": "indirect"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"root": {
|
|
||||||
"inputs": {
|
|
||||||
"nixpkgs": "nixpkgs",
|
|
||||||
"utils": "utils"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"systems": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1681028828,
|
|
||||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default",
|
|
||||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"utils": {
|
|
||||||
"inputs": {
|
|
||||||
"systems": "systems"
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1681202837,
|
|
||||||
"narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=",
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "flake-utils",
|
|
||||||
"rev": "cfacdce06f30d2b68473a46042957675eebb3401",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "flake-utils",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"root": "root",
|
|
||||||
"version": 7
|
|
||||||
}
|
|
45
flake.nix
45
flake.nix
@ -1,45 +0,0 @@
|
|||||||
{
|
|
||||||
description = "fancy_mdbx";
|
|
||||||
|
|
||||||
inputs = {
|
|
||||||
utils.url = "github:numtide/flake-utils";
|
|
||||||
};
|
|
||||||
|
|
||||||
outputs = { self, nixpkgs, utils }:
|
|
||||||
utils.lib.eachDefaultSystem (system: let
|
|
||||||
pkgs = nixpkgs.legacyPackages."${system}";
|
|
||||||
in rec {
|
|
||||||
# `nix develop`
|
|
||||||
devShell = pkgs.mkShell {
|
|
||||||
nativeBuildInputs =
|
|
||||||
let
|
|
||||||
rust-toolchain = pkgs.symlinkJoin {
|
|
||||||
name = "rust-toolchain";
|
|
||||||
paths = with pkgs; [rustc cargo rustfmt rustPlatform.rustcSrc];
|
|
||||||
};
|
|
||||||
in
|
|
||||||
[
|
|
||||||
rust-toolchain
|
|
||||||
];
|
|
||||||
|
|
||||||
# Needed for bindgen when binding to mdbx
|
|
||||||
LIBCLANG_PATH="${pkgs.llvmPackages_latest.libclang.lib}/lib";
|
|
||||||
|
|
||||||
# Cargo culted:
|
|
||||||
# Add to rustc search path
|
|
||||||
RUSTFLAGS = (builtins.map (a: ''-L ${a}/lib'') [
|
|
||||||
]);
|
|
||||||
# Add to bindgen search path
|
|
||||||
BINDGEN_EXTRA_CLANG_ARGS =
|
|
||||||
# Includes with normal include path
|
|
||||||
(builtins.map (a: ''-I"${a}/include"'') [
|
|
||||||
# C standard library
|
|
||||||
pkgs.glibc.dev
|
|
||||||
])
|
|
||||||
# Includes with special directory paths
|
|
||||||
++ [
|
|
||||||
''-I"${pkgs.llvmPackages_latest.libclang.lib}/lib/clang/${pkgs.llvmPackages_latest.libclang.version}/include"''
|
|
||||||
];
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
@ -2,4 +2,4 @@ pub(crate) mod raw;
|
|||||||
pub(crate) mod wrapped;
|
pub(crate) mod wrapped;
|
||||||
|
|
||||||
pub use raw::RawTable;
|
pub use raw::RawTable;
|
||||||
pub use wrapped::Table;
|
pub use wrapped::WrappedTable;
|
58
src/database/raw.rs
Normal file
58
src/database/raw.rs
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
use crate::environment::Txn;
|
||||||
|
use crate::wrapper::ZeroCopyByteWrapper;
|
||||||
|
use libmdbx::{Database, Environment, TransactionKind, WriteFlags, WriteMap, RW};
|
||||||
|
use ouroboros::self_referencing;
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
#[self_referencing(pub_extras)]
|
||||||
|
pub struct RawTable<K: ?Sized, V: ?Sized> {
|
||||||
|
pub mdbx_env: Arc<Environment<WriteMap>>,
|
||||||
|
|
||||||
|
#[borrows(mdbx_env)]
|
||||||
|
#[covariant]
|
||||||
|
pub mdbx_db: Database<'this>,
|
||||||
|
|
||||||
|
pub(crate) phantom_k: PhantomData<K>,
|
||||||
|
pub(crate) phantom_v: PhantomData<V>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<K: ?Sized + ZeroCopyByteWrapper, V: ?Sized + ZeroCopyByteWrapper> RawTable<K, V> {
|
||||||
|
pub fn get<'txn, TK: TransactionKind>(
|
||||||
|
&'txn self,
|
||||||
|
txn: &'txn Txn<'txn, TK>,
|
||||||
|
k: impl AsRef<K>,
|
||||||
|
) -> anyhow::Result<Option<Cow<'txn, V>>> {
|
||||||
|
let bytes = txn
|
||||||
|
.mdbx_txn
|
||||||
|
.get::<Cow<'txn, [u8]>>(self.borrow_mdbx_db(), k.as_ref().as_byte_slice())?;
|
||||||
|
Ok(match bytes {
|
||||||
|
None => None,
|
||||||
|
Some(Cow::Owned(owned)) => Some(Cow::Owned(V::from_owned_bytes(owned)?)),
|
||||||
|
Some(Cow::Borrowed(borrowed)) => Some(Cow::Borrowed(V::from_byte_slice(borrowed)?)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn put<'txn>(
|
||||||
|
&self,
|
||||||
|
txn: &Txn<'txn, RW>,
|
||||||
|
k: impl AsRef<K>,
|
||||||
|
v: impl AsRef<V>,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
txn.mdbx_txn.put(
|
||||||
|
self.borrow_mdbx_db(),
|
||||||
|
k.as_ref().as_byte_slice(),
|
||||||
|
v.as_ref().as_byte_slice(),
|
||||||
|
WriteFlags::empty(),
|
||||||
|
)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if the key was present.
|
||||||
|
pub fn delete<'txn>(&self, txn: &Txn<'txn, RW>, k: impl AsRef<K>) -> anyhow::Result<bool> {
|
||||||
|
Ok(txn
|
||||||
|
.mdbx_txn
|
||||||
|
.del(self.borrow_mdbx_db(), k.as_ref().as_byte_slice(), None)?)
|
||||||
|
}
|
||||||
|
}
|
63
src/database/wrapped.rs
Normal file
63
src/database/wrapped.rs
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
use crate::database::raw::RawTable;
|
||||||
|
use crate::environment::Txn;
|
||||||
|
use crate::wrapper::ByteWrapper;
|
||||||
|
use anyhow::Context;
|
||||||
|
use libmdbx::{TransactionKind, RW};
|
||||||
|
use std::borrow::Borrow;
|
||||||
|
|
||||||
|
pub struct WrappedTable<K, V> {
|
||||||
|
pub raw: RawTable<[u8], [u8]>,
|
||||||
|
pub k_wrapper: K,
|
||||||
|
pub v_wrapper: V,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<K: ByteWrapper, V: ByteWrapper> WrappedTable<K, V> {
|
||||||
|
pub fn get<'txn, TK: TransactionKind>(
|
||||||
|
&self,
|
||||||
|
txn: &Txn<'txn, TK>,
|
||||||
|
k: impl Borrow<K::Item>,
|
||||||
|
) -> anyhow::Result<Option<V::Item>> {
|
||||||
|
let k_bytes = self
|
||||||
|
.k_wrapper
|
||||||
|
.dump_to_db_bytes(k.borrow())
|
||||||
|
.context("whilst converting key to bytes")?;
|
||||||
|
|
||||||
|
self.raw
|
||||||
|
.get(txn, k_bytes)?
|
||||||
|
.map(|v_bytes| self.v_wrapper.load_from_db_bytes(&v_bytes))
|
||||||
|
.transpose()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn put<'txn>(
|
||||||
|
&self,
|
||||||
|
txn: &Txn<'txn, RW>,
|
||||||
|
k: impl Borrow<K::Item>,
|
||||||
|
v: impl Borrow<V::Item>,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let k_bytes = self
|
||||||
|
.k_wrapper
|
||||||
|
.dump_to_db_bytes(k.borrow())
|
||||||
|
.context("whilst converting key to bytes")?;
|
||||||
|
let v_bytes = self
|
||||||
|
.v_wrapper
|
||||||
|
.dump_to_db_bytes(v.borrow())
|
||||||
|
.context("whilst converting value to bytes")?;
|
||||||
|
|
||||||
|
self.raw.put(txn, k_bytes, v_bytes)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if the key was present.
|
||||||
|
pub fn delete<'txn>(
|
||||||
|
&self,
|
||||||
|
txn: &Txn<'txn, RW>,
|
||||||
|
k: impl Borrow<K::Item>,
|
||||||
|
) -> anyhow::Result<bool> {
|
||||||
|
let k_bytes = self
|
||||||
|
.k_wrapper
|
||||||
|
.dump_to_db_bytes(k.borrow())
|
||||||
|
.context("whilst converting key to bytes")?;
|
||||||
|
|
||||||
|
self.raw.delete(txn, k_bytes)
|
||||||
|
}
|
||||||
|
}
|
@ -1,55 +1,47 @@
|
|||||||
|
use crate::database::raw::{RawTable, RawTableTryBuilder};
|
||||||
|
use crate::database::wrapped::WrappedTable;
|
||||||
|
use crate::wrapper::{ByteWrapper, ZeroCopyByteWrapper};
|
||||||
use anyhow::{ensure, Context};
|
use anyhow::{ensure, Context};
|
||||||
use libmdbx::{
|
use libmdbx::{
|
||||||
Database, DatabaseFlags, Geometry, TableFlags, Transaction, TransactionKind, WriteMap, RO, RW,
|
DatabaseFlags, Environment, EnvironmentFlags, Transaction, TransactionKind, WriteMap, RO, RW,
|
||||||
};
|
};
|
||||||
|
use std::marker::PhantomData;
|
||||||
use crate::table::{raw::RawTableTryBuilder, RawTable, Table};
|
|
||||||
use byte_lamination::ByteLamination;
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
pub struct Db {
|
pub struct Env {
|
||||||
pub mdbx_env: Arc<Database<WriteMap>>,
|
pub mdbx_env: Arc<Environment<WriteMap>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Db {
|
impl Env {
|
||||||
pub fn open(path: &Path) -> anyhow::Result<Self> {
|
pub fn open(path: &Path) -> anyhow::Result<Env> {
|
||||||
let mut flags = DatabaseFlags::default();
|
let mut flags = EnvironmentFlags::default();
|
||||||
flags.no_sub_dir = true;
|
flags.no_sub_dir = true;
|
||||||
|
|
||||||
// TODO make the geometry more configurable.
|
let environment = Environment::new()
|
||||||
let mut geom = Geometry::default();
|
.set_max_dbs(256)
|
||||||
// Don't stop the database growing until it hits 64 GiB.
|
|
||||||
// (The default is 1 MiB which is just not enough!)
|
|
||||||
geom.size = Some(1024 * 1024..64 * 1024 * 1024 * 1024);
|
|
||||||
|
|
||||||
// Grow 4 MiB at a time.
|
|
||||||
geom.growth_step = Some(4 * 1024 * 1024);
|
|
||||||
// Shrink 16 MiB at a time.
|
|
||||||
geom.shrink_threshold = Some(16 * 1024 * 1024);
|
|
||||||
// (Yes these numbers represent a large database).
|
|
||||||
|
|
||||||
let environment = Database::new()
|
|
||||||
.set_max_tables(256)
|
|
||||||
.set_geometry(geom)
|
|
||||||
.set_flags(flags)
|
.set_flags(flags)
|
||||||
.open(path)?;
|
.open(path)?;
|
||||||
|
|
||||||
Ok(Db {
|
Ok(Env {
|
||||||
mdbx_env: Arc::new(environment),
|
mdbx_env: Arc::new(environment),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn open_raw_table(&self, name: Option<&str>, _flags: ()) -> anyhow::Result<RawTable> {
|
pub fn open_raw_table<K: ZeroCopyByteWrapper + ?Sized, V: ZeroCopyByteWrapper + ?Sized>(
|
||||||
|
&self,
|
||||||
|
name: Option<&str>,
|
||||||
|
_flags: (),
|
||||||
|
) -> anyhow::Result<RawTable<K, V>> {
|
||||||
Ok(RawTableTryBuilder {
|
Ok(RawTableTryBuilder {
|
||||||
mdbx_env: self.mdbx_env.clone(),
|
mdbx_env: self.mdbx_env.clone(),
|
||||||
mdbx_db_builder: |mdbx_env: &Arc<Database<WriteMap>>| {
|
mdbx_db_builder: |mdbx_env: &Arc<Environment<WriteMap>>| {
|
||||||
let txn = mdbx_env
|
let txn = mdbx_env
|
||||||
.begin_rw_txn()
|
.begin_rw_txn()
|
||||||
.context("Can't start RW transaction")?;
|
.context("Can't start RW transaction")?;
|
||||||
// TODO database flags
|
// TODO database flags
|
||||||
let db = txn
|
let db = txn
|
||||||
.create_table(name, TableFlags::empty())
|
.create_db(name, DatabaseFlags::empty())
|
||||||
.context("Can't open database")?;
|
.context("Can't open database")?;
|
||||||
txn.prime_for_permaopen(db);
|
txn.prime_for_permaopen(db);
|
||||||
|
|
||||||
@ -61,20 +53,25 @@ impl Db {
|
|||||||
|
|
||||||
Ok(mdbx_db)
|
Ok(mdbx_db)
|
||||||
},
|
},
|
||||||
|
phantom_k: PhantomData::default(),
|
||||||
|
phantom_v: PhantomData::default(),
|
||||||
}
|
}
|
||||||
.try_build()?)
|
.try_build()?)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn open_wrapped_table<K: for<'a> ByteLamination<'a>, V: for<'a> ByteLamination<'a>>(
|
pub fn open_wrapped_table<K: ByteWrapper, V: ByteWrapper>(
|
||||||
&self,
|
&self,
|
||||||
name: Option<&str>,
|
name: Option<&str>,
|
||||||
flags: (),
|
flags: (),
|
||||||
) -> anyhow::Result<Table<K, V>> {
|
k_wrapper: K,
|
||||||
|
v_wrapper: V,
|
||||||
|
) -> anyhow::Result<WrappedTable<K, V>> {
|
||||||
let raw_table = self.open_raw_table(name, flags)?;
|
let raw_table = self.open_raw_table(name, flags)?;
|
||||||
|
|
||||||
Ok(Table {
|
Ok(WrappedTable {
|
||||||
raw: raw_table,
|
raw: raw_table,
|
||||||
_marker: Default::default(),
|
k_wrapper,
|
||||||
|
v_wrapper,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
148
src/inouts.rs
148
src/inouts.rs
@ -1,148 +0,0 @@
|
|||||||
use byte_lamination::serialisation::bare::SerdeBare;
|
|
||||||
use byte_lamination::serialisation::cbor::SerdeCbor;
|
|
||||||
use byte_lamination::ByteLamination;
|
|
||||||
use serde::de::DeserializeOwned;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use std::borrow::Cow;
|
|
||||||
use std::error::Error;
|
|
||||||
|
|
||||||
/// Type that can be passed in to a function wanting the bytes equivalent to `T`,
|
|
||||||
/// in the lifetime `'txn`.
|
|
||||||
pub trait In<'txn, T>: TryIntoCowBytes<'txn> {}
|
|
||||||
|
|
||||||
pub trait TryIntoCowBytes<'txn> {
|
|
||||||
fn to_cow_bytes(self) -> Result<Cow<'txn, [u8]>, Box<dyn Error>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Type that should be produced from a function giving the bytes equivalent to `Self`,
|
|
||||||
/// with lifetime `'txn`.
|
|
||||||
pub trait Out {
|
|
||||||
/// The type that should be produced.
|
|
||||||
type Produce<'txn>;
|
|
||||||
|
|
||||||
fn from_cow_bytes<'a>(cow_bytes: Cow<'a, [u8]>) -> Result<Self::Produce<'a>, Box<dyn Error>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
//// Implementations for bytes
|
|
||||||
|
|
||||||
impl<'a> TryIntoCowBytes<'a> for Cow<'a, [u8]> {
|
|
||||||
fn to_cow_bytes(self) -> Result<Cow<'a, [u8]>, Box<dyn Error>> {
|
|
||||||
Ok(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> TryIntoCowBytes<'a> for &'a [u8] {
|
|
||||||
fn to_cow_bytes(self) -> Result<Cow<'a, [u8]>, Box<dyn Error>> {
|
|
||||||
Ok(Cow::Borrowed(self))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> TryIntoCowBytes<'a> for Vec<u8> {
|
|
||||||
fn to_cow_bytes(self) -> Result<Cow<'a, [u8]>, Box<dyn Error>> {
|
|
||||||
Ok(Cow::Owned(self))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> In<'a, Vec<u8>> for Cow<'a, [u8]> {}
|
|
||||||
impl<'a> In<'a, Vec<u8>> for &'a [u8] {}
|
|
||||||
impl<'a> In<'a, Vec<u8>> for Vec<u8> {}
|
|
||||||
|
|
||||||
impl Out for Vec<u8> {
|
|
||||||
type Produce<'txn> = Cow<'txn, [u8]>;
|
|
||||||
|
|
||||||
fn from_cow_bytes<'a>(cow_bytes: Cow<'a, [u8]>) -> Result<Self::Produce<'a>, Box<dyn Error>> {
|
|
||||||
Ok(cow_bytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//// Implementations for strings
|
|
||||||
|
|
||||||
impl<'a> TryIntoCowBytes<'a> for Cow<'a, str> {
|
|
||||||
fn to_cow_bytes(self) -> Result<Cow<'a, [u8]>, Box<dyn Error>> {
|
|
||||||
Ok(match self {
|
|
||||||
Cow::Borrowed(str_slice) => Cow::Borrowed(str_slice.as_bytes()),
|
|
||||||
Cow::Owned(string) => Cow::Owned(string.into_bytes()),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> TryIntoCowBytes<'a> for &'a str {
|
|
||||||
fn to_cow_bytes(self) -> Result<Cow<'a, [u8]>, Box<dyn Error>> {
|
|
||||||
Ok(Cow::Borrowed(self.as_bytes()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> TryIntoCowBytes<'a> for String {
|
|
||||||
fn to_cow_bytes(self) -> Result<Cow<'a, [u8]>, Box<dyn Error>> {
|
|
||||||
Ok(Cow::Owned(self.into_bytes()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> In<'a, String> for Cow<'a, str> {}
|
|
||||||
impl<'a> In<'a, String> for &'a str {}
|
|
||||||
impl<'a> In<'a, String> for String {}
|
|
||||||
|
|
||||||
impl Out for String {
|
|
||||||
type Produce<'txn> = Cow<'txn, str>;
|
|
||||||
|
|
||||||
fn from_cow_bytes<'a>(cow_bytes: Cow<'a, [u8]>) -> Result<Self::Produce<'a>, Box<dyn Error>> {
|
|
||||||
Ok(match cow_bytes {
|
|
||||||
Cow::Borrowed(bslice) => Cow::Borrowed(std::str::from_utf8(bslice)?),
|
|
||||||
Cow::Owned(bvec) => Cow::Owned(String::from_utf8(bvec)?),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//// Implementations for BARE
|
|
||||||
|
|
||||||
pub struct Bare<T>(pub T);
|
|
||||||
|
|
||||||
impl<'a, T: Serialize + 'a> TryIntoCowBytes<'a> for Bare<T> {
|
|
||||||
fn to_cow_bytes(self) -> Result<Cow<'a, [u8]>, Box<dyn Error>> {
|
|
||||||
let bytes = SerdeBare::serialise(&self.0)?;
|
|
||||||
Ok(Cow::Owned(bytes.into_bytes()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// impl<'a, T: Serialize> TryIntoCowBytes<'a> for Bare<&T> {
|
|
||||||
// fn to_cow_bytes(self) -> Result<Cow<'a, [u8]>, Box<dyn Error>> {
|
|
||||||
// let bytes = SerdeBare::serialise(self.0)?;
|
|
||||||
// Ok(bytes.as_cow_bytes())
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
impl<'a, T: Serialize + 'a> In<'a, Bare<T>> for Bare<T> {}
|
|
||||||
impl<'a, T: Serialize + 'a> In<'a, Bare<T>> for Bare<&'a T> {}
|
|
||||||
|
|
||||||
// TODO BARE doesn't support borrowing bytes from the input yet.
|
|
||||||
impl<T: DeserializeOwned> Out for Bare<T> {
|
|
||||||
type Produce<'txn> = T;
|
|
||||||
|
|
||||||
fn from_cow_bytes<'a>(cow_bytes: Cow<'a, [u8]>) -> Result<Self::Produce<'a>, Box<dyn Error>> {
|
|
||||||
let serde_bare = SerdeBare::try_from_bytes(cow_bytes).unwrap();
|
|
||||||
serde_bare.deserialise()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//// Implementations for CBOR
|
|
||||||
|
|
||||||
pub struct Cbor<T>(pub T);
|
|
||||||
|
|
||||||
impl<'a, T: Serialize + 'a> TryIntoCowBytes<'a> for Cbor<T> {
|
|
||||||
fn to_cow_bytes(self) -> Result<Cow<'a, [u8]>, Box<dyn Error>> {
|
|
||||||
let bytes = SerdeCbor::serialise(&self.0)?;
|
|
||||||
Ok(Cow::Owned(bytes.into_bytes()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, T: Serialize + 'a> In<'a, Cbor<T>> for Cbor<T> {}
|
|
||||||
impl<'a, T: Serialize + 'a> In<'a, Cbor<T>> for Cbor<&'a T> {}
|
|
||||||
|
|
||||||
// TODO BARE doesn't support borrowing bytes from the input yet.
|
|
||||||
impl<T: for<'a> Deserialize<'a>> Out for Cbor<T> {
|
|
||||||
type Produce<'txn> = T;
|
|
||||||
|
|
||||||
fn from_cow_bytes<'a>(cow_bytes: Cow<'a, [u8]>) -> Result<Self::Produce<'a>, Box<dyn Error>> {
|
|
||||||
let serde_cbor = SerdeCbor::try_from_bytes(cow_bytes).unwrap();
|
|
||||||
serde_cbor.deserialise()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,6 +1,6 @@
|
|||||||
|
pub mod database;
|
||||||
pub mod environment;
|
pub mod environment;
|
||||||
pub mod inouts;
|
pub mod wrapper;
|
||||||
pub mod table;
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
@ -1,47 +0,0 @@
|
|||||||
use libmdbx::{Database, Table, TransactionKind, WriteFlags, WriteMap, RW};
|
|
||||||
use ouroboros::self_referencing;
|
|
||||||
use std::borrow::Cow;
|
|
||||||
|
|
||||||
use crate::environment::Txn;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
#[self_referencing(pub_extras)]
|
|
||||||
pub struct RawTable {
|
|
||||||
pub mdbx_env: Arc<Database<WriteMap>>,
|
|
||||||
|
|
||||||
#[borrows(mdbx_env)]
|
|
||||||
#[covariant]
|
|
||||||
pub mdbx_db: Table<'this>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RawTable {
|
|
||||||
pub fn get<'txn, TK: TransactionKind>(
|
|
||||||
&'txn self,
|
|
||||||
txn: &'txn Txn<'txn, TK>,
|
|
||||||
k: &[u8],
|
|
||||||
) -> anyhow::Result<Option<Cow<'txn, [u8]>>> {
|
|
||||||
txn.mdbx_txn
|
|
||||||
.get::<Cow<'txn, [u8]>>(self.borrow_mdbx_db(), k.as_ref())
|
|
||||||
.map_err(|e| e.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn put<'txn>(
|
|
||||||
&self,
|
|
||||||
txn: &Txn<'txn, RW>,
|
|
||||||
k: impl AsRef<[u8]>,
|
|
||||||
v: impl AsRef<[u8]>,
|
|
||||||
) -> anyhow::Result<()> {
|
|
||||||
txn.mdbx_txn.put(
|
|
||||||
self.borrow_mdbx_db(),
|
|
||||||
k.as_ref(),
|
|
||||||
v.as_ref(),
|
|
||||||
WriteFlags::empty(),
|
|
||||||
)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns true if the key was present.
|
|
||||||
pub fn delete<'txn>(&self, txn: &Txn<'txn, RW>, k: impl AsRef<[u8]>) -> anyhow::Result<bool> {
|
|
||||||
Ok(txn.mdbx_txn.del(self.borrow_mdbx_db(), k.as_ref(), None)?)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,119 +0,0 @@
|
|||||||
use crate::table::raw::RawTable;
|
|
||||||
|
|
||||||
use crate::environment::Txn;
|
|
||||||
use crate::inouts::{In, Out};
|
|
||||||
use anyhow::anyhow;
|
|
||||||
use byte_lamination::ByteLamination;
|
|
||||||
use libmdbx::{TransactionKind, RW};
|
|
||||||
use std::borrow::Cow;
|
|
||||||
use std::marker::PhantomData;
|
|
||||||
|
|
||||||
pub struct Table<KRep, VRep> {
|
|
||||||
pub raw: RawTable,
|
|
||||||
pub _marker: PhantomData<(KRep, VRep)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<KRep, VRep: Out> Table<KRep, VRep> {
|
|
||||||
pub fn get<'txn, 'this, 'qk, QK: In<'qk, KRep>, TK: TransactionKind>(
|
|
||||||
&'this self,
|
|
||||||
txn: &'this Txn<'txn, TK>,
|
|
||||||
k: QK,
|
|
||||||
) -> anyhow::Result<Option<VRep::Produce<'this>>> {
|
|
||||||
let k_bytes = k
|
|
||||||
.to_cow_bytes()
|
|
||||||
.map_err(|e| anyhow!("failed to convert key value to bytes: {e:?}"))?;
|
|
||||||
|
|
||||||
let val_opt = self.raw.get(txn, &k_bytes)?;
|
|
||||||
|
|
||||||
match val_opt {
|
|
||||||
Some(cow_bytes) => {
|
|
||||||
let value = VRep::from_cow_bytes(cow_bytes)
|
|
||||||
.map_err(|e| anyhow!("failed to convert value bytes to value: {e:?}"))?;
|
|
||||||
Ok(Some(value))
|
|
||||||
}
|
|
||||||
None => Ok(None),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<KRep, VRep> Table<KRep, VRep> {
|
|
||||||
pub fn put<'txn, 'qk, 'qv, QK: In<'qk, KRep>, QV: In<'qv, VRep>>(
|
|
||||||
&self,
|
|
||||||
txn: &Txn<'txn, RW>,
|
|
||||||
k: QK,
|
|
||||||
v: QV,
|
|
||||||
) -> anyhow::Result<()> {
|
|
||||||
let k_bytes = k
|
|
||||||
.to_cow_bytes()
|
|
||||||
.map_err(|e| anyhow!("failed to convert key value to bytes: {e:?}"))?;
|
|
||||||
let v_bytes = v
|
|
||||||
.to_cow_bytes()
|
|
||||||
.map_err(|e| anyhow!("failed to convert value value to bytes: {e:?}"))?;
|
|
||||||
self.raw.put(txn, &k_bytes, &v_bytes)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns true if the key was present.
|
|
||||||
pub fn delete<'txn, 'qk, QK: In<'qk, KRep>>(
|
|
||||||
&self,
|
|
||||||
txn: &Txn<'txn, RW>,
|
|
||||||
k: QK,
|
|
||||||
) -> anyhow::Result<bool> {
|
|
||||||
let k_bytes = k
|
|
||||||
.to_cow_bytes()
|
|
||||||
.map_err(|e| anyhow!("failed to convert key value to bytes: {e:?}"))?;
|
|
||||||
|
|
||||||
self.raw.delete(txn, &k_bytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `T`: a ByteLamination that this type is equivalent to.
|
|
||||||
pub trait ByteLaminationHelper<'a, T: ByteLamination<'a>> {
|
|
||||||
fn as_cow_bytes_helper(&self) -> Cow<'_, [u8]>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, T: ByteLamination<'a>> ByteLaminationHelper<'a, T> for T {
|
|
||||||
fn as_cow_bytes_helper(&self) -> Cow<'_, [u8]> {
|
|
||||||
self.as_cow_bytes()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, 'b, T: ByteLamination<'a>> ByteLaminationHelper<'a, T> for &'b T {
|
|
||||||
fn as_cow_bytes_helper(&self) -> Cow<'_, [u8]> {
|
|
||||||
self.as_cow_bytes()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// can't get rust happy with this...
|
|
||||||
// impl<'a, T: ?Sized + ToOwned> ByteLaminationHelper<'a, Cow<'a, T>> for &'a T
|
|
||||||
// where for<'b> Cow<'b, T>: ByteLamination<'b> {
|
|
||||||
// fn as_cow_bytes_helper(&self) -> Cow<'_, [u8]> {
|
|
||||||
// let x: Cow<'a, T> = Cow::Borrowed(self);
|
|
||||||
// let y: &'a Cow<'a, T> = &x;
|
|
||||||
// Cow::<'_, T>::as_cow_bytes(y)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
impl<'a> ByteLaminationHelper<'a, Cow<'a, str>> for &'a str {
|
|
||||||
fn as_cow_bytes_helper(&self) -> Cow<'_, [u8]> {
|
|
||||||
Cow::Borrowed(self.as_bytes())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> ByteLaminationHelper<'a, String> for &'a str {
|
|
||||||
fn as_cow_bytes_helper(&self) -> Cow<'_, [u8]> {
|
|
||||||
Cow::Borrowed(self.as_bytes())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> ByteLaminationHelper<'a, Cow<'a, [u8]>> for &'a [u8] {
|
|
||||||
fn as_cow_bytes_helper(&self) -> Cow<'_, [u8]> {
|
|
||||||
Cow::Borrowed(&self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> ByteLaminationHelper<'a, Vec<u8>> for &'a [u8] {
|
|
||||||
fn as_cow_bytes_helper(&self) -> Cow<'_, [u8]> {
|
|
||||||
Cow::Borrowed(&self)
|
|
||||||
}
|
|
||||||
}
|
|
14
src/tests.rs
14
src/tests.rs
@ -1,15 +1,15 @@
|
|||||||
use crate::environment::Db;
|
use crate::environment::Env;
|
||||||
use tempfile::NamedTempFile;
|
use tempfile::NamedTempFile;
|
||||||
|
|
||||||
fn temp_env() -> Db {
|
fn temp_env() -> Env {
|
||||||
let tempfile = NamedTempFile::new().unwrap();
|
let tempfile = NamedTempFile::new().unwrap();
|
||||||
Db::open(tempfile.path()).unwrap()
|
Env::open(tempfile.path()).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_raw_bytes_basics() {
|
fn test_raw_bytes_basics() {
|
||||||
let env = temp_env();
|
let env = temp_env();
|
||||||
let db = env.open_raw_table(Some("rawt"), ()).unwrap();
|
let db = env.open_raw_table::<[u8], [u8]>(Some("rawt"), ()).unwrap();
|
||||||
|
|
||||||
let k: [u8; 4] = [0, 1, 2, 3];
|
let k: [u8; 4] = [0, 1, 2, 3];
|
||||||
|
|
||||||
@ -30,11 +30,9 @@ fn test_raw_bytes_basics() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_wrapped_str_basics() {
|
fn test_raw_str_basics() {
|
||||||
let env = temp_env();
|
let env = temp_env();
|
||||||
let db = env
|
let db = env.open_raw_table::<str, str>(Some("rawt"), ()).unwrap();
|
||||||
.open_wrapped_table::<String, String>(Some("rawt"), ())
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
{
|
{
|
||||||
let txn = env.ro_txn().unwrap();
|
let txn = env.ro_txn().unwrap();
|
||||||
|
147
src/wrapper.rs
Normal file
147
src/wrapper.rs
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
use anyhow::anyhow;
|
||||||
|
use serde::de::DeserializeOwned;
|
||||||
|
use serde::Serialize;
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
use zstd::bulk::{Compressor, Decompressor};
|
||||||
|
|
||||||
|
pub trait ByteWrapper {
|
||||||
|
type Item;
|
||||||
|
|
||||||
|
fn load_from_db_bytes(&self, bytes: &[u8]) -> anyhow::Result<Self::Item>;
|
||||||
|
|
||||||
|
fn dump_to_db_bytes(&self, item: &Self::Item) -> anyhow::Result<Vec<u8>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SerdeBareWrapper<T> {
|
||||||
|
phantom: PhantomData<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Default for SerdeBareWrapper<T> {
|
||||||
|
fn default() -> Self {
|
||||||
|
SerdeBareWrapper {
|
||||||
|
phantom: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Serialize + DeserializeOwned> ByteWrapper for SerdeBareWrapper<T> {
|
||||||
|
type Item = T;
|
||||||
|
|
||||||
|
fn load_from_db_bytes(&self, bytes: &[u8]) -> anyhow::Result<Self::Item> {
|
||||||
|
Ok(serde_bare::from_slice(bytes)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dump_to_db_bytes(&self, item: &Self::Item) -> anyhow::Result<Vec<u8>> {
|
||||||
|
Ok(serde_bare::to_vec(item)?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct CompressorWrapper<T> {
|
||||||
|
inner: T,
|
||||||
|
compressor: Mutex<Compressor<'static>>,
|
||||||
|
decompressor: Mutex<Decompressor<'static>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ByteWrapper> ByteWrapper for CompressorWrapper<T> {
|
||||||
|
type Item = T::Item;
|
||||||
|
|
||||||
|
fn load_from_db_bytes(&self, bytes: &[u8]) -> anyhow::Result<Self::Item> {
|
||||||
|
let mut decompressor = self
|
||||||
|
.decompressor
|
||||||
|
.lock()
|
||||||
|
.map_err(|_| anyhow!("Can't lock decompressor"))?;
|
||||||
|
// TODO be more sensible about capacity. 8× should be good though.
|
||||||
|
let raw_bytes = decompressor.decompress(bytes, bytes.len() * 8)?;
|
||||||
|
Ok(self.inner.load_from_db_bytes(&raw_bytes)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dump_to_db_bytes(&self, item: &Self::Item) -> anyhow::Result<Vec<u8>> {
|
||||||
|
let raw_bytes = self.inner.dump_to_db_bytes(item)?;
|
||||||
|
let mut compressor = self
|
||||||
|
.compressor
|
||||||
|
.lock()
|
||||||
|
.map_err(|_| anyhow!("Can't lock compressor"))?;
|
||||||
|
Ok(compressor.compress(&raw_bytes)?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> CompressorWrapper<T> {
|
||||||
|
pub fn new_with(inner: T) -> anyhow::Result<Self> {
|
||||||
|
let compressor = Compressor::new(13)?;
|
||||||
|
let decompressor = Decompressor::new()?;
|
||||||
|
|
||||||
|
Ok(CompressorWrapper {
|
||||||
|
inner,
|
||||||
|
compressor: Mutex::new(compressor),
|
||||||
|
decompressor: Mutex::new(decompressor),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new() -> anyhow::Result<Self>
|
||||||
|
where
|
||||||
|
T: Default,
|
||||||
|
{
|
||||||
|
let inner: T = Default::default();
|
||||||
|
|
||||||
|
Self::new_with(inner)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ByteWrapper for String {
|
||||||
|
type Item = String;
|
||||||
|
|
||||||
|
fn load_from_db_bytes(&self, bytes: &[u8]) -> anyhow::Result<Self::Item> {
|
||||||
|
Ok(std::str::from_utf8(bytes)?.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dump_to_db_bytes(&self, item: &Self::Item) -> anyhow::Result<Vec<u8>> {
|
||||||
|
Ok(item.as_bytes().to_vec())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ByteWrapper> ByteWrapper for Arc<T> {
|
||||||
|
type Item = T::Item;
|
||||||
|
|
||||||
|
fn load_from_db_bytes(&self, bytes: &[u8]) -> anyhow::Result<Self::Item> {
|
||||||
|
(&self as &T).load_from_db_bytes(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dump_to_db_bytes(&self, item: &Self::Item) -> anyhow::Result<Vec<u8>> {
|
||||||
|
(&self as &T).dump_to_db_bytes(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ZeroCopyByteWrapper: ToOwned {
|
||||||
|
fn as_byte_slice(&self) -> &[u8];
|
||||||
|
fn from_byte_slice(bytes: &[u8]) -> anyhow::Result<&Self>;
|
||||||
|
fn from_owned_bytes(bytes: Vec<u8>) -> anyhow::Result<Self::Owned>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ZeroCopyByteWrapper for [u8] {
|
||||||
|
fn as_byte_slice(&self) -> &[u8] {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_byte_slice(bytes: &[u8]) -> anyhow::Result<&Self> {
|
||||||
|
Ok(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_owned_bytes(bytes: Vec<u8>) -> anyhow::Result<Self::Owned> {
|
||||||
|
Ok(bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ZeroCopyByteWrapper for str {
|
||||||
|
fn as_byte_slice(&self) -> &[u8] {
|
||||||
|
self.as_bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_byte_slice(bytes: &[u8]) -> anyhow::Result<&Self> {
|
||||||
|
Ok(std::str::from_utf8(bytes)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_owned_bytes(bytes: Vec<u8>) -> anyhow::Result<Self::Owned> {
|
||||||
|
Ok(String::from_utf8(bytes)?)
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user