use noxu_bind::EntryBinding;
use noxu_db::{Database, Transaction};
use crate::error::Result;
use crate::internal::{
ScanDirection, StartKey, cursor_endpoint, decode_key, encode_key,
scan_records,
};
use crate::stored_iterator::StoredIterator;
use crate::stored_map::StoredMap;
pub struct StoredSortedMap<'db, K, V, KB, VB>
where
KB: EntryBinding<K>,
VB: EntryBinding<V>,
{
inner: StoredMap<'db, K, V, KB, VB>,
}
impl<'db, K, V, KB, VB> StoredSortedMap<'db, K, V, KB, VB>
where
KB: EntryBinding<K>,
VB: EntryBinding<V>,
{
pub fn new(db: &'db Database, key_binding: KB, value_binding: VB) -> Self {
StoredSortedMap {
inner: StoredMap::new(db, key_binding, value_binding),
}
}
pub fn new_read_only(
db: &'db Database,
key_binding: KB,
value_binding: VB,
) -> Self {
StoredSortedMap {
inner: StoredMap::new_read_only(db, key_binding, value_binding),
}
}
pub fn is_read_only(&self) -> bool {
self.inner.is_read_only()
}
pub fn database(&self) -> &'db Database {
self.inner.database()
}
pub fn as_map(&self) -> &StoredMap<'db, K, V, KB, VB> {
&self.inner
}
pub fn put(
&self,
txn: Option<&Transaction>,
key: &K,
value: &V,
) -> Result<Option<V>> {
self.inner.put(txn, key, value)
}
pub fn get(&self, txn: Option<&Transaction>, key: &K) -> Result<Option<V>> {
self.inner.get(txn, key)
}
pub fn remove(
&self,
txn: Option<&Transaction>,
key: &K,
) -> Result<Option<V>> {
self.inner.remove(txn, key)
}
pub fn contains_key(
&self,
txn: Option<&Transaction>,
key: &K,
) -> Result<bool> {
self.inner.contains_key(txn, key)
}
pub fn len(&self, txn: Option<&Transaction>) -> Result<usize> {
self.inner.len(txn)
}
pub fn is_empty(&self, txn: Option<&Transaction>) -> Result<bool> {
self.inner.is_empty(txn)
}
pub fn clear(&self, txn: Option<&Transaction>) -> Result<()> {
self.inner.clear(txn)
}
pub fn iter(
&self,
txn: Option<&Transaction>,
) -> Result<StoredIterator<(K, V)>> {
self.inner.iter(txn)
}
pub fn keys(&self, txn: Option<&Transaction>) -> Result<StoredIterator<K>> {
self.inner.keys(txn)
}
pub fn values(
&self,
txn: Option<&Transaction>,
) -> Result<StoredIterator<V>> {
self.inner.values(txn)
}
pub fn first_key(&self, txn: Option<&Transaction>) -> Result<Option<K>> {
Ok(self.first_entry(txn)?.map(|(k, _)| k))
}
pub fn last_key(&self, txn: Option<&Transaction>) -> Result<Option<K>> {
Ok(self.last_entry(txn)?.map(|(k, _)| k))
}
pub fn first_entry(
&self,
txn: Option<&Transaction>,
) -> Result<Option<(K, V)>> {
cursor_endpoint(
self.inner.database(),
txn,
self.inner.key_binding(),
self.inner.value_binding(),
noxu_db::Get::First,
)
}
pub fn last_entry(
&self,
txn: Option<&Transaction>,
) -> Result<Option<(K, V)>> {
cursor_endpoint(
self.inner.database(),
txn,
self.inner.key_binding(),
self.inner.value_binding(),
noxu_db::Get::Last,
)
}
pub fn iter_from(
&self,
txn: Option<&Transaction>,
start_key: &K,
) -> Result<StoredIterator<(K, V)>> {
let start_entry = encode_key(self.inner.key_binding(), start_key)?;
let bytes = start_entry.get_data().unwrap_or(&[]).to_vec();
let items = scan_records(
self.inner.database(),
txn,
Some(bytes.as_slice()),
ScanDirection::Forward,
self.inner.key_binding(),
self.inner.value_binding(),
|k, v| (k, v),
)?;
Ok(StoredIterator::from_vec(items))
}
pub fn iter_reverse(
&self,
txn: Option<&Transaction>,
) -> Result<StoredIterator<(K, V)>> {
let items = scan_records(
self.inner.database(),
txn,
StartKey::None,
ScanDirection::Reverse,
self.inner.key_binding(),
self.inner.value_binding(),
|k, v| (k, v),
)?;
Ok(StoredIterator::from_vec(items))
}
pub fn higher_key(
&self,
txn: Option<&Transaction>,
key: &K,
) -> Result<Option<K>> {
let key_entry = encode_key(self.inner.key_binding(), key)?;
let bound = key_entry.get_data().unwrap_or(&[]).to_vec();
let mut cursor = self.inner.database().open_cursor(txn, None)?;
let mut k_buf = noxu_db::DatabaseEntry::new();
let mut d_buf = noxu_db::DatabaseEntry::new();
let mut status =
cursor.get(&mut k_buf, &mut d_buf, noxu_db::Get::First, None)?;
let mut result: Option<K> = None;
while matches!(status, noxu_db::OperationStatus::Success) {
let cur = k_buf.get_data().unwrap_or(&[]);
if cur > bound.as_slice() {
result = Some(decode_key(self.inner.key_binding(), &k_buf)?);
break;
}
status =
cursor.get(&mut k_buf, &mut d_buf, noxu_db::Get::Next, None)?;
}
cursor.close()?;
Ok(result)
}
}
#[cfg(test)]
mod tests {
use super::*;
use noxu_bind::{IntBinding, StringBinding};
use noxu_db::{DatabaseConfig, Environment, EnvironmentConfig};
use tempfile::TempDir;
fn setup() -> (TempDir, Environment, noxu_db::Database) {
let td = TempDir::new().unwrap();
let env = Environment::open(
EnvironmentConfig::new(td.path().to_path_buf())
.with_allow_create(true)
.with_transactional(true),
)
.unwrap();
let db = env
.open_database(
None,
"ssm",
&DatabaseConfig::new()
.with_allow_create(true)
.with_transactional(true),
)
.unwrap();
(td, env, db)
}
fn populate(
map: &StoredSortedMap<'_, i32, String, IntBinding, StringBinding>,
) {
for (k, v) in
[(3, "three"), (1, "one"), (2, "two"), (5, "five"), (4, "four")]
{
map.put(None, &k, &v.to_string()).unwrap();
}
}
#[test]
fn first_and_last_key() {
let (_td, _env, db) = setup();
let map: StoredSortedMap<'_, i32, String, _, _> =
StoredSortedMap::new(&db, IntBinding, StringBinding);
populate(&map);
assert_eq!(map.first_key(None).unwrap(), Some(1));
assert_eq!(map.last_key(None).unwrap(), Some(5));
}
#[test]
fn first_and_last_entry() {
let (_td, _env, db) = setup();
let map: StoredSortedMap<'_, i32, String, _, _> =
StoredSortedMap::new(&db, IntBinding, StringBinding);
populate(&map);
assert_eq!(
map.first_entry(None).unwrap(),
Some((1, "one".to_string())),
);
assert_eq!(
map.last_entry(None).unwrap(),
Some((5, "five".to_string())),
);
}
#[test]
fn first_last_empty() {
let (_td, _env, db) = setup();
let map: StoredSortedMap<'_, i32, String, _, _> =
StoredSortedMap::new(&db, IntBinding, StringBinding);
assert_eq!(map.first_key(None).unwrap(), None);
assert_eq!(map.last_key(None).unwrap(), None);
assert_eq!(map.first_entry(None).unwrap(), None);
assert_eq!(map.last_entry(None).unwrap(), None);
}
#[test]
fn iter_reverse() {
let (_td, _env, db) = setup();
let map: StoredSortedMap<'_, i32, String, _, _> =
StoredSortedMap::new(&db, IntBinding, StringBinding);
populate(&map);
let items: Vec<_> =
map.iter_reverse(None).unwrap().map(Result::unwrap).collect();
let keys: Vec<i32> = items.iter().map(|(k, _)| *k).collect();
assert_eq!(keys, vec![5, 4, 3, 2, 1]);
}
#[test]
fn iter_from_inclusive() {
let (_td, _env, db) = setup();
let map: StoredSortedMap<'_, i32, String, _, _> =
StoredSortedMap::new(&db, IntBinding, StringBinding);
populate(&map);
let items: Vec<_> =
map.iter_from(None, &3).unwrap().map(Result::unwrap).collect();
let keys: Vec<i32> = items.iter().map(|(k, _)| *k).collect();
assert_eq!(keys, vec![3, 4, 5]);
}
#[test]
fn iter_from_between_keys() {
let (_td, _env, db) = setup();
let map: StoredSortedMap<'_, i32, String, _, _> =
StoredSortedMap::new(&db, IntBinding, StringBinding);
for k in [1, 2, 4, 5] {
map.put(None, &k, &format!("{k}")).unwrap();
}
let items: Vec<_> =
map.iter_from(None, &3).unwrap().map(Result::unwrap).collect();
let keys: Vec<i32> = items.iter().map(|(k, _)| *k).collect();
assert_eq!(keys, vec![4, 5]);
}
#[test]
fn higher_key() {
let (_td, _env, db) = setup();
let map: StoredSortedMap<'_, i32, String, _, _> =
StoredSortedMap::new(&db, IntBinding, StringBinding);
populate(&map);
assert_eq!(map.higher_key(None, &1).unwrap(), Some(2));
assert_eq!(map.higher_key(None, &3).unwrap(), Some(4));
assert_eq!(map.higher_key(None, &5).unwrap(), None);
assert_eq!(map.higher_key(None, &0).unwrap(), Some(1));
}
#[test]
fn participates_in_user_txn() {
let (_td, env, db) = setup();
let map: StoredSortedMap<'_, i32, String, _, _> =
StoredSortedMap::new(&db, IntBinding, StringBinding);
let txn = env.begin_transaction(None).unwrap();
map.put(Some(&txn), &1, &"one".to_string()).unwrap();
map.put(Some(&txn), &2, &"two".to_string()).unwrap();
assert_eq!(map.first_key(Some(&txn)).unwrap(), Some(1));
txn.commit().unwrap();
assert_eq!(map.first_key(None).unwrap(), Some(1));
}
}