From aa174a6f6a314f0356c994407589fa8f36012565 Mon Sep 17 00:00:00 2001 From: Ziyang Hu Date: Thu, 10 Nov 2022 23:09:10 +0800 Subject: [PATCH] refactor storage API --- Cargo.lock | 73 +++++++++++++++++- Cargo.toml | 1 + cozorocks/src/bridge/tx.rs | 8 +- src/query/stored.rs | 1 + src/runtime/db.rs | 36 ++++----- src/runtime/relation.rs | 111 ++++++++++++++------------- src/runtime/transact.rs | 6 +- src/storage/mod.rs | 26 +++++-- src/storage/rocks.rs | 150 ++++++++++++++++++++++++++++--------- src/storage/sled.rs | 122 ++++++++++++++++++++++++++++++ 10 files changed, 417 insertions(+), 117 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d3e454d4..44fb345d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -386,7 +386,7 @@ checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" [[package]] name = "cozo" -version = "0.1.6" +version = "0.1.7" dependencies = [ "approx", "base64", @@ -421,6 +421,7 @@ dependencies = [ "serde_bytes", "serde_derive", "serde_json", + "sled", "smallvec", "smartstring", "thiserror", @@ -719,12 +720,31 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "fs_extra" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2022715d62ab30faffd124d40b76f4134a550a87792276512b18d63272333394" +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + [[package]] name = "generic-array" version = "0.14.6" @@ -959,6 +979,16 @@ dependencies = [ "cc", ] +[[package]] +name = "lock_api" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +dependencies = [ + "autocfg", + "scopeguard", +] + [[package]] name = "log" version = "0.4.17" @@ -1210,6 +1240,31 @@ version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" +[[package]] +name = "parking_lot" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" +dependencies = [ + "cfg-if 1.0.0", + "instant", + "libc", + "redox_syscall", + "smallvec", + "winapi", +] + [[package]] name = "parse-zoneinfo" version = "0.3.0" @@ -1765,6 +1820,22 @@ version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" +[[package]] +name = "sled" +version = "0.34.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f96b4737c2ce5987354855aed3797279def4ebf734436c6aa4552cf8e169935" +dependencies = [ + "crc32fast", + "crossbeam-epoch", + "crossbeam-utils", + "fs2", + "fxhash", + "libc", + "log", + "parking_lot", +] + [[package]] name = "smallvec" version = "1.10.0" diff --git a/Cargo.toml b/Cargo.toml index 9ba179da..af03ea21 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,6 +57,7 @@ uuid = { version = "1.1.2", features = ["v1", "v4", "serde"] } csv = "1.1.6" tikv-jemallocator-global = { version = "0.5.0", optional = true } cozorocks = { path = "cozorocks", version = "0.1.0" } +sled = "0.34.7" clap = { version = "3.2.8", features = ["derive"] } rouille = "3.5.0" diff --git a/cozorocks/src/bridge/tx.rs b/cozorocks/src/bridge/tx.rs index fe700df3..1850b28e 100644 --- a/cozorocks/src/bridge/tx.rs +++ b/cozorocks/src/bridge/tx.rs @@ -25,6 +25,12 @@ impl Deref for PinSlice { } } +impl AsRef<[u8]> for PinSlice { + fn as_ref(&self) -> &[u8] { + self as &[u8] + } +} + impl Debug for PinSlice { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { let to_d: &[u8] = self; @@ -176,6 +182,6 @@ impl Tx { IterBuilder { inner: self.inner.iterator(), } - .auto_prefix_mode(true) + .auto_prefix_mode(true) } } diff --git a/src/query/stored.rs b/src/query/stored.rs index 37972ffa..14e9d6d3 100644 --- a/src/query/stored.rs +++ b/src/query/stored.rs @@ -21,6 +21,7 @@ use crate::parse::parse_script; use crate::runtime::relation::{AccessLevel, InputRelationHandle, InsufficientAccessLevel}; use crate::runtime::transact::SessionTx; use crate::Db; +use crate::storage::StoreTx; #[derive(Debug, Error, Diagnostic)] #[error("attempting to write into relation {0} of arity {1} with data of arity {2}")] diff --git a/src/runtime/db.rs b/src/runtime/db.rs index 6bd2732c..55cfe39c 100644 --- a/src/runtime/db.rs +++ b/src/runtime/db.rs @@ -21,7 +21,7 @@ use serde_json::{json, Map}; use smartstring::SmartString; use thiserror::Error; -use cozorocks::{DbBuilder, RocksDb}; +use cozorocks::DbBuilder; use crate::data::json::JsonValue; use crate::data::program::{InputProgram, QueryAssertion, RelationOp}; @@ -36,6 +36,8 @@ use crate::query::relation::{ }; use crate::runtime::relation::{RelationHandle, RelationId}; use crate::runtime::transact::SessionTx; +use crate::storage::rocks::RocksDbStorage; +use crate::storage::{Storage, StoreTx}; struct RunningQueryHandle { started_at: f64, @@ -66,7 +68,7 @@ const CURRENT_STORAGE_VERSION: u64 = 1; /// The database object of Cozo. #[derive(Clone)] pub struct Db { - db: RocksDb, + db: RocksDbStorage, relation_store_id: Arc, queries_count: Arc, running_queries: Arc>>, @@ -146,7 +148,7 @@ impl Db { let db = db_builder.build()?; let ret = Self { - db, + db: RocksDbStorage::new(db), relation_store_id: Arc::new(Default::default()), queries_count: Arc::new(Default::default()), running_queries: Arc::new(Mutex::new(Default::default())), @@ -170,7 +172,7 @@ impl Db { } fn transact(&self) -> Result { let ret = SessionTx { - tx: self.db.transact().set_snapshot(true).start(), + tx: self.db.transact()?, mem_store_id: Default::default(), relation_store_id: self.relation_store_id.clone(), }; @@ -178,7 +180,7 @@ impl Db { } fn transact_write(&self) -> Result { let ret = SessionTx { - tx: self.db.transact().set_snapshot(true).start(), + tx: self.db.transact()?, mem_store_id: Default::default(), relation_store_id: self.relation_store_id.clone(), }; @@ -273,7 +275,7 @@ impl Db { assert!(cleanups.is_empty(), "non-empty cleanups on read-only tx"); } for (lower, upper) in cleanups { - self.db.range_del(&lower, &upper)?; + self.db.del_range(&lower, &upper)?; } Ok(res) } @@ -716,7 +718,7 @@ impl Db { } pub(crate) fn remove_relation(&self, name: &Symbol, tx: &mut SessionTx) -> Result<()> { let (lower, upper) = tx.destroy_relation(name)?; - self.db.range_del(&lower, &upper)?; + self.db.del_range(&lower, &upper)?; Ok(()) } pub(crate) fn list_running(&self) -> Result { @@ -763,23 +765,14 @@ impl Db { LARGEST_UTF_CHAR, )))]) .encode_as_key(RelationId::SYSTEM); - let mut it = self - .db - .transact() - .start() - .iterator() - .upper_bound(&upper) - .start(); - it.seek(&lower); + let tx = self.db.transact()?; let mut collected = vec![]; - while let Some((k_slice, v_slice)) = it.pair()? { - if upper.as_slice() <= k_slice { + for kv_res in tx.range_scan_raw(&lower, &upper) { + let (k_slice, v_slice) = kv_res?; + if upper <= k_slice { break; } - // if compare_tuple_keys(&upper, k_slice) != Greater { - // break; - // } - let meta = RelationHandle::decode(v_slice)?; + let meta = RelationHandle::decode(&v_slice)?; let n_keys = meta.metadata.keys.len(); let n_dependents = meta.metadata.non_keys.len(); let arity = n_keys + n_dependents; @@ -795,7 +788,6 @@ impl Db { meta.rm_triggers.len(), meta.replace_triggers.len(), ])); - it.next(); } Ok(json!({"rows": collected, "headers": ["name", "arity", "access_level", "n_keys", "n_non_keys", "n_put_triggers", "n_rm_triggers", "n_replace_triggers"]})) diff --git a/src/runtime/relation.rs b/src/runtime/relation.rs index ebda8f05..4bccd1aa 100644 --- a/src/runtime/relation.rs +++ b/src/runtime/relation.rs @@ -12,16 +12,14 @@ use serde::Serialize; use smartstring::{LazyCompact, SmartString}; use thiserror::Error; -use cozorocks::DbIter; - use crate::data::memcmp::MemCmpEncoder; use crate::data::relation::StoredRelationMetadata; use crate::data::symb::Symbol; -use crate::data::tuple::{ENCODED_KEY_MIN_LEN, Tuple}; +use crate::data::tuple::{Tuple, ENCODED_KEY_MIN_LEN}; use crate::data::value::DataValue; use crate::parse::SourceSpan; use crate::runtime::transact::SessionTx; -use crate::utils::swap_option_result; +use crate::storage::StoreTx; #[derive( Copy, @@ -220,7 +218,7 @@ impl RelationHandle { pub(crate) fn scan_all(&self, tx: &SessionTx) -> impl Iterator> { let lower = Tuple::default().encode_as_key(self.id); let upper = Tuple::default().encode_as_key(self.id.next()); - RelationIterator::new(tx, &lower, &upper) + tx.tx.range_scan(&lower, &upper) } pub(crate) fn scan_prefix( @@ -234,7 +232,9 @@ impl RelationHandle { upper.push(DataValue::Bot); let prefix_encoded = Tuple(lower).encode_as_key(self.id); let upper_encoded = Tuple(upper).encode_as_key(self.id); - RelationIterator::new(tx, &prefix_encoded, &upper_encoded) + // RelationIterator::new(tx, &prefix_encoded, &upper_encoded) + tx.tx + .range_scan(&prefix_encoded, &upper_encoded) } pub(crate) fn scan_bounded_prefix( &self, @@ -250,56 +250,67 @@ impl RelationHandle { upper_t.0.push(DataValue::Bot); let lower_encoded = lower_t.encode_as_key(self.id); let upper_encoded = upper_t.encode_as_key(self.id); - RelationIterator::new(tx, &lower_encoded, &upper_encoded) + tx.tx + .range_scan(&lower_encoded, &upper_encoded) } } -struct RelationIterator { - inner: DbIter, - started: bool, - upper_bound: Vec, -} - -impl RelationIterator { - fn new(sess: &SessionTx, lower: &[u8], upper: &[u8]) -> Self { - let mut inner = sess.tx.iterator().upper_bound(upper).start(); - inner.seek(lower); - Self { - inner, - started: false, - upper_bound: upper.to_vec(), - } - } - fn next_inner(&mut self) -> Result> { - if self.started { - self.inner.next() - } else { - self.started = true; - } - Ok(match self.inner.pair()? { - None => None, - Some((k_slice, v_slice)) => { - if self.upper_bound.as_slice() <= k_slice { - None - } else { - let mut tup = Tuple::decode_from_key(k_slice); - if !v_slice.is_empty() { - let vals: Vec = rmp_serde::from_slice(&v_slice[ENCODED_KEY_MIN_LEN..]).unwrap(); - tup.0.extend(vals); - } - Some(tup) - } - } - }) +// struct RelationIterator { +// inner: DbIter, +// started: bool, +// upper_bound: Vec, +// } + +#[inline] +pub(crate) fn decode_tuple_from_kv(key: &[u8], val: &[u8]) -> Tuple { + let mut tup = Tuple::decode_from_key(key); + if !val.is_empty() { + let vals: Vec = rmp_serde::from_slice(&val[ENCODED_KEY_MIN_LEN..]).unwrap(); + tup.0.extend(vals); } + tup } -impl Iterator for RelationIterator { - type Item = Result; - fn next(&mut self) -> Option { - swap_option_result(self.next_inner()) - } -} +// impl RelationIterator { +// fn new(sess: &SessionTx, lower: &[u8], upper: &[u8]) -> Self { +// let mut inner = sess.tx.iterator().upper_bound(upper).start(); +// inner.seek(lower); +// Self { +// inner, +// started: false, +// upper_bound: upper.to_vec(), +// } +// } +// fn next_inner(&mut self) -> Result> { +// if self.started { +// self.inner.next() +// } else { +// self.started = true; +// } +// Ok(match self.inner.pair()? { +// None => None, +// Some((k_slice, v_slice)) => { +// if self.upper_bound.as_slice() <= k_slice { +// None +// } else { +// let mut tup = Tuple::decode_from_key(k_slice); +// if !v_slice.is_empty() { +// let vals: Vec = rmp_serde::from_slice(&v_slice[ENCODED_KEY_MIN_LEN..]).unwrap(); +// tup.0.extend(vals); +// } +// Some(tup) +// } +// } +// }) +// } +// } +// +// impl Iterator for RelationIterator { +// type Item = Result; +// fn next(&mut self) -> Option { +// swap_option_result(self.next_inner()) +// } +// } #[derive(Debug, Diagnostic, Error)] #[error("Cannot create relation {0} as one with the same name already exists")] diff --git a/src/runtime/transact.rs b/src/runtime/transact.rs index 5f2cab13..84e3e21a 100644 --- a/src/runtime/transact.rs +++ b/src/runtime/transact.rs @@ -7,8 +7,6 @@ use std::sync::atomic::{AtomicU32, AtomicU64, Ordering}; use miette::Result; -use cozorocks::Tx; - use crate::data::program::MagicSymbol; use crate::data::symb::Symbol; use crate::data::tuple::Tuple; @@ -16,9 +14,11 @@ use crate::data::value::DataValue; use crate::parse::SourceSpan; use crate::runtime::in_mem::{InMemRelation, StoredRelationId}; use crate::runtime::relation::RelationId; +use crate::storage::rocks::RocksDbTx; +use crate::storage::StoreTx; pub struct SessionTx { - pub(crate) tx: Tx, + pub(crate) tx: RocksDbTx, pub(crate) relation_store_id: Arc, pub(crate) mem_store_id: Arc, } diff --git a/src/storage/mod.rs b/src/storage/mod.rs index 2ee9d26a..39714ced 100644 --- a/src/storage/mod.rs +++ b/src/storage/mod.rs @@ -4,6 +4,8 @@ use miette::Result; +use crate::data::tuple::Tuple; + pub(crate) mod rocks; pub(crate) mod sled; pub(crate) mod tikv; @@ -11,22 +13,34 @@ pub(crate) mod tikv; pub(crate) trait Storage { type Tx: StoreTx; - fn tx(&self) -> Result; + fn transact(&self) -> Result; fn del_range(&self, lower: &[u8], upper: &[u8]) -> Result<()>; + fn range_compact( + &self, + lower: &[u8], + upper: &[u8], + ) -> Result<()>; } pub(crate) trait StoreTx { type ReadSlice: AsRef<[u8]>; - type IterSlice: AsRef<[u8]>; - type KeyIter: Iterator>; - type KeyValueIter: Iterator>; + type KVIter: Iterator>; + type KVIterRaw: Iterator, Vec)>>; fn get(&self, key: &[u8], for_update: bool) -> Result>; fn put(&mut self, key: &[u8], val: &[u8]) -> Result<()>; fn del(&mut self, key: &[u8]) -> Result<()>; fn exists(&self, key: &[u8], for_update: bool) -> Result; fn commit(&mut self) -> Result<()>; - fn range_scan(&self, lower: &[u8], upper: &[u8]) -> Self::KeyValueIter; - fn range_key_scan(&self, lower: &[u8], upper: &[u8]) -> Self::KeyIter; + fn range_scan( + &self, + lower: &[u8], + upper: &[u8], + ) -> Self::KVIter; + fn range_scan_raw( + &self, + lower: &[u8], + upper: &[u8], + ) -> Self::KVIterRaw; } diff --git a/src/storage/rocks.rs b/src/storage/rocks.rs index 21af33f8..41398de1 100644 --- a/src/storage/rocks.rs +++ b/src/storage/rocks.rs @@ -2,77 +2,159 @@ * Copyright 2022, The Cozo Project Authors. Licensed under MPL-2.0. */ -use miette::Result; +use miette::{IntoDiagnostic, Result}; +use cozorocks::{DbIter, PinSlice, RocksDb, Tx}; + +use crate::data::tuple::Tuple; +use crate::runtime::relation::decode_tuple_from_kv; use crate::storage::{Storage, StoreTx}; +use crate::utils::swap_option_result; + +#[derive(Clone)] +pub(crate) struct RocksDbStorage { + db: RocksDb, +} -struct RocksDbStorage; +impl RocksDbStorage { + pub(crate) fn new(db: RocksDb) -> Self { + Self { db } + } +} impl Storage for RocksDbStorage { type Tx = RocksDbTx; - fn tx(&self) -> miette::Result { - todo!() + fn transact(&self) -> Result { + let db_tx = self.db.transact().set_snapshot(true).start(); + Ok(RocksDbTx { db_tx }) } - fn del_range(&self, lower: &[u8], upper: &[u8]) -> miette::Result<()> { - todo!() + fn del_range(&self, lower: &[u8], upper: &[u8]) -> Result<()> { + Ok(self.db.range_del(lower, upper)?) + } + + fn range_compact(&self, lower: &[u8], upper: &[u8]) -> Result<()> { + self.db.range_compact(lower, upper).into_diagnostic() } } -struct RocksDbTx; +pub(crate) struct RocksDbTx { + db_tx: Tx, +} impl StoreTx for RocksDbTx { - type ReadSlice = Vec; - type IterSlice = Vec; - type KeyIter = RocksDbKeyIter; - type KeyValueIter = RocksDbIter; + type ReadSlice = PinSlice; + type KVIter = RocksDbIterator; + type KVIterRaw = RocksDbIteratorRaw; - fn get(&self, key: &[u8], for_update: bool) -> miette::Result> { - todo!() + #[inline] + fn get(&self, key: &[u8], for_update: bool) -> Result> { + Ok(self.db_tx.get(key, for_update)?) } - fn put(&mut self, key: &[u8], val: &[u8]) -> miette::Result<()> { - todo!() + #[inline] + fn put(&mut self, key: &[u8], val: &[u8]) -> Result<()> { + Ok(self.db_tx.put(key, val)?) } - fn del(&mut self, key: &[u8]) -> miette::Result<()> { - todo!() + #[inline] + fn del(&mut self, key: &[u8]) -> Result<()> { + Ok(self.db_tx.del(key)?) } - fn exists(&self, key: &[u8], for_update: bool) -> miette::Result { - todo!() + #[inline] + fn exists(&self, key: &[u8], for_update: bool) -> Result { + Ok(self.db_tx.exists(key, for_update)?) } - fn commit(&mut self) -> miette::Result<()> { - todo!() + fn commit(&mut self) -> Result<()> { + Ok(self.db_tx.commit()?) } - fn range_scan(&self, lower: &[u8], upper: &[u8]) -> Self::KeyValueIter { - todo!() + fn range_scan(&self, lower: &[u8], upper: &[u8]) -> Self::KVIter { + let mut inner = self.db_tx.iterator().upper_bound(upper).start(); + inner.seek(lower); + RocksDbIterator { + inner, + started: false, + upper_bound: upper.to_vec(), + } } - fn range_key_scan(&self, lower: &[u8], upper: &[u8]) -> Self::KeyIter { - todo!() + fn range_scan_raw(&self, lower: &[u8], upper: &[u8]) -> Self::KVIterRaw { + let mut inner = self.db_tx.iterator().upper_bound(upper).start(); + inner.seek(lower); + RocksDbIteratorRaw { + inner, + started: false, + upper_bound: upper.to_vec(), + } } } -struct RocksDbKeyIter; +pub(crate) struct RocksDbIterator { + inner: DbIter, + started: bool, + upper_bound: Vec, +} -impl Iterator for RocksDbKeyIter { - type Item = Result>; +impl RocksDbIterator { + fn next_inner(&mut self) -> Result> { + if self.started { + self.inner.next() + } else { + self.started = true; + } + Ok(match self.inner.pair()? { + None => None, + Some((k_slice, v_slice)) => { + if self.upper_bound.as_slice() <= k_slice { + None + } else { + Some(decode_tuple_from_kv(k_slice, v_slice)) + } + } + }) + } +} +impl Iterator for RocksDbIterator { + type Item = Result; fn next(&mut self) -> Option { - todo!() + swap_option_result(self.next_inner()) } } -struct RocksDbIter; +pub(crate) struct RocksDbIteratorRaw { + inner: DbIter, + started: bool, + upper_bound: Vec, +} -impl Iterator for RocksDbIter { - type Item = Result<(Vec, Vec)>; +impl RocksDbIteratorRaw { + fn next_inner(&mut self) -> Result, Vec)>> { + if self.started { + self.inner.next() + } else { + self.started = true; + } + Ok(match self.inner.pair()? { + None => None, + Some((k_slice, v_slice)) => { + if self.upper_bound.as_slice() <= k_slice { + None + } else { + Some((k_slice.to_vec(), v_slice.to_vec())) + } + } + }) + } +} +impl Iterator for RocksDbIteratorRaw { + type Item = Result<(Vec, Vec)>; fn next(&mut self) -> Option { - todo!() + swap_option_result(self.next_inner()) } -} \ No newline at end of file +} diff --git a/src/storage/sled.rs b/src/storage/sled.rs index 54b1970e..4cc050dc 100644 --- a/src/storage/sled.rs +++ b/src/storage/sled.rs @@ -2,3 +2,125 @@ * Copyright 2022, The Cozo Project Authors. Licensed under MPL-2.0. */ +use std::collections::BTreeMap; + +use miette::{IntoDiagnostic, Result}; +use sled::transaction::{ConflictableTransactionError, TransactionalTree}; +use sled::{Db, IVec}; + +use crate::data::tuple::Tuple; +use crate::storage::{Storage, StoreTx}; + +#[derive(Clone)] +struct SledStorage { + db: Db, +} + +impl Storage for SledStorage { + type Tx = SledTx; + + fn transact(&self) -> Result { + Ok(SledTx { + db: self.db.clone(), + changes: Default::default(), + }) + } + + fn del_range(&self, lower: &[u8], upper: &[u8]) -> Result<()> { + todo!() + } + + fn range_compact(&self, _lower: &[u8], _upper: &[u8]) -> Result<()> { + Ok(()) + } +} + +struct SledTx { + db: Db, + changes: BTreeMap, Option>>, +} + +impl StoreTx for SledTx { + type ReadSlice = IVec; + type KVIter = SledIter; + type KVIterRaw = SledIterRaw; + + fn get(&self, key: &[u8], _for_update: bool) -> Result> { + Ok(match self.changes.get(key) { + Some(Some(val)) => Some(IVec::from(val as &[u8])), + Some(None) => None, + None => self.db.get(key).into_diagnostic()?, + }) + } + + fn put(&mut self, key: &[u8], val: &[u8]) -> Result<()> { + self.changes.insert(key.into(), Some(val.into())); + Ok(()) + } + + fn del(&mut self, key: &[u8]) -> Result<()> { + self.changes.insert(key.into(), None); + Ok(()) + } + + fn exists(&self, key: &[u8], _for_update: bool) -> Result { + Ok(match self.changes.get(key) { + Some(Some(_)) => true, + Some(None) => false, + None => self.db.get(key).into_diagnostic()?.is_some(), + }) + } + + fn commit(&mut self) -> Result<()> { + self.db + .transaction( + |db: &TransactionalTree| -> Result<(), ConflictableTransactionError> { + for (k, v) in &self.changes { + match v { + None => { + db.remove(k as &[u8])?; + } + Some(v) => { + db.insert(k as &[u8], v as &[u8])?; + } + } + } + Ok(()) + }, + ) + .into_diagnostic()?; + Ok(()) + } + + fn range_scan( + &self, + lower: &[u8], + upper: &[u8], + ) -> Self::KVIter { + todo!() + } + + fn range_scan_raw(&self, lower: &[u8], upper: &[u8]) -> Self::KVIterRaw { + todo!() + } +} + +struct SledIter {} + +impl Iterator for SledIter { + type Item = Result; + + fn next(&mut self) -> Option { + todo!() + } +} + +struct SledIterRaw {} + +impl Iterator for SledIterRaw { + type Item = Result<(Vec, Vec)>; + + fn next(&mut self) -> Option { + todo!() + } +} \ No newline at end of file