├── .github ├── dependabot.yml └── workflows │ └── rust.yml ├── .gitignore ├── .travis.yml ├── Cargo.toml ├── LICENSE.md ├── README.md └── src ├── bucket.rs ├── codec.rs ├── config.rs ├── error.rs ├── key.rs ├── lib.rs ├── store.rs ├── tests.rs ├── transaction.rs └── value.rs /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: cargo 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "13:00" 8 | open-pull-requests-limit: 10 9 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ master, rewrite ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | linux: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | 15 | - name: Run tests 16 | run: cargo test --verbose --all-features 17 | macos: 18 | runs-on: macos-latest 19 | steps: 20 | - uses: actions/checkout@v2 21 | 22 | - name: Run tests 23 | run: cargo test --verbose --all-features 24 | linux_nightly: 25 | runs-on: ubuntu-latest 26 | steps: 27 | - uses: actions/checkout@v2 28 | 29 | - name: Nightly 30 | run: rustup toolchain install nightly 31 | 32 | - name: Run tests 33 | run: cargo +nightly test --verbose --all-features 34 | macos_nightly: 35 | runs-on: macos-latest 36 | steps: 37 | - uses: actions/checkout@v2 38 | 39 | - name: Nightly 40 | run: rustup toolchain install nightly 41 | 42 | - name: Run tests 43 | run: cargo +nightly test --verbose --all-features 44 | linux_no_default_features: 45 | runs-on: ubuntu-latest 46 | steps: 47 | - uses: actions/checkout@v2 48 | 49 | - name: Run tests 50 | run: cargo test --no-default-features --verbose 51 | clippy: 52 | runs-on: ubuntu-20.04 53 | steps: 54 | - uses: actions/checkout@v2 55 | 56 | - name: Nightly 57 | run: rustup toolchain install nightly --profile=default 58 | 59 | - name: Run tests 60 | run: cargo +nightly clippy --all 61 | 62 | 63 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | test/ 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | rust: 3 | - stable 4 | - beta 5 | - nightly 6 | matrix: 7 | allow_failures: 8 | - rust: nightly 9 | fast_finish: true 10 | script: 11 | - cargo test --all-features --verbose 12 | 13 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kv" 3 | version = "0.24.0" 4 | authors = ["Zach Shipko "] 5 | license = "ISC" 6 | keywords = ["key-value-store", "database", "sled"] 7 | repository = "https://github.com/zshipko/rust-kv" 8 | documentation = "https://docs.rs/kv" 9 | description = "An embedded key/value store for Rust" 10 | readme = "README.md" 11 | edition = "2021" 12 | 13 | [package.metadata.docs.rs] 14 | all-features = true 15 | 16 | [dependencies] 17 | sled = "0.34" 18 | thiserror = "1" 19 | toml = "0.5" 20 | pin-project-lite = "0.2" 21 | serde = {version = "1", features = ["derive"]} 22 | serde_json = {version = "1", optional = true} 23 | rmp-serde = {version = "1.0", optional = true} 24 | bincode = {version = "1.3", optional = true} 25 | serde-lexpr = {version = "0.1", optional = true} 26 | 27 | [features] 28 | default = [] 29 | json-value = ["serde_json"] 30 | msgpack-value = ["rmp-serde"] 31 | bincode-value = ["bincode"] 32 | lexpr-value = ["serde-lexpr"] 33 | compression = ["sled/compression"] 34 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | ISC License 2 | 3 | Copyright (c) 2017, Zach Shipko 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # kv 2 | 3 | 4 | 5 | 6 | 7 | An embedded key/value store for Rust built on [sled](https://docs.rs/sled) 8 | 9 | - Easy configuration 10 | - Integer keys 11 | - Serde integration 12 | 13 | Note: `kv` `0.20` and greater have been completely re-written to use [sled](https://docs.rs/sled) instead of [LMDB](https://github.com/LMDB/lmdb). In the process the entire API has been redesigned and simplified significantly. If you still need to use LMDB or don't like the new interface then you might want to check out [rkv](https://docs.rs/rkv). 14 | 15 | ## Optional features 16 | 17 | * `msgpack-value` 18 | - MessagePack encoding using `rmp-serde` 19 | * `json-value` 20 | - JSON encoding using `serde_json` 21 | * `bincode-value` 22 | - bincode encoding using `bincode` 23 | * `lexpr-value` 24 | - S-expression encoding using `serde-lexpr` 25 | 26 | ## Documentation 27 | 28 | See [https://docs.rs/kv](https://docs.rs/kv) 29 | 30 | -------------------------------------------------------------------------------- /src/bucket.rs: -------------------------------------------------------------------------------- 1 | use pin_project_lite::pin_project; 2 | use std::future::Future; 3 | use std::marker::PhantomData; 4 | use std::pin::Pin; 5 | use std::task::{Context, Poll}; 6 | 7 | use sled::Transactional; 8 | 9 | use crate::{Error, Key, Raw, Transaction, TransactionError, Value}; 10 | 11 | /// Provides typed access to the key/value store 12 | #[derive(Clone)] 13 | pub struct Bucket<'a, K: Key<'a>, V: Value>( 14 | pub(crate) sled::Tree, 15 | PhantomData, 16 | PhantomData, 17 | PhantomData<&'a ()>, 18 | ); 19 | 20 | /// Key/value pair 21 | #[derive(Clone)] 22 | pub struct Item(Raw, Raw, PhantomData, PhantomData); 23 | 24 | /// Batch update 25 | #[derive(Clone)] 26 | pub struct Batch(pub(crate) sled::Batch, PhantomData, PhantomData); 27 | 28 | pin_project! { 29 | /// Subscribe to key updated 30 | pub struct Watch { 31 | #[pin] 32 | subscriber: sled::Subscriber, 33 | phantom: PhantomData<(K, V)> 34 | } 35 | } 36 | 37 | /// Event is used to describe the type of update 38 | pub enum Event { 39 | /// A key has been updated 40 | Set(Item), 41 | /// A key has been removed 42 | Remove(Raw), 43 | } 44 | 45 | impl<'a, K: Key<'a>, V> Event { 46 | fn from_sled(event: sled::Event) -> Self { 47 | match event { 48 | sled::Event::Insert { key, value } => { 49 | Event::Set(Item(key, value, PhantomData, PhantomData)) 50 | } 51 | sled::Event::Remove { key } => Event::Remove(key), 52 | } 53 | } 54 | } 55 | 56 | impl<'a, K: Key<'a>, V> Iterator for Watch { 57 | type Item = Result, Error>; 58 | 59 | fn next(&mut self) -> Option { 60 | match self.subscriber.next() { 61 | None => None, 62 | Some(e) => Some(Ok(Event::from_sled(e))), 63 | } 64 | } 65 | } 66 | 67 | impl<'a, K: Key<'a>, V> Future for Watch { 68 | type Output = Option>; 69 | fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 70 | let this = self.project(); 71 | match this.subscriber.poll(cx) { 72 | Poll::Pending => Poll::Pending, 73 | Poll::Ready(r) => Poll::Ready(r.map(Event::from_sled)), 74 | } 75 | } 76 | } 77 | 78 | impl<'a, K: Key<'a>, V: Value> Event { 79 | /// Returns true when event is `Set` 80 | pub fn is_set(&self) -> bool { 81 | matches!(self, Event::Set(_)) 82 | } 83 | 84 | /// Returns true when event is `Remove` 85 | pub fn is_remove(&self) -> bool { 86 | matches!(self, Event::Remove(_)) 87 | } 88 | 89 | /// Get event key 90 | pub fn key(&'a self) -> Result { 91 | match self { 92 | Event::Remove(k) => K::from_raw_key(k), 93 | Event::Set(item) => item.key(), 94 | } 95 | } 96 | 97 | /// Get event value (for insert) 98 | pub fn value(&'a self) -> Result, Error> { 99 | match self { 100 | Event::Remove(_) => Ok(None), 101 | Event::Set(item) => item.value().map(Some), 102 | } 103 | } 104 | } 105 | 106 | impl<'a, K: Key<'a>, V: Value> Item { 107 | /// Get the value associated with the specified key 108 | pub fn value>(&'a self) -> Result { 109 | let x = V::from_raw_value(self.1.clone())?; 110 | Ok(x.into()) 111 | } 112 | 113 | /// Get the value associated with the specified key 114 | pub fn key(&'a self) -> Result 115 | where 116 | K: Into, 117 | { 118 | let k = K::from_raw_key(&self.0)?; 119 | Ok(k.into()) 120 | } 121 | } 122 | 123 | /// Iterator over Bucket keys and values 124 | pub struct Iter(sled::Iter, PhantomData, PhantomData); 125 | 126 | impl<'a, K, V> Iterator for Iter 127 | where 128 | K: Key<'a>, 129 | V: Value, 130 | { 131 | type Item = Result, Error>; 132 | 133 | fn next(&mut self) -> Option { 134 | match self.0.next() { 135 | None => None, 136 | Some(Err(e)) => Some(Err(e.into())), 137 | Some(Ok((k, v))) => Some(Ok(Item(k, v, PhantomData, PhantomData))), 138 | } 139 | } 140 | } 141 | 142 | impl<'a, K, V> DoubleEndedIterator for Iter 143 | where 144 | K: Key<'a>, 145 | V: Value, 146 | { 147 | fn next_back(&mut self) -> Option { 148 | match self.0.next_back() { 149 | None => None, 150 | Some(Err(e)) => Some(Err(e.into())), 151 | Some(Ok((k, v))) => Some(Ok(Item(k, v, PhantomData, PhantomData))), 152 | } 153 | } 154 | } 155 | 156 | impl<'a, K: Key<'a>, V: Value> Bucket<'a, K, V> { 157 | pub(crate) fn new(t: sled::Tree) -> Bucket<'a, K, V> { 158 | Bucket(t, PhantomData, PhantomData, PhantomData) 159 | } 160 | 161 | /// Returns true if the bucket contains the given key 162 | pub fn contains(&self, key: &K) -> Result { 163 | let v = self.0.contains_key(key.to_raw_key()?)?; 164 | Ok(v) 165 | } 166 | 167 | /// Get the value associated with the specified key 168 | pub fn get(&self, key: &K) -> Result, Error> { 169 | let v = self.0.get(key.to_raw_key()?)?; 170 | 171 | match v { 172 | None => Ok(None), 173 | Some(x) => Ok(Some(V::from_raw_value(x)?)), 174 | } 175 | } 176 | 177 | /// Set the value associated with the specified key to the provided value 178 | pub fn set(&self, key: &K, value: &V) -> Result, Error> { 179 | let v = value.to_raw_value()?; 180 | Ok(self 181 | .0 182 | .insert(key.to_raw_key()?, v)? 183 | .map(|x| V::from_raw_value(x)) 184 | // https://users.rust-lang.org/t/convenience-method-for-flipping-option-result-to-result-option/13695/7 185 | .map_or(Ok(None), |v| v.map(Some))?) 186 | } 187 | 188 | /// Set the value associated with the specified key to the provided value, only if the existing 189 | /// value matches the `old` parameter 190 | pub fn compare_and_swap( 191 | &self, 192 | key: &K, 193 | old: Option<&V>, 194 | value: Option<&V>, 195 | ) -> Result<(), Error> { 196 | let old = match old { 197 | Some(x) => Some(x.to_raw_value()?), 198 | None => None, 199 | }; 200 | 201 | let value = match value { 202 | Some(x) => Some(x.to_raw_value()?), 203 | None => None, 204 | }; 205 | 206 | let a = self.0.compare_and_swap(key.to_raw_key()?, old, value)?; 207 | 208 | Ok(a?) 209 | } 210 | 211 | /// Remove the value associated with the specified key from the database 212 | pub fn remove(&self, key: &K) -> Result, Error> { 213 | Ok(self 214 | .0 215 | .remove(key.to_raw_key()?)? 216 | .map(|x| V::from_raw_value(x)) 217 | // https://users.rust-lang.org/t/convenience-method-for-flipping-option-result-to-result-option/13695/7 218 | .map_or(Ok(None), |v| v.map(Some))?) 219 | } 220 | 221 | /// Get an iterator over keys/values 222 | pub fn iter(&self) -> Iter { 223 | Iter(self.0.iter(), PhantomData, PhantomData) 224 | } 225 | 226 | /// Get an iterator over keys/values in the specified range 227 | pub fn iter_range(&self, a: &K, b: &K) -> Result, Error> { 228 | let a = a.to_raw_key()?; 229 | let b = b.to_raw_key()?; 230 | Ok(Iter(self.0.range(a..b), PhantomData, PhantomData)) 231 | } 232 | 233 | /// Iterate over keys/values with the specified prefix 234 | pub fn iter_prefix(&self, a: &K) -> Result, Error> { 235 | let a = a.to_raw_key()?; 236 | Ok(Iter(self.0.scan_prefix(a), PhantomData, PhantomData)) 237 | } 238 | 239 | /// Apply batch update 240 | pub fn batch(&self, batch: Batch) -> Result<(), Error> { 241 | self.0.apply_batch(batch.0)?; 242 | Ok(()) 243 | } 244 | 245 | /// Get updates when a key with the given prefix is changed 246 | pub fn watch_prefix(&self, prefix: Option<&K>) -> Result, Error> { 247 | let k = match prefix { 248 | Some(k) => k.to_raw_key()?, 249 | None => b"".into(), 250 | }; 251 | let subscriber = self.0.watch_prefix(k); 252 | Ok(Watch { 253 | subscriber, 254 | phantom: PhantomData {}, 255 | }) 256 | } 257 | 258 | /// Execute a transaction 259 | pub fn transaction< 260 | A, 261 | E: From, 262 | F: Fn(Transaction) -> Result>, 263 | >( 264 | &self, 265 | f: F, 266 | ) -> Result { 267 | let result = self.0.transaction(|t| { 268 | let txn = Transaction::new(t); 269 | f(txn) 270 | }); 271 | 272 | match result { 273 | Ok(x) => Ok(x), 274 | Err(sled::transaction::TransactionError::Abort(x)) => Err(x), 275 | Err(sled::transaction::TransactionError::Storage(e)) => Err(e.into()), 276 | } 277 | } 278 | 279 | /// Create a transaction with access to two buckets 280 | pub fn transaction2< 281 | A, 282 | T: Key<'a>, 283 | U: Value, 284 | E: From, 285 | F: Fn(Transaction, Transaction) -> Result>, 286 | >( 287 | &self, 288 | other: &Bucket<'a, T, U>, 289 | f: F, 290 | ) -> Result { 291 | let result = (&self.0, &other.0).transaction(|(a, b)| { 292 | let a = Transaction::new(a); 293 | let b = Transaction::new(b); 294 | f(a, b) 295 | }); 296 | 297 | match result { 298 | Ok(x) => Ok(x), 299 | Err(sled::transaction::TransactionError::Abort(x)) => Err(x), 300 | Err(sled::transaction::TransactionError::Storage(e)) => Err(e.into()), 301 | } 302 | } 303 | 304 | /// Create a transaction with access to three buckets 305 | pub fn transaction3< 306 | A, 307 | T: Key<'a>, 308 | U: Value, 309 | X: Key<'a>, 310 | Y: Value, 311 | E: From, 312 | F: Fn( 313 | Transaction, 314 | Transaction, 315 | Transaction, 316 | ) -> Result>, 317 | >( 318 | &self, 319 | other: &Bucket<'a, T, U>, 320 | other1: &Bucket<'a, X, Y>, 321 | f: F, 322 | ) -> Result { 323 | let result = (&self.0, &other.0, &other1.0).transaction(|(a, b, c)| { 324 | let a = Transaction::new(a); 325 | let b = Transaction::new(b); 326 | let c = Transaction::new(c); 327 | f(a, b, c) 328 | }); 329 | 330 | match result { 331 | Ok(x) => Ok(x), 332 | Err(sled::transaction::TransactionError::Abort(x)) => Err(x), 333 | Err(sled::transaction::TransactionError::Storage(e)) => Err(e.into()), 334 | } 335 | } 336 | 337 | /// Get previous key and value in order, if one exists 338 | pub fn prev_key(&self, key: &K) -> Result>, Error> { 339 | let item = self.0.get_lt(key)?; 340 | Ok(item.map(|(k, v)| Item(k, v, PhantomData, PhantomData))) 341 | } 342 | 343 | /// Get next key and value in order, if one exists 344 | pub fn next_key(&self, key: &K) -> Result>, Error> { 345 | let item = self.0.get_gt(key)?; 346 | Ok(item.map(|(k, v)| Item(k, v, PhantomData, PhantomData))) 347 | } 348 | 349 | /// Flush to disk 350 | pub fn flush(&self) -> Result { 351 | Ok(self.0.flush()?) 352 | } 353 | 354 | /// Flush to disk 355 | pub async fn flush_async(&self) -> Result { 356 | let f = self.0.flush_async().await?; 357 | Ok(f) 358 | } 359 | 360 | /// Remove and return the last item 361 | pub fn pop_back(&self) -> Result>, Error> { 362 | let x = self.0.pop_max()?; 363 | Ok(x.map(|(k, v)| Item(k, v, PhantomData, PhantomData))) 364 | } 365 | 366 | /// Remove and return the first item 367 | pub fn pop_front(&self) -> Result>, Error> { 368 | let x = self.0.pop_min()?; 369 | Ok(x.map(|(k, v)| Item(k, v, PhantomData, PhantomData))) 370 | } 371 | 372 | /// Get the first item 373 | pub fn first(&self) -> Result>, Error> { 374 | let x = self.0.first()?; 375 | Ok(x.map(|(k, v)| Item(k, v, PhantomData, PhantomData))) 376 | } 377 | 378 | /// Get the last item 379 | pub fn last(&self) -> Result>, Error> { 380 | let x = self.0.last()?; 381 | Ok(x.map(|(k, v)| Item(k, v, PhantomData, PhantomData))) 382 | } 383 | 384 | /// Get the number of items 385 | pub fn len(&self) -> usize { 386 | self.0.len() 387 | } 388 | 389 | /// Returns true when there are no items 390 | pub fn is_empty(&self) -> bool { 391 | self.0.is_empty() 392 | } 393 | 394 | /// Remove all items 395 | pub fn clear(&self) -> Result<(), Error> { 396 | self.0.clear()?; 397 | Ok(()) 398 | } 399 | 400 | /// CRC32 checksum of all keys and values 401 | pub fn checksum(&self) -> Result { 402 | Ok(self.0.checksum()?) 403 | } 404 | } 405 | 406 | impl<'a, K: Key<'a>, V: Value> Default for Batch { 407 | fn default() -> Self { 408 | Batch::new() 409 | } 410 | } 411 | 412 | impl<'a, K: Key<'a>, V: Value> Batch { 413 | /// Create a new Batch instance 414 | pub fn new() -> Batch { 415 | Batch(sled::Batch::default(), PhantomData, PhantomData) 416 | } 417 | 418 | /// Set the value associated with the specified key to the provided value 419 | pub fn set(&mut self, key: &K, value: &V) -> Result<(), Error> { 420 | let v = value.to_raw_value()?; 421 | self.0.insert(key.to_raw_key()?, v); 422 | Ok(()) 423 | } 424 | 425 | /// Remove the value associated with the specified key from the database 426 | pub fn remove(&mut self, key: &K) -> Result<(), Error> { 427 | self.0.remove(key.to_raw_key()?); 428 | Ok(()) 429 | } 430 | } 431 | -------------------------------------------------------------------------------- /src/codec.rs: -------------------------------------------------------------------------------- 1 | #[allow(unused_imports)] 2 | use crate::{Error, Raw, Value}; 3 | 4 | /// Base trait for values that can be encoded using serde 5 | pub trait Codec: 6 | Value + AsRef + AsMut 7 | { 8 | /// Convert back into inner value 9 | fn into_inner(self) -> T; 10 | } 11 | 12 | #[macro_export] 13 | /// Define a codec type and implement the Codec trait 14 | macro_rules! codec { 15 | ($x:ident) => { 16 | /// Codec implementation 17 | pub struct $x(pub T); 18 | 19 | impl AsRef for $x { 20 | fn as_ref(&self) -> &T { 21 | &self.0 22 | } 23 | } 24 | 25 | impl AsMut for $x { 26 | fn as_mut(&mut self) -> &mut T { 27 | &mut self.0 28 | } 29 | } 30 | 31 | impl Codec for $x { 32 | fn into_inner(self) -> T { 33 | self.0 34 | } 35 | } 36 | 37 | impl Clone for $x { 38 | fn clone(&self) -> Self { 39 | $x(self.0.clone()) 40 | } 41 | } 42 | }; 43 | 44 | ($x:ident, {$ser:expr, $de:expr}) => { 45 | codec!($x); 46 | 47 | impl Value for $x { 48 | fn to_raw_value(&self) -> Result { 49 | let x = $ser(&self.0)?; 50 | Ok(x.into()) 51 | } 52 | 53 | fn from_raw_value(r: Raw) -> Result { 54 | let x = $de(&r)?; 55 | Ok($x(x)) 56 | } 57 | } 58 | }; 59 | } 60 | 61 | #[cfg(feature = "msgpack-value")] 62 | mod msgpack_value { 63 | use super::*; 64 | 65 | codec!(Msgpack, {rmp_serde::to_vec, rmp_serde::from_slice}); 66 | } 67 | 68 | #[cfg(feature = "json-value")] 69 | mod json_value { 70 | use super::*; 71 | 72 | codec!(Json, {serde_json::to_vec, serde_json::from_slice}); 73 | 74 | impl std::fmt::Display for Json { 75 | fn fmt(&self, w: &mut std::fmt::Formatter) -> std::fmt::Result { 76 | let s = match serde_json::to_string_pretty(&self.0) { 77 | Ok(s) => s, 78 | Err(_) => return Err(std::fmt::Error), 79 | }; 80 | write!(w, "{}", s)?; 81 | Ok(()) 82 | } 83 | } 84 | } 85 | 86 | #[cfg(feature = "bincode-value")] 87 | mod bincode_value { 88 | use super::*; 89 | 90 | codec!(Bincode, {bincode::serialize, bincode::deserialize}); 91 | } 92 | 93 | #[cfg(feature = "lexpr-value")] 94 | mod lexpr_value { 95 | use super::*; 96 | 97 | codec!(Lexpr, {serde_lexpr::to_vec, serde_lexpr::from_slice}); 98 | } 99 | 100 | #[cfg(feature = "json-value")] 101 | pub use json_value::Json; 102 | 103 | #[cfg(feature = "msgpack-value")] 104 | pub use msgpack_value::Msgpack; 105 | 106 | #[cfg(feature = "bincode-value")] 107 | pub use bincode_value::Bincode; 108 | 109 | #[cfg(feature = "lexpr-value")] 110 | pub use lexpr_value::Lexpr; 111 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | use std::path::{Path, PathBuf}; 2 | use std::{fs, io}; 3 | 4 | use crate::error::Error; 5 | use serde::{Deserialize, Serialize}; 6 | 7 | /// Config is used to create a new store 8 | #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] 9 | pub struct Config { 10 | /// The `path` field determines where the database will be created 11 | pub path: PathBuf, 12 | 13 | /// The `temporary` field specifies if the database will be destroyed on close 14 | #[serde(default)] 15 | pub temporary: bool, 16 | 17 | /// Enable compression by setting `use_compression` to true 18 | #[serde(default)] 19 | pub use_compression: bool, 20 | 21 | /// Specify the flush frequency 22 | #[serde(default)] 23 | pub flush_every_ms: Option, 24 | 25 | /// Specify the cache capacity in bytes 26 | #[serde(default)] 27 | pub cache_capacity: Option, 28 | 29 | /// Specify the segment size for compatibility 30 | #[serde(default)] 31 | pub segment_size: Option, 32 | } 33 | 34 | impl Config { 35 | /// Create a default configuration object 36 | pub fn new>(p: P) -> Config { 37 | Config { 38 | path: p.as_ref().to_path_buf(), 39 | temporary: false, 40 | use_compression: false, 41 | flush_every_ms: None, 42 | cache_capacity: None, 43 | segment_size: None, 44 | } 45 | } 46 | 47 | /// Save Config to an io::Write 48 | pub fn save_to(&self, mut w: W) -> Result<(), Error> { 49 | let s = match toml::to_string(self) { 50 | Ok(s) => s, 51 | Err(_) => return Err(Error::InvalidConfiguration), 52 | }; 53 | w.write_all(s.as_ref())?; 54 | Ok(()) 55 | } 56 | 57 | /// Save Config to a file 58 | pub fn save>(&self, path: P) -> Result<(), Error> { 59 | let file = fs::File::create(path.as_ref())?; 60 | self.save_to(file) 61 | } 62 | 63 | /// Load configuration from an io::Read 64 | pub fn load_from(mut r: R) -> Result { 65 | let mut buf = Vec::new(); 66 | r.read_to_end(&mut buf)?; 67 | match toml::from_slice(buf.as_ref()) { 68 | Ok(cfg) => Ok(cfg), 69 | Err(_) => Err(Error::InvalidConfiguration), 70 | } 71 | } 72 | 73 | /// Load configuration to a file 74 | pub fn load>(path: P) -> Result { 75 | let file = fs::File::open(path.as_ref())?; 76 | Self::load_from(file) 77 | } 78 | 79 | /// Set compression field 80 | pub fn use_compression(mut self, use_compression: bool) -> Config { 81 | self.use_compression = use_compression; 82 | self 83 | } 84 | 85 | /// Toggle `temporary` value 86 | pub fn temporary(mut self, temporary: bool) -> Config { 87 | self.temporary = temporary; 88 | self 89 | } 90 | 91 | /// Set flush frequency 92 | pub fn flush_every_ms(mut self, ms: u64) -> Config { 93 | self.flush_every_ms = Some(ms); 94 | self 95 | } 96 | 97 | /// Set cache capacity 98 | pub fn cache_capacity(mut self, bytes: u64) -> Config { 99 | self.cache_capacity = Some(bytes); 100 | self 101 | } 102 | 103 | /// Set cache capacity 104 | pub fn segment_size(mut self, kb: usize) -> Config { 105 | self.segment_size = Some(kb); 106 | self 107 | } 108 | 109 | pub(crate) fn open(&mut self) -> Result { 110 | let config = sled::Config::new() 111 | .path(&self.path) 112 | .temporary(self.temporary) 113 | .flush_every_ms(self.flush_every_ms) 114 | .use_compression(self.use_compression); 115 | let config = if let Some(cache_capacity) = self.cache_capacity { 116 | config.cache_capacity(cache_capacity) 117 | } else { 118 | config 119 | }; 120 | let config = if let Some(segment_size) = self.segment_size { 121 | // allow old database to work 122 | config.segment_size(segment_size) 123 | } else { 124 | config 125 | }; 126 | let db = config.open()?; 127 | Ok(db) 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use std::sync::PoisonError; 3 | 4 | use thiserror::Error as TError; 5 | 6 | #[derive(Debug, TError)] 7 | /// Error type 8 | pub enum Error { 9 | /// A Sled error 10 | #[error("Error in Sled: {0}")] 11 | Sled(#[from] sled::Error), 12 | 13 | /// Error when calling `Bucket::compare_and_swap` 14 | #[error("Compare and swap error: {0}")] 15 | CompareAndSwap(#[from] sled::CompareAndSwapError), 16 | 17 | /// An IO error 18 | #[error("IO error: {0}")] 19 | IO(#[from] io::Error), 20 | 21 | /// Configuration is invalid 22 | #[error("Configuration is invalid")] 23 | InvalidConfiguration, 24 | 25 | /// RwLock is poisoned 26 | #[error("RwLock is poisoned")] 27 | Poison, 28 | 29 | /// UTF8 Error 30 | #[error("UTF8 error")] 31 | Utf8(std::str::Utf8Error), 32 | 33 | /// String UTF8 Error 34 | #[error("String UTF8 error")] 35 | FromUtf8(std::string::FromUtf8Error), 36 | 37 | /// SystemTime 38 | #[error("SystemTime: {0}")] 39 | SystemTime(#[from] std::time::SystemTimeError), 40 | 41 | /// Generic message 42 | #[error("Message: {0}")] 43 | Message(String), 44 | 45 | /// Json error 46 | #[cfg(feature = "json-value")] 47 | #[error("JSON error: {0}")] 48 | Json(#[from] serde_json::Error), 49 | 50 | /// Msgpack encoding error 51 | #[cfg(feature = "msgpack-value")] 52 | #[error("Msgpack encoding error: {0}")] 53 | MsgpackEncode(#[from] rmp_serde::encode::Error), 54 | 55 | /// Msgpack decoding error 56 | #[cfg(feature = "msgpack-value")] 57 | #[error("Msgpack decoding error: {0}")] 58 | MsgpackDecode(#[from] rmp_serde::decode::Error), 59 | 60 | /// Bincode error 61 | #[cfg(feature = "bincode-value")] 62 | #[error("Bincode encoding Error: {0}")] 63 | Bincode(#[from] Box), 64 | 65 | /// Lexpr error 66 | #[cfg(feature = "lexpr-value")] 67 | #[error("S-Expression error: {0}")] 68 | Lexpr(#[from] serde_lexpr::Error), 69 | } 70 | 71 | impl From> for Error { 72 | fn from(_: PoisonError) -> Error { 73 | Error::Poison 74 | } 75 | } 76 | 77 | impl From for Error { 78 | fn from(e: std::str::Utf8Error) -> Error { 79 | Error::Utf8(e) 80 | } 81 | } 82 | 83 | impl From for Error { 84 | fn from(e: std::string::FromUtf8Error) -> Error { 85 | Error::FromUtf8(e) 86 | } 87 | } 88 | 89 | impl From for sled::transaction::ConflictableTransactionError { 90 | fn from(e: Error) -> sled::transaction::ConflictableTransactionError { 91 | sled::transaction::ConflictableTransactionError::Abort(e) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/key.rs: -------------------------------------------------------------------------------- 1 | use std::mem; 2 | use std::time::SystemTime; 3 | 4 | use crate::{Error, Raw}; 5 | 6 | /// A Key can be used as a key to a database 7 | pub trait Key<'a>: Sized + AsRef<[u8]> { 8 | /// Convert from Raw 9 | fn from_raw_key(r: &'a Raw) -> Result; 10 | 11 | /// Wrapper around AsRef<[u8]> 12 | fn to_raw_key(&self) -> Result { 13 | Ok(self.as_ref().into()) 14 | } 15 | } 16 | 17 | impl<'a> Key<'a> for Raw { 18 | fn from_raw_key(x: &Raw) -> Result { 19 | Ok(x.clone()) 20 | } 21 | } 22 | 23 | impl<'a> Key<'a> for &'a [u8] { 24 | fn from_raw_key(x: &'a Raw) -> Result<&'a [u8], Error> { 25 | Ok(x.as_ref()) 26 | } 27 | } 28 | 29 | impl<'a> Key<'a> for &'a str { 30 | fn from_raw_key(x: &'a Raw) -> Result { 31 | Ok(std::str::from_utf8(x.as_ref())?) 32 | } 33 | } 34 | 35 | impl<'a> Key<'a> for Vec { 36 | fn from_raw_key(r: &Raw) -> Result { 37 | Ok(r.to_vec()) 38 | } 39 | } 40 | 41 | impl<'a> Key<'a> for String { 42 | fn from_raw_key(x: &Raw) -> Result { 43 | Ok(std::str::from_utf8(x.as_ref())?.to_string()) 44 | } 45 | } 46 | 47 | impl<'a> Key<'a> for Integer { 48 | fn from_raw_key(x: &Raw) -> Result { 49 | Ok(Integer::from(x.as_ref())) 50 | } 51 | } 52 | 53 | /// Integer key type 54 | #[derive(Debug, Clone, Copy, PartialEq, PartialOrd)] 55 | pub struct Integer([u8; 16]); 56 | 57 | impl From for Integer { 58 | fn from(i: u128) -> Integer { 59 | unsafe { Integer(mem::transmute(i.to_be())) } 60 | } 61 | } 62 | 63 | impl From for Integer { 64 | fn from(i: u64) -> Integer { 65 | let i = i as u128; 66 | i.into() 67 | } 68 | } 69 | 70 | impl From for Integer { 71 | fn from(i: u32) -> Integer { 72 | let i = i as u128; 73 | i.into() 74 | } 75 | } 76 | 77 | impl From for Integer { 78 | fn from(i: i32) -> Integer { 79 | let i = i as u128; 80 | i.into() 81 | } 82 | } 83 | 84 | impl From for Integer { 85 | fn from(i: usize) -> Integer { 86 | let i = i as u128; 87 | i.into() 88 | } 89 | } 90 | 91 | impl From for u128 { 92 | #[cfg(target_endian = "big")] 93 | fn from(i: Integer) -> u128 { 94 | unsafe { mem::transmute(i.0) } 95 | } 96 | 97 | #[cfg(target_endian = "little")] 98 | fn from(i: Integer) -> u128 { 99 | u128::from_be(unsafe { mem::transmute(i.0) }) 100 | } 101 | } 102 | 103 | impl From for u64 { 104 | fn from(i: Integer) -> u64 { 105 | let i: u128 = i.into(); 106 | i as u64 107 | } 108 | } 109 | 110 | impl From for usize { 111 | fn from(i: Integer) -> usize { 112 | let i: u128 = i.into(); 113 | i as usize 114 | } 115 | } 116 | 117 | impl AsRef<[u8]> for Integer { 118 | fn as_ref(&self) -> &[u8] { 119 | &self.0 120 | } 121 | } 122 | 123 | impl<'a> From<&'a [u8]> for Integer { 124 | fn from(buf: &'a [u8]) -> Integer { 125 | let mut dst = Integer::from(0u128); 126 | dst.0[..16].clone_from_slice(&buf[..16]); 127 | dst 128 | } 129 | } 130 | 131 | impl Integer { 132 | /// Current timestamp in seconds from the Unix epoch 133 | pub fn timestamp() -> Result { 134 | let ts = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?; 135 | Ok(Integer::from(ts.as_secs() as u128)) 136 | } 137 | 138 | /// Current timestamp in milliseconds from the Unix epoch 139 | pub fn timestamp_ms() -> Result { 140 | let ts = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?; 141 | Ok(Integer::from(ts.as_millis())) 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(missing_docs)] 2 | 3 | //! `kv` is a simple way to embed a key/value store in Rust applications. It is built using 4 | //! [sled](https://docs.rs/sled) and aims to be as lightweight as possible while still 5 | //! providing a nice high level interface. 6 | //! 7 | //! ## Getting started 8 | //! 9 | //! ```rust 10 | //! use kv::*; 11 | //! 12 | //! #[derive(serde::Serialize, serde::Deserialize, PartialEq)] 13 | //! struct SomeType { 14 | //! a: i32, 15 | //! b: i32 16 | //! } 17 | //! 18 | //! fn run() -> Result<(), Error> { 19 | //! // Configure the database 20 | //! let mut cfg = Config::new("./test/example1"); 21 | //! 22 | //! // Open the key/value store 23 | //! let store = Store::new(cfg)?; 24 | //! 25 | //! // A Bucket provides typed access to a section of the key/value store 26 | //! let test = store.bucket::(Some("test"))?; 27 | //! 28 | //! let key = Raw::from(b"test"); 29 | //! let value = Raw::from(b"123"); 30 | //! 31 | //! // Set test = 123 32 | //! test.set(&key, &value)?; 33 | //! assert!(test.get(&key).unwrap().unwrap() == value); 34 | //! assert!(test.get(&b"something else".into()).unwrap() == None); 35 | //! 36 | //! // Integer keys 37 | //! let aaa = store.bucket::(Some("aaa"))?; 38 | //! let key = Integer::from(1); 39 | //! let value = String::from("Testing"); 40 | //! aaa.set(&key, &value); 41 | //! 42 | //! #[cfg(feature = "json-value")] 43 | //! { 44 | //! // Using a Json encoded type is easy, thanks to Serde 45 | //! let bucket = store.bucket::<&str, Json>(None)?; 46 | //! 47 | //! let k = "example"; 48 | //! let x = Json(SomeType {a: 1, b: 2}); 49 | //! bucket.set(&k, &x)?; 50 | //! 51 | //! let x: Json = bucket.get(&k)?.unwrap(); 52 | //! 53 | //! for item in bucket.iter() { 54 | //! let item = item?; 55 | //! let key: String = item.key()?; 56 | //! let value = item.value::>()?; 57 | //! println!("key: {}, value: {}", key, value); 58 | //! } 59 | //! 60 | //! // A transaction 61 | //! bucket.transaction(|txn| { 62 | //! txn.set(&"x", &Json(SomeType {a: 1, b: 2}))?; 63 | //! txn.set(&"y", &Json(SomeType {a: 3, b: 4}))?; 64 | //! txn.set(&"z", &Json(SomeType {a: 5, b: 6}))?; 65 | //! 66 | //! Ok(()) 67 | //! })?; 68 | //! } 69 | //! Ok(()) 70 | //! } 71 | //! # 72 | //! # fn main() { 73 | //! # run().unwrap(); 74 | //! # } 75 | //! ``` 76 | 77 | mod bucket; 78 | mod codec; 79 | mod config; 80 | mod error; 81 | mod key; 82 | mod store; 83 | mod transaction; 84 | mod value; 85 | 86 | pub use bucket::{Batch, Bucket, Event, Item, Iter, Watch}; 87 | pub use codec::*; 88 | pub use config::Config; 89 | pub use error::Error; 90 | pub use key::{Integer, Key}; 91 | pub use store::Store; 92 | pub use transaction::{Transaction, TransactionError}; 93 | pub use value::{Raw, Value}; 94 | 95 | /// Abort a transaction 96 | pub fn abort(x: E) -> TransactionError { 97 | TransactionError::Abort(x) 98 | } 99 | 100 | #[cfg(test)] 101 | mod tests; 102 | -------------------------------------------------------------------------------- /src/store.rs: -------------------------------------------------------------------------------- 1 | use std::path::Path; 2 | 3 | use crate::{Bucket, Config, Error, Key, Value}; 4 | 5 | /// Store is used to read/write data to disk using `sled` 6 | #[derive(Clone, Debug)] 7 | pub struct Store { 8 | config: Config, 9 | db: sled::Db, 10 | } 11 | 12 | impl Store { 13 | /// Create a new store from the given config 14 | pub fn new(mut config: Config) -> Result { 15 | Ok(Store { 16 | db: config.open()?, 17 | config, 18 | }) 19 | } 20 | 21 | /// Get the store's path 22 | pub fn path(&self) -> Result<&Path, Error> { 23 | Ok(self.config.path.as_path()) 24 | } 25 | 26 | /// Generate monotonic ID 27 | pub fn generate_id(&self) -> Result { 28 | let id = self.db.generate_id()?; 29 | Ok(id) 30 | } 31 | 32 | /// Get a list of bucket names 33 | pub fn buckets(&self) -> Vec { 34 | self.db 35 | .tree_names() 36 | .into_iter() 37 | .map(|x| String::from_utf8(x.to_vec())) 38 | .filter_map(|x| match x { 39 | Ok(x) => Some(x), 40 | Err(_) => None, 41 | }) 42 | .collect() 43 | } 44 | 45 | /// Open a new bucket 46 | pub fn bucket<'a, K: Key<'a>, V: Value>( 47 | &self, 48 | name: Option<&str>, 49 | ) -> Result, Error> { 50 | let t = self.db.open_tree(name.unwrap_or("__sled__default"))?; 51 | Ok(Bucket::new(t)) 52 | } 53 | 54 | /// Remove a bucket from the store 55 | pub fn drop_bucket>(&self, name: S) -> Result<(), Error> { 56 | self.db.drop_tree(name.as_ref().as_bytes())?; 57 | Ok(()) 58 | } 59 | 60 | /// Returns the size on disk in bytes 61 | pub fn size_on_disk(&self) -> Result { 62 | let i = self.db.size_on_disk()?; 63 | Ok(i) 64 | } 65 | 66 | /// Export entire database 67 | pub fn export(&self) -> Vec<(Vec, Vec, impl Iterator>>)> { 68 | self.db.export() 69 | } 70 | 71 | /// Import from database export 72 | pub fn import(&self, export: Vec<(Vec, Vec, impl Iterator>>)>) { 73 | self.db.import(export) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/tests.rs: -------------------------------------------------------------------------------- 1 | use std::{fs, path}; 2 | 3 | use crate::*; 4 | 5 | fn reset(name: &str) -> String { 6 | let s = format!("./test/{}", name); 7 | let _ = fs::remove_dir_all(&s); 8 | s 9 | } 10 | 11 | #[test] 12 | fn test_basic() { 13 | let path = reset("basic"); 14 | 15 | // Create a new store 16 | let cfg = Config::new(path.clone()); 17 | let store = Store::new(cfg).unwrap(); 18 | let bucket = store.bucket::(None).unwrap(); 19 | assert!(path::Path::new(path.as_str()).exists()); 20 | 21 | let key = Raw::from("testing"); 22 | let value = Raw::from(b"abc123"); 23 | bucket.set(&key, &value).unwrap(); 24 | assert_eq!(bucket.get(&key).unwrap().unwrap(), b"abc123"); 25 | } 26 | 27 | #[test] 28 | fn test_integer_keys() { 29 | let path = reset("integer_keys"); 30 | 31 | let cfg = Config::new(path.clone()); 32 | let store = Store::new(cfg).unwrap(); 33 | let bucket = store.bucket::(None).unwrap(); 34 | assert!(path::Path::new(path.as_str()).exists()); 35 | 36 | let key = Integer::from(0x1234); 37 | bucket.set(&key, &Raw::from("abc123")).unwrap(); 38 | assert_eq!(bucket.get(&key).unwrap().unwrap(), b"abc123"); 39 | } 40 | 41 | #[test] 42 | fn test_iter() { 43 | let path = reset("iter"); 44 | 45 | let cfg = Config::new(path.clone()); 46 | let store = Store::new(cfg).unwrap(); 47 | let bucket = store.bucket::(None).unwrap(); 48 | assert!(path::Path::new(path.as_str()).exists()); 49 | 50 | for i in 0..100 { 51 | bucket.set(&i.into(), &format!("{}", i)).unwrap(); 52 | } 53 | 54 | let iter = bucket.iter(); 55 | 56 | iter.enumerate().for_each(|(index, item)| { 57 | let item = item.unwrap(); 58 | let key: u128 = item.key().unwrap(); 59 | assert_eq!(key, index as u128); 60 | assert_eq!(item.value::().unwrap(), format!("{}", index)); 61 | }); 62 | } 63 | 64 | #[cfg(feature = "msgpack-value")] 65 | #[test] 66 | fn test_msgpack_encoding() { 67 | use crate::Msgpack; 68 | let path = reset("msgpack"); 69 | 70 | #[derive(Debug, serde::Serialize, serde::Deserialize, PartialEq)] 71 | struct Testing { 72 | a: i32, 73 | b: String, 74 | } 75 | 76 | let cfg = Config::new(path.clone()); 77 | let store = Store::new(cfg).unwrap(); 78 | let bucket = store.bucket::<&str, Msgpack>(None).unwrap(); 79 | assert!(path::Path::new(path.as_str()).exists()); 80 | 81 | let key = "testing"; 82 | bucket 83 | .set( 84 | &key, 85 | &Msgpack(Testing { 86 | a: 1, 87 | b: "field".into(), 88 | }), 89 | ) 90 | .unwrap(); 91 | 92 | let v = bucket.get(&key).unwrap(); 93 | assert_eq!( 94 | v.unwrap().0, 95 | Testing { 96 | a: 1, 97 | b: "field".into(), 98 | } 99 | ); 100 | } 101 | 102 | #[cfg(feature = "json-value")] 103 | #[test] 104 | fn test_json_encoding() { 105 | use crate::Json; 106 | let path = reset("json"); 107 | 108 | #[derive(Debug, serde::Serialize, serde::Deserialize, PartialEq)] 109 | struct Testing { 110 | a: i32, 111 | b: String, 112 | } 113 | 114 | let cfg = Config::new(path.clone()); 115 | let store = Store::new(cfg).unwrap(); 116 | let bucket = store.bucket::<&str, Json>(None).unwrap(); 117 | assert!(path::Path::new(path.as_str()).exists()); 118 | 119 | let key = "testing"; 120 | bucket 121 | .set( 122 | &key, 123 | &Json(Testing { 124 | a: 1, 125 | b: "field".into(), 126 | }), 127 | ) 128 | .unwrap(); 129 | 130 | let v = bucket.get(&key).unwrap(); 131 | assert_eq!( 132 | v.unwrap().0, 133 | Testing { 134 | a: 1, 135 | b: "field".into(), 136 | } 137 | ); 138 | } 139 | 140 | #[cfg(feature = "bincode-value")] 141 | #[test] 142 | fn test_bincode_encoding() { 143 | use crate::Bincode; 144 | let path = reset("bincode"); 145 | 146 | #[derive(Debug, serde::Serialize, serde::Deserialize, PartialEq)] 147 | struct Testing { 148 | a: i32, 149 | b: String, 150 | } 151 | 152 | let cfg = Config::new(path.clone()); 153 | let store = Store::new(cfg).unwrap(); 154 | let bucket = store.bucket::<&str, Bincode>(None).unwrap(); 155 | assert!(path::Path::new(path.as_str()).exists()); 156 | 157 | let key = "testing"; 158 | bucket 159 | .set( 160 | &key, 161 | &Bincode(Testing { 162 | a: 1, 163 | b: "field".into(), 164 | }), 165 | ) 166 | .unwrap(); 167 | 168 | let v = bucket.get(&key).unwrap(); 169 | assert_eq!( 170 | v.unwrap().0, 171 | Testing { 172 | a: 1, 173 | b: "field".into(), 174 | } 175 | ); 176 | } 177 | 178 | #[cfg(feature = "lexpr-value")] 179 | #[test] 180 | fn test_sexpr_encoding() { 181 | use crate::Lexpr; 182 | let path = reset("sexpr"); 183 | 184 | #[derive(Debug, serde::Serialize, serde::Deserialize, PartialEq)] 185 | struct Testing { 186 | a: i32, 187 | b: String, 188 | } 189 | 190 | let cfg = Config::new(path.clone()); 191 | let store = Store::new(cfg).unwrap(); 192 | let bucket = store.bucket::<&str, Lexpr>(None).unwrap(); 193 | assert!(path::Path::new(path.as_str()).exists()); 194 | 195 | let key = "testing"; 196 | let value = Lexpr(Testing { 197 | a: 1, 198 | b: "field".into(), 199 | }); 200 | 201 | bucket.set(&key, &value).unwrap(); 202 | let v = bucket.get(&key).unwrap(); 203 | assert_eq!( 204 | v.unwrap().0, 205 | Testing { 206 | a: 1, 207 | b: "field".into(), 208 | } 209 | ); 210 | } 211 | 212 | #[test] 213 | fn test_config_encoding() { 214 | let mut cfg = Config::new("./test"); 215 | cfg.use_compression = true; 216 | cfg.save("./config").unwrap(); 217 | let cfg2 = Config::load("./config").unwrap(); 218 | assert!(cfg == cfg2); 219 | let _ = fs::remove_file("./config"); 220 | } 221 | 222 | #[test] 223 | fn test_watch() { 224 | let path = reset("watch"); 225 | let cfg = Config::new(path.clone()); 226 | let store = Store::new(cfg).unwrap(); 227 | let bucket = store.bucket::<&str, Raw>(Some("watch")).unwrap(); 228 | let mut watch = bucket.watch_prefix(None).unwrap(); 229 | 230 | let key = "abc"; 231 | bucket.set(&key, &Raw::from(b"123")).unwrap(); 232 | 233 | let next = watch.next().unwrap(); 234 | let next = next.unwrap(); 235 | 236 | assert!(next.is_set()); 237 | assert!(next.value().unwrap().unwrap() == b"123"); 238 | assert!(next.key().unwrap() == "abc"); 239 | 240 | bucket.remove(&key).unwrap(); 241 | 242 | let next = watch.next().unwrap(); 243 | let next = next.unwrap(); 244 | 245 | assert!(next.is_remove()); 246 | assert!(next.value().unwrap() == None); 247 | assert!(next.key().unwrap() == "abc"); 248 | } 249 | -------------------------------------------------------------------------------- /src/transaction.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | use crate::{Batch, Error, Key, Value}; 4 | 5 | /// Transaction error 6 | pub type TransactionError = sled::transaction::ConflictableTransactionError; 7 | 8 | /// Transaction 9 | #[derive(Clone)] 10 | pub struct Transaction<'a, 'b, K: Key<'a>, V: Value>( 11 | &'b sled::transaction::TransactionalTree, 12 | PhantomData, 13 | PhantomData, 14 | PhantomData<&'a ()>, 15 | ); 16 | 17 | impl<'a, 'b, K: Key<'a>, V: Value> Transaction<'a, 'b, K, V> { 18 | pub(crate) fn new(t: &'b sled::transaction::TransactionalTree) -> Self { 19 | Transaction(t, PhantomData, PhantomData, PhantomData) 20 | } 21 | 22 | /// Get the value associated with the specified key 23 | pub fn get(&self, key: &K) -> Result, TransactionError> { 24 | let v = self 25 | .0 26 | .get(key.to_raw_key().map_err(TransactionError::Abort)?)?; 27 | 28 | match v { 29 | None => Ok(None), 30 | Some(x) => Ok(Some(V::from_raw_value(x).map_err(TransactionError::Abort)?)), 31 | } 32 | } 33 | 34 | /// Returns true if the bucket contains the given key 35 | pub fn contains(&self, key: &K) -> Result> { 36 | let v = self 37 | .0 38 | .get(key.to_raw_key().map_err(TransactionError::Abort)?)?; 39 | Ok(v.is_some()) 40 | } 41 | 42 | /// Set the value associated with the specified key to the provided value 43 | pub fn set(&self, key: &K, value: &V) -> Result, TransactionError> { 44 | let v = value.to_raw_value().map_err(TransactionError::Abort)?; 45 | Ok(self 46 | .0 47 | .insert(key.to_raw_key().map_err(TransactionError::Abort)?, v)? 48 | .map(|x| V::from_raw_value(x).map_err(TransactionError::Abort)) 49 | // https://users.rust-lang.org/t/convenience-method-for-flipping-option-result-to-result-option/13695/7 50 | .map_or(Ok(None), |v| v.map(Some))?) 51 | } 52 | 53 | /// Remove the value associated with the specified key from the database 54 | pub fn remove(&self, key: &K) -> Result, TransactionError> { 55 | Ok(self 56 | .0 57 | .remove(key.to_raw_key().map_err(TransactionError::Abort)?)? 58 | .map(|x| V::from_raw_value(x).map_err(TransactionError::Abort)) 59 | // https://users.rust-lang.org/t/convenience-method-for-flipping-option-result-to-result-option/13695/7 60 | .map_or(Ok(None), |v| v.map(Some))?) 61 | } 62 | 63 | /// Apply batch update 64 | pub fn batch(&self, batch: &Batch) -> Result<(), TransactionError> { 65 | self.0.apply_batch(&batch.0)?; 66 | Ok(()) 67 | } 68 | 69 | /// Generate a monotonic ID. Not guaranteed to be contiguous or idempotent, can produce different values in the same transaction in case of conflicts 70 | pub fn generate_id(&self) -> Result> { 71 | Ok(self.0.generate_id()?) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/value.rs: -------------------------------------------------------------------------------- 1 | use crate::Error; 2 | 3 | /// A trait used to convert between types and `Raw` 4 | pub trait Value: Sized { 5 | /// Wrapper around AsRef<[u8]> 6 | fn to_raw_value(&self) -> Result; 7 | 8 | /// Convert from Raw 9 | fn from_raw_value(r: Raw) -> Result; 10 | } 11 | 12 | /// Raw is an alias for `sled::IVec` 13 | pub type Raw = sled::IVec; 14 | 15 | impl Value for Raw { 16 | fn to_raw_value(&self) -> Result { 17 | Ok(self.clone()) 18 | } 19 | 20 | fn from_raw_value(r: Raw) -> Result { 21 | Ok(r) 22 | } 23 | } 24 | 25 | impl Value for std::sync::Arc<[u8]> { 26 | fn to_raw_value(&self) -> Result { 27 | Ok(self.as_ref().into()) 28 | } 29 | 30 | fn from_raw_value(r: Raw) -> Result { 31 | Ok(r.as_ref().into()) 32 | } 33 | } 34 | 35 | impl Value for Vec { 36 | fn to_raw_value(&self) -> Result { 37 | Ok(self.as_slice().into()) 38 | } 39 | 40 | fn from_raw_value(r: Raw) -> Result { 41 | Ok(r.to_vec()) 42 | } 43 | } 44 | 45 | impl Value for String { 46 | fn to_raw_value(&self) -> Result { 47 | Ok(self.as_str().into()) 48 | } 49 | 50 | fn from_raw_value(r: Raw) -> Result { 51 | let x = r.to_vec(); 52 | Ok(String::from_utf8(x)?) 53 | } 54 | } 55 | --------------------------------------------------------------------------------