mirror of
https://github.com/Cuprate/cuprate.git
synced 2025-02-06 05:06:33 +00:00
6502729d8c
* cargo.toml: add `allow_attributes` lint * fix lints * fixes * fmt * fix docs * fix docs * fix expect msg
404 lines
12 KiB
Rust
404 lines
12 KiB
Rust
//! Tests for `cuprate_database`'s backends.
|
|
//!
|
|
//! These tests are fully trait-based, meaning there
|
|
//! is no reference to `backend/`-specific types.
|
|
//!
|
|
//! As such, which backend is tested is
|
|
//! dependant on the feature flags used.
|
|
//!
|
|
//! | Feature flag | Tested backend |
|
|
//! |---------------|----------------|
|
|
//! | Only `redb` | `redb`
|
|
//! | Anything else | `heed`
|
|
//!
|
|
//! `redb`, and it only must be enabled for it to be tested.
|
|
|
|
//---------------------------------------------------------------------------------------------------- Import
|
|
use crate::{
|
|
database::{DatabaseIter, DatabaseRo, DatabaseRw},
|
|
env::{Env, EnvInner},
|
|
error::RuntimeError,
|
|
resize::ResizeAlgorithm,
|
|
tests::{tmp_concrete_env, TestTable},
|
|
transaction::{TxRo, TxRw},
|
|
ConcreteEnv,
|
|
};
|
|
|
|
//---------------------------------------------------------------------------------------------------- Tests
|
|
/// Simply call [`Env::open`]. If this fails, something is really wrong.
|
|
#[test]
|
|
fn open() {
|
|
tmp_concrete_env();
|
|
}
|
|
|
|
/// Create database transactions, but don't write any data.
|
|
#[test]
|
|
fn tx() {
|
|
let (env, _tempdir) = tmp_concrete_env();
|
|
let env_inner = env.env_inner();
|
|
|
|
TxRo::commit(env_inner.tx_ro().unwrap()).unwrap();
|
|
TxRw::commit(env_inner.tx_rw().unwrap()).unwrap();
|
|
TxRw::abort(env_inner.tx_rw().unwrap()).unwrap();
|
|
}
|
|
|
|
/// Test [`Env::open`] and creating/opening tables.
|
|
#[test]
|
|
fn open_db() {
|
|
let (env, _tempdir) = tmp_concrete_env();
|
|
let env_inner = env.env_inner();
|
|
|
|
// Create table.
|
|
{
|
|
let tx_rw = env_inner.tx_rw().unwrap();
|
|
env_inner.create_db::<TestTable>(&tx_rw).unwrap();
|
|
TxRw::commit(tx_rw).unwrap();
|
|
}
|
|
|
|
let tx_ro = env_inner.tx_ro().unwrap();
|
|
let tx_rw = env_inner.tx_rw().unwrap();
|
|
|
|
// Open table in read-only mode.
|
|
env_inner.open_db_ro::<TestTable>(&tx_ro).unwrap();
|
|
TxRo::commit(tx_ro).unwrap();
|
|
|
|
// Open table in read/write mode.
|
|
env_inner.open_db_rw::<TestTable>(&tx_rw).unwrap();
|
|
TxRw::commit(tx_rw).unwrap();
|
|
}
|
|
|
|
/// Assert that opening a read-only table before creating errors.
|
|
#[test]
|
|
fn open_ro_uncreated_table() {
|
|
let (env, _tempdir) = tmp_concrete_env();
|
|
let env_inner = env.env_inner();
|
|
let tx_ro = env_inner.tx_ro().unwrap();
|
|
|
|
// Open uncreated table.
|
|
let error = env_inner.open_db_ro::<TestTable>(&tx_ro);
|
|
assert!(matches!(error, Err(RuntimeError::TableNotFound)));
|
|
}
|
|
|
|
/// Assert that opening a read/write table before creating is OK.
|
|
#[test]
|
|
fn open_rw_uncreated_table() {
|
|
let (env, _tempdir) = tmp_concrete_env();
|
|
let env_inner = env.env_inner();
|
|
let tx_rw = env_inner.tx_rw().unwrap();
|
|
|
|
// Open uncreated table.
|
|
let _table = env_inner.open_db_rw::<TestTable>(&tx_rw).unwrap();
|
|
}
|
|
|
|
/// Assert that opening a read-only table after creating is OK.
|
|
#[test]
|
|
fn open_ro_created_table() {
|
|
let (env, _tempdir) = tmp_concrete_env();
|
|
let env_inner = env.env_inner();
|
|
|
|
// Assert uncreated table errors.
|
|
{
|
|
let tx_ro = env_inner.tx_ro().unwrap();
|
|
let error = env_inner.open_db_ro::<TestTable>(&tx_ro);
|
|
assert!(matches!(error, Err(RuntimeError::TableNotFound)));
|
|
}
|
|
|
|
// Create table.
|
|
{
|
|
let tx_rw = env_inner.tx_rw().unwrap();
|
|
env_inner.create_db::<TestTable>(&tx_rw).unwrap();
|
|
TxRw::commit(tx_rw).unwrap();
|
|
}
|
|
|
|
// Assert created table is now OK.
|
|
let tx_ro = env_inner.tx_ro().unwrap();
|
|
let _table = env_inner.open_db_ro::<TestTable>(&tx_ro).unwrap();
|
|
}
|
|
|
|
/// Test `Env` resizes.
|
|
#[test]
|
|
fn resize() {
|
|
// This test is only valid for `Env`'s that need to resize manually.
|
|
if !ConcreteEnv::MANUAL_RESIZE {
|
|
return;
|
|
}
|
|
|
|
let (env, _tempdir) = tmp_concrete_env();
|
|
|
|
// Resize by the OS page size.
|
|
let page_size = *crate::resize::PAGE_SIZE;
|
|
let old_size = env.current_map_size();
|
|
env.resize_map(Some(ResizeAlgorithm::FixedBytes(page_size)));
|
|
|
|
// Assert it resized exactly by the OS page size.
|
|
let new_size = env.current_map_size();
|
|
assert_eq!(new_size, old_size + page_size.get());
|
|
}
|
|
|
|
/// Test that `Env`'s that don't manually resize.
|
|
#[test]
|
|
#[should_panic = "unreachable"]
|
|
fn non_manual_resize_1() {
|
|
if ConcreteEnv::MANUAL_RESIZE {
|
|
unreachable!();
|
|
}
|
|
let (env, _tempdir) = tmp_concrete_env();
|
|
env.resize_map(None);
|
|
}
|
|
|
|
#[test]
|
|
#[should_panic = "unreachable"]
|
|
fn non_manual_resize_2() {
|
|
if ConcreteEnv::MANUAL_RESIZE {
|
|
unreachable!();
|
|
}
|
|
let (env, _tempdir) = tmp_concrete_env();
|
|
env.current_map_size();
|
|
}
|
|
|
|
/// Tests that [`EnvInner::clear_db`] will return
|
|
/// [`RuntimeError::TableNotFound`] if the table doesn't exist.
|
|
#[test]
|
|
fn clear_db_table_not_found() {
|
|
let (env, _tmpdir) = tmp_concrete_env();
|
|
let env_inner = env.env_inner();
|
|
let mut tx_rw = env_inner.tx_rw().unwrap();
|
|
let err = env_inner.clear_db::<TestTable>(&mut tx_rw).unwrap_err();
|
|
assert!(matches!(err, RuntimeError::TableNotFound));
|
|
|
|
env_inner.create_db::<TestTable>(&tx_rw).unwrap();
|
|
env_inner.clear_db::<TestTable>(&mut tx_rw).unwrap();
|
|
}
|
|
|
|
/// Test all `DatabaseR{o,w}` operations.
|
|
#[test]
|
|
fn db_read_write() {
|
|
let (env, _tempdir) = tmp_concrete_env();
|
|
let env_inner = env.env_inner();
|
|
let tx_rw = env_inner.tx_rw().unwrap();
|
|
let mut table = env_inner.open_db_rw::<TestTable>(&tx_rw).unwrap();
|
|
|
|
/// The (1st) key.
|
|
const KEY: u32 = 0;
|
|
/// The expected value.
|
|
const VALUE: u64 = 0;
|
|
/// How many `(key, value)` pairs will be inserted.
|
|
const N: u32 = 100;
|
|
|
|
/// Assert a u64 is the same as `VALUE`.
|
|
fn assert_value(value: u64) {
|
|
assert_eq!(value, VALUE);
|
|
}
|
|
|
|
assert!(table.is_empty().unwrap());
|
|
|
|
// Insert keys.
|
|
let mut key = KEY;
|
|
#[expect(clippy::explicit_counter_loop, reason = "we need the +1 side effect")]
|
|
for _ in 0..N {
|
|
table.put(&key, &VALUE).unwrap();
|
|
key += 1;
|
|
}
|
|
|
|
assert_eq!(table.len().unwrap(), u64::from(N));
|
|
|
|
// Assert the first/last `(key, value)`s are there.
|
|
{
|
|
assert!(table.contains(&KEY).unwrap());
|
|
let get = table.get(&KEY).unwrap();
|
|
assert_value(get);
|
|
|
|
let first = table.first().unwrap().1;
|
|
assert_value(first);
|
|
|
|
let last = table.last().unwrap().1;
|
|
assert_value(last);
|
|
}
|
|
|
|
// Commit transactions, create new ones.
|
|
drop(table);
|
|
TxRw::commit(tx_rw).unwrap();
|
|
let tx_ro = env_inner.tx_ro().unwrap();
|
|
let table_ro = env_inner.open_db_ro::<TestTable>(&tx_ro).unwrap();
|
|
let tx_rw = env_inner.tx_rw().unwrap();
|
|
let mut table = env_inner.open_db_rw::<TestTable>(&tx_rw).unwrap();
|
|
|
|
// Assert the whole range is there.
|
|
{
|
|
let range = table_ro.get_range(..).unwrap();
|
|
let mut i = 0;
|
|
for result in range {
|
|
let value = result.unwrap();
|
|
assert_value(value);
|
|
i += 1;
|
|
}
|
|
assert_eq!(i, N);
|
|
}
|
|
|
|
// `get_range()` tests.
|
|
let mut key = KEY;
|
|
key += N;
|
|
let range = KEY..key;
|
|
|
|
// Assert count is correct.
|
|
assert_eq!(
|
|
N as usize,
|
|
table_ro.get_range(range.clone()).unwrap().count()
|
|
);
|
|
|
|
// Assert each returned value from the iterator is owned.
|
|
{
|
|
let mut iter = table_ro.get_range(range.clone()).unwrap();
|
|
let value = iter.next().unwrap().unwrap(); // 1. take value out
|
|
drop(iter); // 2. drop the `impl Iterator + 'a`
|
|
assert_value(value); // 3. assert even without the iterator, the value is alive
|
|
}
|
|
|
|
// Assert each value is the same.
|
|
{
|
|
let mut iter = table_ro.get_range(range).unwrap();
|
|
for _ in 0..N {
|
|
let value = iter.next().unwrap().unwrap();
|
|
assert_value(value);
|
|
}
|
|
}
|
|
|
|
// Assert `update()` works.
|
|
{
|
|
const NEW_VALUE: u64 = 999;
|
|
|
|
assert_ne!(table.get(&KEY).unwrap(), NEW_VALUE);
|
|
|
|
#[expect(unused_assignments)]
|
|
table
|
|
.update(&KEY, |mut value| {
|
|
value = NEW_VALUE;
|
|
Some(value)
|
|
})
|
|
.unwrap();
|
|
|
|
assert_eq!(table.get(&KEY).unwrap(), NEW_VALUE);
|
|
}
|
|
|
|
// Assert deleting works.
|
|
{
|
|
table.delete(&KEY).unwrap();
|
|
let value = table.get(&KEY);
|
|
assert!(!table.contains(&KEY).unwrap());
|
|
assert!(matches!(value, Err(RuntimeError::KeyNotFound)));
|
|
// Assert the other `(key, value)` pairs are still there.
|
|
let mut key = KEY;
|
|
key += N - 1; // we used inclusive `0..N`
|
|
let value = table.get(&key).unwrap();
|
|
assert_value(value);
|
|
}
|
|
|
|
// Assert `take()` works.
|
|
{
|
|
let mut key = KEY;
|
|
key += 1;
|
|
let value = table.take(&key).unwrap();
|
|
assert_eq!(value, VALUE);
|
|
|
|
let get = table.get(&KEY);
|
|
assert!(!table.contains(&key).unwrap());
|
|
assert!(matches!(get, Err(RuntimeError::KeyNotFound)));
|
|
|
|
// Assert the other `(key, value)` pairs are still there.
|
|
key += 1;
|
|
let value = table.get(&key).unwrap();
|
|
assert_value(value);
|
|
}
|
|
|
|
drop(table);
|
|
TxRw::commit(tx_rw).unwrap();
|
|
|
|
// Assert `clear_db()` works.
|
|
{
|
|
let mut tx_rw = env_inner.tx_rw().unwrap();
|
|
env_inner.clear_db::<TestTable>(&mut tx_rw).unwrap();
|
|
let table = env_inner.open_db_rw::<TestTable>(&tx_rw).unwrap();
|
|
assert!(table.is_empty().unwrap());
|
|
for n in 0..N {
|
|
let mut key = KEY;
|
|
key += n;
|
|
let value = table.get(&key);
|
|
assert!(matches!(value, Err(RuntimeError::KeyNotFound)));
|
|
assert!(!table.contains(&key).unwrap());
|
|
}
|
|
|
|
// Reader still sees old value.
|
|
assert!(!table_ro.is_empty().unwrap());
|
|
|
|
// Writer sees updated value (nothing).
|
|
assert!(table.is_empty().unwrap());
|
|
}
|
|
}
|
|
|
|
/// Assert that `key`'s in database tables are sorted in
|
|
/// an ordered B-Tree fashion, i.e. `min_value -> max_value`.
|
|
///
|
|
/// And that it is true for integers, e.g. `0` -> `10`.
|
|
#[test]
|
|
fn tables_are_sorted() {
|
|
let (env, _tmp) = tmp_concrete_env();
|
|
let env_inner = env.env_inner();
|
|
|
|
/// Range of keys to insert, `{0, 1, 2 ... 256}`.
|
|
const RANGE: std::ops::Range<u32> = 0..257;
|
|
|
|
// Create tables and set flags / comparison flags.
|
|
{
|
|
let tx_rw = env_inner.tx_rw().unwrap();
|
|
env_inner.create_db::<TestTable>(&tx_rw).unwrap();
|
|
TxRw::commit(tx_rw).unwrap();
|
|
}
|
|
|
|
let tx_rw = env_inner.tx_rw().unwrap();
|
|
let mut table = env_inner.open_db_rw::<TestTable>(&tx_rw).unwrap();
|
|
|
|
// Insert range, assert each new
|
|
// number inserted is the minimum `last()` value.
|
|
for key in RANGE {
|
|
table.put(&key, &0).unwrap();
|
|
table.contains(&key).unwrap();
|
|
let (first, _) = table.first().unwrap();
|
|
let (last, _) = table.last().unwrap();
|
|
println!("first: {first}, last: {last}, key: {key}");
|
|
assert_eq!(last, key);
|
|
}
|
|
|
|
drop(table);
|
|
TxRw::commit(tx_rw).unwrap();
|
|
let tx_rw = env_inner.tx_rw().unwrap();
|
|
|
|
// Assert iterators are ordered.
|
|
{
|
|
let tx_ro = env_inner.tx_ro().unwrap();
|
|
let table = env_inner.open_db_ro::<TestTable>(&tx_ro).unwrap();
|
|
let iter = table.iter().unwrap();
|
|
let keys = table.keys().unwrap();
|
|
for ((i, iter), key) in RANGE.zip(iter).zip(keys) {
|
|
let (iter, _) = iter.unwrap();
|
|
let key = key.unwrap();
|
|
assert_eq!(i, iter);
|
|
assert_eq!(iter, key);
|
|
}
|
|
}
|
|
|
|
let mut table = env_inner.open_db_rw::<TestTable>(&tx_rw).unwrap();
|
|
|
|
// Assert the `first()` values are the minimum, i.e. `{0, 1, 2}`
|
|
for key in [0, 1, 2] {
|
|
let (first, _) = table.first().unwrap();
|
|
assert_eq!(first, key);
|
|
table.delete(&key).unwrap();
|
|
}
|
|
|
|
// Assert the `last()` values are the maximum, i.e. `{256, 255, 254}`
|
|
for key in [256, 255, 254] {
|
|
let (last, _) = table.last().unwrap();
|
|
assert_eq!(last, key);
|
|
table.delete(&key).unwrap();
|
|
}
|
|
}
|