#[allow(unused_imports)]
use crate::persistent_artrie::wal::WalConfig;
#[allow(unused_imports)]
use std::sync::atomic::Ordering as AtomicOrdering;
use std::sync::Arc;
pub const CHAR_TRIE_MAGIC: [u8; 4] = *b"ARTC";
pub const CHAR_FILE_HEADER_SIZE: usize = 64;
pub const CHAR_HEADER_VERSION_V1: u8 = 1;
pub const CHAR_HEADER_VERSION_V2: u8 = 2;
pub const DEFAULT_CHAR_BUFFER_POOL_SIZE: usize = 256;
pub use super::recovery_stats::EnhancedRecoveryMode;
pub(super) enum LockfreeInsertResult<V = ()> {
Inserted(
Arc<super::nodes::persistent_node::PersistentCharNode<V>>,
u64,
),
AlreadyExists(u64),
Conflict,
IoError(crate::persistent_artrie::error::PersistentARTrieError),
}
impl<V: Clone> std::fmt::Debug for LockfreeInsertResult<V> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
LockfreeInsertResult::Inserted(node, gen) => f
.debug_tuple("LockfreeInsertResult::Inserted")
.field(node)
.field(gen)
.finish(),
LockfreeInsertResult::AlreadyExists(gen) => f
.debug_tuple("LockfreeInsertResult::AlreadyExists")
.field(gen)
.finish(),
LockfreeInsertResult::Conflict => f.write_str("LockfreeInsertResult::Conflict"),
LockfreeInsertResult::IoError(e) => f
.debug_tuple("LockfreeInsertResult::IoError")
.field(e)
.finish(),
}
}
}
pub use super::recovery_stats::EnhancedRecoveryStats;
pub use super::file_header::CharTrieFileHeader;
pub use super::prefix_term::{PrefixTermWithArena, PrefixTermWithValueAndArena};
pub use crate::persistent_artrie::TransactionState;
pub use crate::persistent_artrie::DurabilityPolicy;
pub use super::transactions::CharDocumentTransaction;
pub(super) const ROOT_TYPE_EMPTY: u8 = 0;
pub(super) const ROOT_TYPE_NODE: u8 = 1;
#[cfg(test)]
#[allow(deprecated)]
mod tests {
use super::super::PersistentARTrieChar;
use super::*;
use crate::ARTrie;
use crate::persistent_artrie_core::shared_access::SharedTrieAccess;
#[test]
fn test_file_header_roundtrip() {
let mut header = CharTrieFileHeader {
magic: CHAR_TRIE_MAGIC,
version: CHAR_HEADER_VERSION_V2,
_reserved: [0; 3],
root_ptr: 12345,
entry_count: 67890,
checkpoint_lsn: 111,
header_checksum: 0,
_padding: [0; 28],
};
header.finalize_checksum();
let bytes = header.to_bytes();
let restored = CharTrieFileHeader::from_bytes(&bytes);
assert_eq!(restored.magic, CHAR_TRIE_MAGIC);
assert_eq!(restored.version, CHAR_HEADER_VERSION_V2);
assert_eq!(restored.root_ptr, 12345);
assert_eq!(restored.entry_count, 67890);
assert_eq!(restored.checkpoint_lsn, 111);
assert!(restored.verify_checksum());
}
#[test]
fn test_file_header_v1_roundtrip() {
let header = CharTrieFileHeader {
magic: CHAR_TRIE_MAGIC,
version: CHAR_HEADER_VERSION_V1,
_reserved: [0; 3],
root_ptr: 12345,
entry_count: 67890,
checkpoint_lsn: 111,
header_checksum: 0,
_padding: [0; 28],
};
let bytes = header.to_bytes();
let restored = CharTrieFileHeader::from_bytes(&bytes);
assert_eq!(restored.magic, CHAR_TRIE_MAGIC);
assert_eq!(restored.version, CHAR_HEADER_VERSION_V1);
assert_eq!(restored.root_ptr, 12345);
assert!(!restored.has_checksum());
assert!(restored.verify_checksum()); }
#[test]
fn test_file_header_checksum() {
let mut header = CharTrieFileHeader::new();
header.root_ptr = 12345;
header.entry_count = 67890;
assert_eq!(header.header_checksum, 0);
assert!(!header.verify_checksum());
header.finalize_checksum();
assert_ne!(header.header_checksum, 0);
assert!(header.verify_checksum());
header.root_ptr = 99999;
assert!(!header.verify_checksum());
header.finalize_checksum();
assert!(header.verify_checksum());
}
#[test]
fn test_file_header_validation() {
let mut header = CharTrieFileHeader::new();
header.finalize_checksum();
assert!(header.validate().is_ok());
header.magic = *b"XXXX";
assert!(header.validate().is_err());
header.magic = CHAR_TRIE_MAGIC;
header.header_checksum = 0xDEADBEEF;
assert!(header.validate().is_err());
}
#[test]
fn test_file_header_from_bytes_verified() {
let mut header = CharTrieFileHeader::new();
header.root_ptr = 12345;
header.finalize_checksum();
let bytes = header.to_bytes();
let restored = CharTrieFileHeader::from_bytes_verified(&bytes);
assert!(restored.is_ok());
let mut corrupted = bytes;
corrupted[8] = 0xFF; let result = CharTrieFileHeader::from_bytes_verified(&corrupted);
assert!(result.is_err());
}
#[test]
fn test_file_header_upgrade_to_v2() {
let mut header = CharTrieFileHeader::new_v1();
assert!(!header.has_checksum());
header.root_ptr = 12345;
header.upgrade_to_v2();
assert!(header.has_checksum());
assert!(header.verify_checksum());
assert_eq!(header.version, CHAR_HEADER_VERSION_V2);
}
#[test]
fn test_inner_new() {
let inner: PersistentARTrieChar<()> = PersistentARTrieChar::new();
assert_eq!(inner.len.load(AtomicOrdering::Acquire), 0);
assert!(!inner.dirty.load(AtomicOrdering::Acquire));
assert_eq!(inner.len(), 0, "fresh trie is empty (overlay-backed)");
}
#[test]
fn test_create_and_open() {
use tempfile::tempdir;
let dir = tempdir().expect("create temp dir");
let path = dir.path().join("test.trie");
{
let inner: PersistentARTrieChar<()> =
PersistentARTrieChar::create(&path).expect("create");
inner.insert("hello").expect("insert");
inner.insert("world").expect("insert");
inner.sync().expect("sync");
}
{
let inner: PersistentARTrieChar<()> = PersistentARTrieChar::open(&path).expect("open");
assert_eq!(inner.len(), 2);
}
}
#[test]
fn test_insert_and_contains() {
let inner: PersistentARTrieChar<()> = PersistentARTrieChar::new();
assert!(inner.insert("hello").expect("ins"));
assert!(inner.insert("world").expect("ins"));
assert!(inner.insert("hello world").expect("ins"));
assert!(inner.contains("hello"));
assert!(inner.contains("world"));
assert!(inner.contains("hello world"));
assert!(!inner.contains("hell"));
assert!(!inner.contains("hello worl"));
assert_eq!(inner.len(), 3);
}
#[test]
fn test_insert_duplicate() {
let inner: PersistentARTrieChar<()> = PersistentARTrieChar::new();
assert!(inner.insert("hello").expect("ins"));
assert!(!inner.insert("hello").expect("ins"));
assert_eq!(inner.len(), 1);
}
#[test]
fn test_remove() {
let inner: PersistentARTrieChar<()> = PersistentARTrieChar::new();
inner.insert("hello").expect("ins");
inner.insert("world").expect("ins");
assert_eq!(inner.len(), 2);
assert!(inner.remove("hello").expect("rm"));
assert_eq!(inner.len(), 1);
assert!(!inner.contains("hello"));
assert!(inner.contains("world"));
assert!(!inner.remove("hello").expect("rm"));
assert!(inner.remove("world").expect("rm"));
assert_eq!(inner.len(), 0);
}
#[test]
fn test_unicode_support() {
let inner: PersistentARTrieChar<()> = PersistentARTrieChar::new();
let terms = vec![
"こんにちは", "你好", "안녕하세요", "مرحبا", "שלום", "🎉🎊🎋", "café", "naïve", ];
for term in &terms {
assert!(inner.insert(term).expect("ins"), "should insert: {}", term);
}
assert_eq!(inner.len(), terms.len());
for term in &terms {
assert!(inner.contains(term), "should contain: {}", term);
}
assert!(!inner.contains("こん"));
assert!(!inner.contains("你"));
assert!(!inner.contains("🎉"));
}
#[test]
fn test_prefix_sharing() {
let inner: PersistentARTrieChar<()> = PersistentARTrieChar::new();
inner.insert("a").expect("ins");
inner.insert("ab").expect("ins");
inner.insert("abc").expect("ins");
inner.insert("abd").expect("ins");
inner.insert("abcd").expect("ins");
assert_eq!(inner.len(), 5);
assert!(inner.contains("a"));
assert!(inner.contains("ab"));
assert!(inner.contains("abc"));
assert!(inner.contains("abd"));
assert!(inner.contains("abcd"));
assert!(!inner.contains("abce"));
}
#[test]
fn test_empty_string() {
let inner: PersistentARTrieChar<()> = PersistentARTrieChar::new();
assert!(inner.insert("").expect("ins"));
assert!(inner.contains(""));
assert_eq!(inner.len(), 1);
inner.insert("hello").expect("ins");
assert_eq!(inner.len(), 2);
assert!(inner.contains(""));
assert!(inner.contains("hello"));
}
#[test]
fn test_get_value() {
let inner: PersistentARTrieChar<i32> = PersistentARTrieChar::new();
inner.insert_with_value("one", 1).expect("ins");
inner.insert_with_value("two", 2).expect("ins");
inner.insert_with_value("three", 3).expect("ins");
assert_eq!(inner.get("one"), Some(1));
assert_eq!(inner.get("two"), Some(2));
assert_eq!(inner.get("three"), Some(3));
assert_eq!(inner.get("four"), None);
}
#[test]
fn test_value_update() {
let inner: PersistentARTrieChar<i32> = PersistentARTrieChar::new();
assert!(inner.insert_with_value("key", 100).expect("ins"));
assert_eq!(inner.get("key"), Some(100));
assert!(!inner.insert_with_value("key", 200).expect("ins"));
assert_eq!(inner.get("key"), Some(200));
assert_eq!(inner.len(), 1);
}
#[test]
fn test_wal_recovery_with_values() {
use tempfile::tempdir;
let dir = tempdir().expect("create temp dir");
let path = dir.path().join("test_values.trie");
{
let inner: PersistentARTrieChar<()> =
PersistentARTrieChar::create(&path).expect("create");
inner.insert("alpha").expect("insert");
inner.insert("beta").expect("insert");
inner.insert("gamma").expect("insert");
inner.sync().expect("sync");
}
{
let inner: PersistentARTrieChar<()> = PersistentARTrieChar::open(&path).expect("open");
assert_eq!(inner.len(), 3);
assert!(inner.contains("alpha"));
assert!(inner.contains("beta"));
assert!(inner.contains("gamma"));
assert!(!inner.contains("delta"));
}
}
#[test]
fn test_wal_recovery_mixed_operations() {
use tempfile::tempdir;
let dir = tempdir().expect("create temp dir");
let path = dir.path().join("test_mixed.trie");
{
let inner: PersistentARTrieChar<()> =
PersistentARTrieChar::create(&path).expect("create");
inner.insert("a").expect("insert");
inner.insert("b").expect("insert");
inner.insert("c").expect("insert");
inner.remove("b").expect("remove");
inner.insert("d").expect("insert");
inner.sync().expect("sync");
}
{
let inner: PersistentARTrieChar<()> = PersistentARTrieChar::open(&path).expect("open");
assert_eq!(inner.len(), 3);
assert!(inner.contains("a"));
assert!(!inner.contains("b"));
assert!(inner.contains("c"));
assert!(inner.contains("d"));
}
}
#[test]
fn test_checkpoint_and_disk_loading() {
use tempfile::tempdir;
let dir = tempdir().expect("create temp dir");
let path = dir.path().join("test_checkpoint.trie");
let root_ptr_after_checkpoint;
{
let inner: PersistentARTrieChar<()> =
PersistentARTrieChar::create(&path).expect("create");
inner.insert("apple").expect("insert");
inner.insert("banana").expect("insert");
inner.insert("cherry").expect("insert");
assert_eq!(inner.len(), 3, "len after inserts");
inner.checkpoint().expect("checkpoint");
let buffer_manager = inner.buffer_manager.as_ref().expect("buffer manager");
let bm = buffer_manager.read();
root_ptr_after_checkpoint = bm.disk_manager().root_ptr().expect("root_ptr");
}
assert_ne!(
root_ptr_after_checkpoint, 0,
"root_ptr should be non-zero after checkpoint"
);
{
let dm = crate::persistent_artrie::disk_manager::DiskManager::open(&path)
.expect("open disk manager");
let stored_root_ptr = dm.root_ptr().expect("read root_ptr");
let stored_entry_count = dm.entry_count().expect("read entry_count");
assert_ne!(
stored_root_ptr, 0,
"root_ptr on disk should be non-zero (was: {}, entry_count: {})",
stored_root_ptr, stored_entry_count
);
drop(dm);
let inner: PersistentARTrieChar<()> = PersistentARTrieChar::open(&path).expect("open");
assert_eq!(
inner.len(),
3,
"len after reopen (root_ptr was {}, entry_count was {})",
stored_root_ptr,
stored_entry_count
);
assert!(inner.contains("apple"));
assert!(inner.contains("banana"));
assert!(inner.contains("cherry"));
assert!(!inner.contains("date"));
}
}
#[test]
fn test_checkpoint_with_unicode() {
use tempfile::tempdir;
let dir = tempdir().expect("create temp dir");
let path = dir.path().join("test_unicode_checkpoint.trie");
{
let inner: PersistentARTrieChar<()> =
PersistentARTrieChar::create(&path).expect("create");
inner.insert("こんにちは").expect("insert");
inner.insert("你好").expect("insert");
inner.insert("🎉").expect("insert");
inner.insert("café").expect("insert");
inner.checkpoint().expect("checkpoint");
}
{
let inner: PersistentARTrieChar<()> = PersistentARTrieChar::open(&path).expect("open");
assert_eq!(inner.len(), 4);
assert!(inner.contains("こんにちは"));
assert!(inner.contains("你好"));
assert!(inner.contains("🎉"));
assert!(inner.contains("café"));
}
}
#[test]
fn test_checkpoint_then_more_inserts() {
use tempfile::tempdir;
let dir = tempdir().expect("create temp dir");
let path = dir.path().join("test_post_checkpoint.trie");
{
let inner: PersistentARTrieChar<()> =
PersistentARTrieChar::create(&path).expect("create");
inner.insert("first").expect("insert");
inner.insert("second").expect("insert");
inner.checkpoint().expect("checkpoint");
inner.insert("third").expect("insert");
inner.insert("fourth").expect("insert");
inner.sync().expect("sync");
}
{
let inner: PersistentARTrieChar<()> = PersistentARTrieChar::open(&path).expect("open");
assert_eq!(inner.len(), 4);
assert!(inner.contains("first"));
assert!(inner.contains("second"));
assert!(inner.contains("third"));
assert!(inner.contains("fourth"));
}
}
#[test]
fn test_checkpoint_empty_trie() {
use tempfile::tempdir;
let dir = tempdir().expect("create temp dir");
let path = dir.path().join("test_empty_checkpoint.trie");
{
let inner: PersistentARTrieChar<()> =
PersistentARTrieChar::create(&path).expect("create");
inner.checkpoint().expect("checkpoint");
}
{
let inner: PersistentARTrieChar<()> = PersistentARTrieChar::open(&path).expect("open");
assert_eq!(inner.len(), 0);
assert!(!inner.contains("anything"));
}
}
#[test]
fn test_multiple_checkpoints() {
use tempfile::tempdir;
let dir = tempdir().expect("create temp dir");
let path = dir.path().join("test_multi_checkpoint.trie");
{
let inner: PersistentARTrieChar<()> =
PersistentARTrieChar::create(&path).expect("create");
inner.insert("one").expect("insert");
inner.checkpoint().expect("checkpoint 1");
inner.insert("two").expect("insert");
inner.checkpoint().expect("checkpoint 2");
inner.insert("three").expect("insert");
inner.checkpoint().expect("checkpoint 3");
}
{
let inner: PersistentARTrieChar<()> = PersistentARTrieChar::open(&path).expect("open");
assert_eq!(inner.len(), 3);
assert!(inner.contains("one"));
assert!(inner.contains("two"));
assert!(inner.contains("three"));
}
}
#[test]
fn test_deep_trie_checkpoint() {
use tempfile::tempdir;
let dir = tempdir().expect("create temp dir");
let path = dir.path().join("test_deep_checkpoint.trie");
{
let inner: PersistentARTrieChar<()> =
PersistentARTrieChar::create(&path).expect("create");
inner.insert("a").expect("insert");
inner.insert("ab").expect("insert");
inner.insert("abc").expect("insert");
inner.insert("abcd").expect("insert");
inner.insert("abcde").expect("insert");
inner.insert("abcdef").expect("insert");
inner.insert("abcdefg").expect("insert");
inner.insert("abcdefgh").expect("insert");
inner.checkpoint().expect("checkpoint");
}
{
let inner: PersistentARTrieChar<()> = PersistentARTrieChar::open(&path).expect("open");
assert_eq!(inner.len(), 8);
assert!(inner.contains("a"));
assert!(inner.contains("ab"));
assert!(inner.contains("abc"));
assert!(inner.contains("abcd"));
assert!(inner.contains("abcde"));
assert!(inner.contains("abcdef"));
assert!(inner.contains("abcdefg"));
assert!(inner.contains("abcdefgh"));
assert!(!inner.contains("abcdefghi"));
}
}
#[test]
fn test_increment_with_wal() {
use tempfile::tempdir;
let dir = tempdir().expect("create temp dir");
let path = dir.path().join("test_increment.trie");
{
let mut inner: PersistentARTrieChar<i64> =
PersistentARTrieChar::create(&path).expect("create");
let result = inner.increment("counter", 10).expect("increment");
assert_eq!(result, 10);
let result = inner.increment("counter", 5).expect("increment");
assert_eq!(result, 15);
let result = inner.increment("counter", -3).expect("increment");
assert_eq!(result, 12);
inner.sync().expect("sync");
}
{
let inner: PersistentARTrieChar<i64> = PersistentARTrieChar::open(&path).expect("open");
assert!(inner.contains("counter"));
}
}
#[test]
fn test_upsert_with_wal() {
use tempfile::tempdir;
let dir = tempdir().expect("create temp dir");
let path = dir.path().join("test_upsert.trie");
{
let inner: PersistentARTrieChar<String> =
PersistentARTrieChar::create(&path).expect("create");
let inserted = inner.upsert("key", "value1".to_string()).expect("upsert");
assert!(inserted);
assert!(inner.contains("key"));
let inserted = inner.upsert("key", "value2".to_string()).expect("upsert");
assert!(!inserted);
assert!(inner.contains("key"));
inner.sync().expect("sync");
}
{
let inner: PersistentARTrieChar<String> =
PersistentARTrieChar::open(&path).expect("open");
assert!(inner.contains("key"));
assert_eq!(inner.len(), 1);
}
}
#[test]
fn test_compare_and_swap_with_wal() {
use tempfile::tempdir;
let dir = tempdir().expect("create temp dir");
let path = dir.path().join("test_cas.trie");
{
let mut inner: PersistentARTrieChar<i32> =
PersistentARTrieChar::create(&path).expect("create");
let success = inner.compare_and_swap("key", None, 100).expect("cas");
assert!(success);
assert!(inner.contains("key"));
let success = inner.compare_and_swap("key", Some(50), 200).expect("cas");
assert!(!success);
let success = inner.compare_and_swap("key", Some(100), 200).expect("cas");
assert!(success);
inner.sync().expect("sync");
}
{
let inner: PersistentARTrieChar<i32> = PersistentARTrieChar::open(&path).expect("open");
assert!(inner.contains("key"));
}
}
#[test]
fn test_fetch_add_with_wal() {
use tempfile::tempdir;
let dir = tempdir().expect("create temp dir");
let path = dir.path().join("test_fetch_add.trie");
{
let mut inner: PersistentARTrieChar<i64> =
PersistentARTrieChar::create(&path).expect("create");
let old = inner.fetch_add("counter", 10).expect("fetch_add");
assert_eq!(old, 0);
let old = inner.fetch_add("counter", 5).expect("fetch_add");
assert_eq!(old, 10);
let old = inner.fetch_add("counter", -3).expect("fetch_add");
assert_eq!(old, 15);
inner.sync().expect("sync");
}
{
let inner: PersistentARTrieChar<i64> = PersistentARTrieChar::open(&path).expect("open");
assert!(inner.contains("counter"));
}
}
#[test]
fn test_get_or_insert_with_wal() {
use tempfile::tempdir;
let dir = tempdir().expect("create temp dir");
let path = dir.path().join("test_get_or_insert.trie");
{
let inner: PersistentARTrieChar<String> =
PersistentARTrieChar::create(&path).expect("create");
let value = inner
.get_or_insert("key", "default".to_string())
.expect("get_or_insert");
assert_eq!(value, "default");
assert!(inner.contains("key"));
let value = inner
.get_or_insert("key", "other".to_string())
.expect("get_or_insert");
assert_eq!(value, "default");
inner.sync().expect("sync");
}
{
let inner: PersistentARTrieChar<String> =
PersistentARTrieChar::open(&path).expect("open");
assert!(inner.contains("key"));
assert_eq!(inner.len(), 1);
}
}
#[test]
fn test_atomic_ops_recovery() {
use tempfile::tempdir;
let dir = tempdir().expect("create temp dir");
let path = dir.path().join("test_atomic_recovery.trie");
{
let mut inner: PersistentARTrieChar<i64> =
PersistentARTrieChar::create(&path).expect("create");
inner.increment("counter1", 100).expect("increment");
inner.increment("counter1", 50).expect("increment");
inner.fetch_add("counter2", 200).expect("fetch_add");
inner.fetch_add("counter2", 25).expect("fetch_add");
inner.sync().expect("sync");
}
{
let inner: PersistentARTrieChar<i64> = PersistentARTrieChar::open(&path).expect("open");
assert!(inner.contains("counter1"));
assert!(inner.contains("counter2"));
assert_eq!(inner.len(), 2);
}
}
#[test]
fn test_atomic_ops_with_checkpoint() {
use tempfile::tempdir;
let dir = tempdir().expect("create temp dir");
let path = dir.path().join("test_atomic_checkpoint.trie");
{
let mut inner: PersistentARTrieChar<i64> =
PersistentARTrieChar::create(&path).expect("create");
inner.increment("before_cp", 100).expect("increment");
inner.checkpoint().expect("checkpoint");
inner.increment("after_cp", 200).expect("increment");
inner.sync().expect("sync");
}
{
let inner: PersistentARTrieChar<i64> = PersistentARTrieChar::open(&path).expect("open");
assert!(inner.contains("before_cp"));
assert!(inner.contains("after_cp"));
assert_eq!(inner.len(), 2);
}
}
#[test]
fn test_unicode_atomic_ops() {
use tempfile::tempdir;
let dir = tempdir().expect("create temp dir");
let path = dir.path().join("test_unicode_atomic.trie");
{
let mut inner: PersistentARTrieChar<i64> =
PersistentARTrieChar::create(&path).expect("create");
inner.increment("カウンター", 10).expect("increment");
inner.increment("计数器", 20).expect("increment");
inner.increment("🔢", 30).expect("increment");
inner.sync().expect("sync");
}
{
let inner: PersistentARTrieChar<i64> = PersistentARTrieChar::open(&path).expect("open");
assert!(inner.contains("カウンター"));
assert!(inner.contains("计数器"));
assert!(inner.contains("🔢"));
assert_eq!(inner.len(), 3);
}
}
#[test]
fn test_optimistic_contains() {
use tempfile::tempdir;
let dir = tempdir().expect("create temp dir");
let path = dir.path().join("test_optimistic_contains.trie");
let inner: PersistentARTrieChar<()> = PersistentARTrieChar::create(&path).expect("create");
inner.insert("hello").expect("insert");
inner.insert("world").expect("insert");
let result = inner.contains_optimistic("hello", 10);
assert_eq!(result, Some(true));
let result = inner.contains_optimistic("world", 10);
assert_eq!(result, Some(true));
let result = inner.contains_optimistic("missing", 10);
assert_eq!(result, Some(false));
}
#[test]
fn test_optimistic_get() {
use tempfile::tempdir;
let dir = tempdir().expect("create temp dir");
let path = dir.path().join("test_optimistic_get.trie");
let mut inner: PersistentARTrieChar<i64> =
PersistentARTrieChar::create(&path).expect("create");
inner.increment("counter", 100).expect("increment");
let result = inner.get_optimistic("counter", 10);
assert!(result.is_some());
let value = result.unwrap();
assert_eq!(value, Some(100));
let result = inner.get_optimistic("missing", 10);
assert!(result.is_some());
assert_eq!(result.unwrap(), None);
}
#[test]
fn test_epoch_management() {
use tempfile::tempdir;
let dir = tempdir().expect("create temp dir");
let path = dir.path().join("test_epoch.trie");
let inner: PersistentARTrieChar<()> = PersistentARTrieChar::create(&path).expect("create");
assert_eq!(inner.current_epoch(), 0);
assert_eq!(inner.active_readers(), 0);
{
let _guard = inner.enter_epoch();
assert_eq!(inner.active_readers(), 1);
{
let _guard2 = inner.enter_epoch();
assert_eq!(inner.active_readers(), 2);
}
assert_eq!(inner.active_readers(), 1);
}
assert_eq!(inner.active_readers(), 0);
let old = inner.advance_epoch();
assert_eq!(old, 0);
assert_eq!(inner.current_epoch(), 1);
}
#[test]
fn test_retry_stats() {
use tempfile::tempdir;
let dir = tempdir().expect("create temp dir");
let path = dir.path().join("test_stats.trie");
let inner: PersistentARTrieChar<()> = PersistentARTrieChar::create(&path).expect("create");
inner.insert("test").expect("insert");
for _ in 0..10 {
let _ = inner.contains_optimistic("test", 5);
}
let stats = inner.retry_stats_snapshot();
assert!(stats.successful >= 10); assert_eq!(stats.retries, 0);
}
#[test]
fn test_concurrent_readers() {
use std::sync::Arc;
use std::thread;
use tempfile::tempdir;
let dir = tempdir().expect("create temp dir");
let path = dir.path().join("test_concurrent.trie");
{
let inner: PersistentARTrieChar<()> =
PersistentARTrieChar::create(&path).expect("create");
for i in 0..100 {
inner.insert(&format!("term{}", i)).expect("insert");
}
inner.sync().expect("sync");
}
let inner = Arc::new(PersistentARTrieChar::<()>::open(&path).expect("open"));
let handles: Vec<_> = (0..4)
.map(|t| {
let inner = inner.clone();
thread::spawn(move || {
let mut found = 0;
for i in 0..100 {
let _guard = inner.enter_epoch();
if let Some(true) = inner.contains_optimistic(&format!("term{}", i), 10) {
found += 1;
}
}
(t, found)
})
})
.collect();
for handle in handles {
let (thread_id, found) = handle.join().expect("thread join");
assert_eq!(found, 100, "Thread {} should find all 100 terms", thread_id);
}
}
#[test]
fn test_try_contains_optimistic() {
use tempfile::tempdir;
let dir = tempdir().expect("create temp dir");
let path = dir.path().join("test_try_contains.trie");
let inner: PersistentARTrieChar<()> = PersistentARTrieChar::create(&path).expect("create");
inner.insert("apple").expect("insert");
let result = inner.try_contains_optimistic("apple");
assert_eq!(result, Some(true));
let result = inner.try_contains_optimistic("banana");
assert_eq!(result, Some(false));
}
#[test]
fn test_unicode_optimistic() {
use tempfile::tempdir;
let dir = tempdir().expect("create temp dir");
let path = dir.path().join("test_unicode_optimistic.trie");
let inner: PersistentARTrieChar<()> = PersistentARTrieChar::create(&path).expect("create");
inner.insert("日本語").expect("insert");
inner.insert("中文").expect("insert");
inner.insert("🎉🎊🎋").expect("insert");
assert_eq!(inner.contains_optimistic("日本語", 10), Some(true));
assert_eq!(inner.contains_optimistic("中文", 10), Some(true));
assert_eq!(inner.contains_optimistic("🎉🎊🎋", 10), Some(true));
assert_eq!(inner.contains_optimistic("한글", 10), Some(false));
}
#[test]
fn test_document_transaction_basic() {
use tempfile::tempdir;
let dir = tempdir().expect("create temp dir");
let path = dir.path().join("test_doc_tx_basic.trie");
let inner: PersistentARTrieChar<u64> = PersistentARTrieChar::create(&path).expect("create");
let mut tx = inner.begin_document("doc_001").expect("begin");
assert!(tx.is_active());
assert!(tx.is_empty());
inner.tx_insert(&mut tx, "hello", Some(1));
inner.tx_insert(&mut tx, "world", Some(2));
inner.tx_insert(&mut tx, "foo", None);
assert_eq!(tx.len(), 3);
assert!(!tx.is_empty());
assert!(!inner.contains("hello"));
assert!(!inner.contains("world"));
assert!(!inner.contains("foo"));
let count = inner.commit_document(tx).expect("commit");
assert_eq!(count, 3);
assert!(inner.contains("hello"));
assert!(inner.contains("world"));
assert!(inner.contains("foo"));
assert_eq!(inner.len(), 3);
}
#[test]
fn test_document_transaction_abort() {
use tempfile::tempdir;
let dir = tempdir().expect("create temp dir");
let path = dir.path().join("test_doc_tx_abort.trie");
let inner: PersistentARTrieChar<u64> = PersistentARTrieChar::create(&path).expect("create");
inner.insert("existing").expect("insert");
let mut tx = inner.begin_document("doc_002").expect("begin");
inner.tx_insert(&mut tx, "new_term_1", Some(1));
inner.tx_insert(&mut tx, "new_term_2", Some(2));
inner.abort_document(tx).expect("abort");
assert!(!inner.contains("new_term_1"));
assert!(!inner.contains("new_term_2"));
assert!(inner.contains("existing"));
assert_eq!(inner.len(), 1);
}
#[test]
fn test_document_transaction_unicode() {
use tempfile::tempdir;
let dir = tempdir().expect("create temp dir");
let path = dir.path().join("test_doc_tx_unicode.trie");
let inner: PersistentARTrieChar<i64> = PersistentARTrieChar::create(&path).expect("create");
let mut tx = inner.begin_document("unicode_doc").expect("begin");
inner.tx_insert(&mut tx, "日本語", Some(1));
inner.tx_insert(&mut tx, "中文", Some(2));
inner.tx_insert(&mut tx, "🎉🎊🎋", Some(3));
inner.tx_insert_chars(&mut tx, &['한', '글'], Some(4));
inner.tx_insert_chars(&mut tx, &['π', '∑', '∫'], Some(5));
let count = inner.commit_document(tx).expect("commit");
assert_eq!(count, 5);
assert!(inner.contains("日本語"));
assert!(inner.contains("中文"));
assert!(inner.contains("🎉🎊🎋"));
assert!(inner.contains("한글"));
assert!(inner.contains("π∑∫"));
}
#[test]
fn test_document_transaction_empty() {
use tempfile::tempdir;
let dir = tempdir().expect("create temp dir");
let path = dir.path().join("test_doc_tx_empty.trie");
let inner: PersistentARTrieChar<()> = PersistentARTrieChar::create(&path).expect("create");
let tx = inner.begin_document("empty_doc").expect("begin");
let count = inner.commit_document(tx).expect("commit");
assert_eq!(count, 0);
assert_eq!(inner.len(), 0);
}
#[test]
fn test_document_transaction_recovery() {
use tempfile::tempdir;
let dir = tempdir().expect("create temp dir");
let path = dir.path().join("test_doc_tx_recovery.trie");
{
let inner: PersistentARTrieChar<i64> =
PersistentARTrieChar::create(&path).expect("create");
let mut tx = inner.begin_document("recovery_doc").expect("begin");
inner.tx_insert(&mut tx, "term1", Some(100));
inner.tx_insert(&mut tx, "term2", Some(200));
inner.tx_insert(&mut tx, "term3", Some(300));
inner.commit_document(tx).expect("commit");
inner.sync().expect("sync");
}
{
let inner: PersistentARTrieChar<i64> = PersistentARTrieChar::open(&path).expect("open");
assert!(inner.contains("term1"));
assert!(inner.contains("term2"));
assert!(inner.contains("term3"));
assert_eq!(inner.len(), 3);
}
}
#[test]
fn test_document_transaction_commit_twice_error() {
use tempfile::tempdir;
let dir = tempdir().expect("create temp dir");
let path = dir.path().join("test_doc_tx_commit_twice.trie");
let inner: PersistentARTrieChar<()> = PersistentARTrieChar::create(&path).expect("create");
let mut tx = inner.begin_document("test").expect("begin");
inner.tx_insert(&mut tx, "term", None);
inner.commit_document(tx).expect("commit");
let tx2 = inner.begin_document("test2").expect("begin");
inner.commit_document(tx2).expect("commit empty");
}
#[test]
fn test_document_transaction_multiple_sequential() {
use tempfile::tempdir;
let dir = tempdir().expect("create temp dir");
let path = dir.path().join("test_doc_tx_sequential.trie");
let inner: PersistentARTrieChar<u64> = PersistentARTrieChar::create(&path).expect("create");
let mut tx1 = inner.begin_document("doc1").expect("begin");
inner.tx_insert(&mut tx1, "apple", Some(1));
inner.tx_insert(&mut tx1, "apricot", Some(2));
inner.commit_document(tx1).expect("commit");
let mut tx2 = inner.begin_document("doc2").expect("begin");
inner.tx_insert(&mut tx2, "banana", Some(3));
inner.abort_document(tx2).expect("abort");
let mut tx3 = inner.begin_document("doc3").expect("begin");
inner.tx_insert(&mut tx3, "cherry", Some(4));
inner.tx_insert(&mut tx3, "coconut", Some(5));
inner.commit_document(tx3).expect("commit");
assert!(inner.contains("apple"));
assert!(inner.contains("apricot"));
assert!(!inner.contains("banana")); assert!(inner.contains("cherry"));
assert!(inner.contains("coconut"));
assert_eq!(inner.len(), 4);
}
#[test]
fn test_document_transaction_tx_insert_bytes() {
use tempfile::tempdir;
let dir = tempdir().expect("create temp dir");
let path = dir.path().join("test_doc_tx_bytes.trie");
let inner: PersistentARTrieChar<u64> = PersistentARTrieChar::create(&path).expect("create");
let mut tx = inner.begin_document("bytes_doc").expect("begin");
inner.tx_insert_bytes(&mut tx, b"hello", Some(1));
inner.tx_insert_bytes(&mut tx, b"world", Some(2));
inner.tx_insert_bytes(&mut tx, "日本語".as_bytes(), Some(3));
let count = inner.commit_document(tx).expect("commit");
assert_eq!(count, 3);
assert!(inner.contains("hello"));
assert!(inner.contains("world"));
assert!(inner.contains("日本語"));
}
#[test]
fn test_document_transaction_tx_increment() {
use tempfile::tempdir;
let dir = tempdir().expect("create temp dir");
let path = dir.path().join("test_doc_tx_increment.trie");
let mut inner: PersistentARTrieChar<u64> =
PersistentARTrieChar::create(&path).expect("create");
inner.increment("term_a", 100).expect("initial increment");
inner.increment("term_b", 50).expect("initial increment");
let mut tx = inner.begin_document("increment_doc").expect("begin");
inner.tx_increment(&mut tx, "term_a", 25); inner.tx_increment(&mut tx, "term_b", 10); inner.tx_increment(&mut tx, "term_c", 75); inner.tx_increment(&mut tx, "term_a", 5);
assert_eq!(tx.increment_count(), 4);
assert_eq!(tx.set_count(), 0);
assert_eq!(tx.len(), 4);
assert_eq!(inner.get("term_a"), Some(100u64));
assert_eq!(inner.get("term_b"), Some(50u64));
assert!(inner.get("term_c").is_none());
let count = inner.commit_document(tx).expect("commit");
assert_eq!(count, 4);
assert_eq!(inner.get("term_a"), Some(130u64));
assert_eq!(inner.get("term_b"), Some(60u64));
assert_eq!(inner.get("term_c"), Some(75u64));
}
#[test]
fn test_document_transaction_mixed_insert_and_increment() {
use tempfile::tempdir;
let dir = tempdir().expect("create temp dir");
let path = dir.path().join("test_doc_tx_mixed.trie");
let inner: PersistentARTrieChar<u64> = PersistentARTrieChar::create(&path).expect("create");
let mut tx = inner.begin_document("mixed_doc").expect("begin");
inner.tx_insert(&mut tx, "set_term", Some(100));
inner.tx_increment(&mut tx, "inc_term", 50);
assert_eq!(tx.set_count(), 1);
assert_eq!(tx.increment_count(), 1);
assert_eq!(tx.len(), 2);
let count = inner.commit_document(tx).expect("commit");
assert_eq!(count, 2);
assert_eq!(inner.get("set_term"), Some(100u64));
assert_eq!(inner.get("inc_term"), Some(50u64));
}
#[test]
fn test_document_transaction_increment_recovery() {
use tempfile::tempdir;
let dir = tempdir().expect("create temp dir");
let path = dir.path().join("test_doc_tx_inc_recovery.trie");
{
let mut inner: PersistentARTrieChar<u64> =
PersistentARTrieChar::create(&path).expect("create");
inner.increment("existing", 100).expect("initial");
let mut tx = inner.begin_document("recovery_doc").expect("begin");
inner.tx_increment(&mut tx, "existing", 50);
inner.tx_increment(&mut tx, "new_term", 75);
inner.commit_document(tx).expect("commit");
assert_eq!(inner.get("existing"), Some(150u64));
assert_eq!(inner.get("new_term"), Some(75u64));
}
{
let inner: PersistentARTrieChar<u64> = PersistentARTrieChar::open(&path).expect("open");
assert_eq!(inner.get("existing"), Some(150u64));
assert_eq!(inner.get("new_term"), Some(75u64));
}
}
#[test]
fn test_insert_batch_basic() {
use tempfile::tempdir;
let dir = tempdir().expect("create temp dir");
let path = dir.path().join("test_batch_basic.trie");
let mut inner: PersistentARTrieChar<u64> =
PersistentARTrieChar::create(&path).expect("create");
let entries = vec![
("hello".to_string(), Some(1u64)),
("world".to_string(), Some(2u64)),
("foo".to_string(), None),
("bar".to_string(), Some(4u64)),
];
let count = inner.insert_batch(&entries);
assert_eq!(count, 4);
assert_eq!(inner.len(), 4);
assert!(inner.contains("hello"));
assert!(inner.contains("world"));
assert!(inner.contains("foo"));
assert!(inner.contains("bar"));
}
#[test]
fn test_insert_batch_unicode() {
use tempfile::tempdir;
let dir = tempdir().expect("create temp dir");
let path = dir.path().join("test_batch_unicode.trie");
let mut inner: PersistentARTrieChar<i64> =
PersistentARTrieChar::create(&path).expect("create");
let entries = vec![
("日本語".to_string(), Some(1)),
("中文".to_string(), Some(2)),
("한글".to_string(), Some(3)),
("🎉🎊🎋".to_string(), Some(4)),
];
let count = inner.insert_batch(&entries);
assert_eq!(count, 4);
assert!(inner.contains("日本語"));
assert!(inner.contains("中文"));
assert!(inner.contains("한글"));
assert!(inner.contains("🎉🎊🎋"));
}
#[test]
fn test_insert_batch_chars() {
use tempfile::tempdir;
let dir = tempdir().expect("create temp dir");
let path = dir.path().join("test_batch_chars.trie");
let mut inner: PersistentARTrieChar<u64> =
PersistentARTrieChar::create(&path).expect("create");
let entries: Vec<(&[char], Option<u64>)> = vec![
(&['h', 'e', 'l', 'l', 'o'][..], Some(1)),
(&['日', '本', '語'][..], Some(2)),
(&['π', '∑', '∫'][..], None),
];
let count = inner.insert_batch_chars(&entries);
assert_eq!(count, 3);
assert!(inner.contains("hello"));
assert!(inner.contains("日本語"));
assert!(inner.contains("π∑∫"));
}
#[test]
fn test_insert_batch_sorted() {
use tempfile::tempdir;
let dir = tempdir().expect("create temp dir");
let path = dir.path().join("test_batch_sorted.trie");
let mut inner: PersistentARTrieChar<u64> =
PersistentARTrieChar::create(&path).expect("create");
let entries = vec![
("zebra".to_string(), Some(1u64)),
("apple".to_string(), Some(2u64)),
("mango".to_string(), Some(3u64)),
("apricot".to_string(), Some(4u64)),
];
let count = inner.insert_batch_sorted(entries);
assert_eq!(count, 4);
assert!(inner.contains("apple"));
assert!(inner.contains("apricot"));
assert!(inner.contains("mango"));
assert!(inner.contains("zebra"));
}
#[test]
fn test_insert_batch_chars_sorted() {
use tempfile::tempdir;
let dir = tempdir().expect("create temp dir");
let path = dir.path().join("test_batch_chars_sorted.trie");
let mut inner: PersistentARTrieChar<u64> =
PersistentARTrieChar::create(&path).expect("create");
let entries: Vec<(Vec<char>, Option<u64>)> = vec![
(vec!['z', 'e', 'b', 'r', 'a'], Some(1)),
(vec!['a', 'p', 'p', 'l', 'e'], Some(2)),
(vec!['m', 'a', 'n', 'g', 'o'], Some(3)),
];
let count = inner.insert_batch_chars_sorted(entries);
assert_eq!(count, 3);
assert!(inner.contains("apple"));
assert!(inner.contains("mango"));
assert!(inner.contains("zebra"));
}
#[test]
fn test_insert_batch_bytes() {
use tempfile::tempdir;
let dir = tempdir().expect("create temp dir");
let path = dir.path().join("test_batch_bytes.trie");
let mut inner: PersistentARTrieChar<u64> =
PersistentARTrieChar::create(&path).expect("create");
let entries: Vec<(&[u8], Option<u64>)> = vec![
(b"hello" as &[u8], Some(1)),
(b"world" as &[u8], Some(2)),
("日本語".as_bytes(), Some(3)),
];
let count = inner.insert_batch_bytes(&entries);
assert_eq!(count, 3);
assert!(inner.contains("hello"));
assert!(inner.contains("world"));
assert!(inner.contains("日本語"));
}
#[test]
fn test_insert_batch_bytes_sorted() {
use tempfile::tempdir;
let dir = tempdir().expect("create temp dir");
let path = dir.path().join("test_batch_bytes_sorted.trie");
let mut inner: PersistentARTrieChar<u64> =
PersistentARTrieChar::create(&path).expect("create");
let entries: Vec<(Vec<u8>, Option<u64>)> = vec![
(b"zebra".to_vec(), Some(1)),
(b"apple".to_vec(), Some(2)),
(b"mango".to_vec(), Some(3)),
];
let count = inner.insert_batch_bytes_sorted(entries);
assert_eq!(count, 3);
assert!(inner.contains("apple"));
assert!(inner.contains("mango"));
assert!(inner.contains("zebra"));
}
#[test]
fn test_insert_batch_empty() {
use tempfile::tempdir;
let dir = tempdir().expect("create temp dir");
let path = dir.path().join("test_batch_empty.trie");
let mut inner: PersistentARTrieChar<u64> =
PersistentARTrieChar::create(&path).expect("create");
let entries: Vec<(String, Option<u64>)> = vec![];
let count = inner.insert_batch(&entries);
assert_eq!(count, 0);
assert_eq!(inner.len(), 0);
}
#[test]
fn test_insert_batch_duplicates() {
use tempfile::tempdir;
let dir = tempdir().expect("create temp dir");
let path = dir.path().join("test_batch_duplicates.trie");
let mut inner: PersistentARTrieChar<u64> =
PersistentARTrieChar::create(&path).expect("create");
let entries1 = vec![
("apple".to_string(), Some(1u64)),
("banana".to_string(), Some(2u64)),
];
let count1 = inner.insert_batch(&entries1);
assert_eq!(count1, 2);
let entries2 = vec![
("apple".to_string(), Some(10u64)), ("cherry".to_string(), Some(3u64)), ("banana".to_string(), Some(20u64)), ];
let count2 = inner.insert_batch(&entries2);
assert_eq!(count2, 1);
assert_eq!(inner.len(), 3); }
#[test]
fn test_insert_batch_recovery() {
use tempfile::tempdir;
let dir = tempdir().expect("create temp dir");
let path = dir.path().join("test_batch_recovery.trie");
{
let mut inner: PersistentARTrieChar<i64> =
PersistentARTrieChar::create(&path).expect("create");
let entries = vec![
("term1".to_string(), Some(100i64)),
("term2".to_string(), Some(200i64)),
("term3".to_string(), Some(300i64)),
];
inner.insert_batch(&entries);
inner.sync().expect("sync");
}
{
let inner: PersistentARTrieChar<i64> = PersistentARTrieChar::open(&path).expect("open");
assert!(inner.contains("term1"));
assert!(inner.contains("term2"));
assert!(inner.contains("term3"));
assert_eq!(inner.len(), 3);
}
}
#[test]
fn test_insert_batch_large() {
use tempfile::tempdir;
let dir = tempdir().expect("create temp dir");
let path = dir.path().join("test_batch_large.trie");
let mut inner: PersistentARTrieChar<u64> =
PersistentARTrieChar::create(&path).expect("create");
let entries: Vec<(String, Option<u64>)> = (0..1000)
.map(|i| (format!("term_{:05}", i), Some(i as u64)))
.collect();
let count = inner.insert_batch(&entries);
assert_eq!(count, 1000);
assert_eq!(inner.len(), 1000);
assert!(inner.contains("term_00000"));
assert!(inner.contains("term_00500"));
assert!(inner.contains("term_00999"));
}
#[test]
fn test_merge_from_batched_basic() {
use tempfile::tempdir;
let dir = tempdir().expect("create temp dir");
let path1 = dir.path().join("test_merge_batched_src.trie");
let path2 = dir.path().join("test_merge_batched_dst.trie");
let mut src: PersistentARTrieChar<i64> =
PersistentARTrieChar::create(&path1).expect("create");
src.increment("apple", 10).expect("increment");
src.increment("banana", 20).expect("increment");
src.increment("cherry", 30).expect("increment");
let mut dst: PersistentARTrieChar<i64> =
PersistentARTrieChar::create(&path2).expect("create");
dst.increment("apple", 5).expect("increment");
dst.increment("date", 40).expect("increment");
let count = dst
.merge_from_batched(&src, |a, b| a + b, 2)
.expect("merge");
assert_eq!(count, 3);
assert!(dst.contains("apple")); assert!(dst.contains("banana")); assert!(dst.contains("cherry")); assert!(dst.contains("date")); assert_eq!(dst.len(), 4);
}
#[test]
fn test_merge_from_batched_unicode() {
use tempfile::tempdir;
let dir = tempdir().expect("create temp dir");
let path1 = dir.path().join("test_merge_batched_unicode_src.trie");
let path2 = dir.path().join("test_merge_batched_unicode_dst.trie");
let mut src: PersistentARTrieChar<i64> =
PersistentARTrieChar::create(&path1).expect("create");
src.increment("日本語", 1).expect("increment");
src.increment("中文", 2).expect("increment");
src.increment("한글", 3).expect("increment");
let mut dst: PersistentARTrieChar<i64> =
PersistentARTrieChar::create(&path2).expect("create");
dst.increment("日本語", 100).expect("increment");
let count = dst
.merge_from_batched(&src, |a, b| a + b, 10)
.expect("merge");
assert_eq!(count, 3);
assert!(dst.contains("日本語"));
assert!(dst.contains("中文"));
assert!(dst.contains("한글"));
}
#[test]
fn test_merge_from_batched_empty() {
use tempfile::tempdir;
let dir = tempdir().expect("create temp dir");
let path1 = dir.path().join("test_merge_batched_empty_src.trie");
let path2 = dir.path().join("test_merge_batched_empty_dst.trie");
let src: PersistentARTrieChar<i64> = PersistentARTrieChar::create(&path1).expect("create");
let mut dst: PersistentARTrieChar<i64> =
PersistentARTrieChar::create(&path2).expect("create");
dst.increment("existing", 100).expect("increment");
let count = dst
.merge_from_batched(&src, |a, b| a + b, 100)
.expect("merge");
assert_eq!(count, 0);
assert_eq!(dst.len(), 1);
}
#[cfg(feature = "parallel-merge")]
#[test]
fn test_merge_from_parallel_basic() {
use tempfile::tempdir;
let dir = tempdir().expect("create temp dir");
let path1 = dir.path().join("test_merge_parallel_src.trie");
let path2 = dir.path().join("test_merge_parallel_dst.trie");
let mut src: PersistentARTrieChar<i64> =
PersistentARTrieChar::create(&path1).expect("create");
for i in 0..100 {
src.increment(&format!("term_{:03}", i), i as i64)
.expect("increment");
}
let mut dst: PersistentARTrieChar<i64> =
PersistentARTrieChar::create(&path2).expect("create");
for i in 0..50 {
dst.increment(&format!("term_{:03}", i), 1000)
.expect("increment");
}
let count = dst.merge_from_parallel(&src, |a, b| a + b).expect("merge");
assert_eq!(count, 100);
assert_eq!(dst.len(), 100);
for i in 0..100 {
assert!(dst.contains(&format!("term_{:03}", i)));
}
}
#[cfg(feature = "parallel-merge")]
#[test]
fn test_merge_from_batched_parallel_basic() {
use tempfile::tempdir;
let dir = tempdir().expect("create temp dir");
let path1 = dir.path().join("test_merge_batched_parallel_src.trie");
let path2 = dir.path().join("test_merge_batched_parallel_dst.trie");
let mut src: PersistentARTrieChar<i64> =
PersistentARTrieChar::create(&path1).expect("create");
for i in 0..50 {
src.increment(&format!("key_{:02}", i), i as i64)
.expect("increment");
}
let mut dst: PersistentARTrieChar<i64> =
PersistentARTrieChar::create(&path2).expect("create");
dst.increment("key_00", 1000).expect("increment");
let count = dst
.merge_from_batched_parallel(&src, |a, b| a + b, 10)
.expect("merge");
assert_eq!(count, 50);
assert_eq!(dst.len(), 50);
}
#[cfg(feature = "parallel-merge")]
#[test]
fn test_merge_from_parallel_unicode() {
use tempfile::tempdir;
let dir = tempdir().expect("create temp dir");
let path1 = dir.path().join("test_merge_parallel_unicode_src.trie");
let path2 = dir.path().join("test_merge_parallel_unicode_dst.trie");
let mut src: PersistentARTrieChar<i64> =
PersistentARTrieChar::create(&path1).expect("create");
src.increment("日本語_001", 1).expect("increment");
src.increment("日本語_002", 2).expect("increment");
src.increment("中文_001", 3).expect("increment");
src.increment("한글_001", 4).expect("increment");
src.increment("🎉_emoji", 5).expect("increment");
src.increment("ascii_test", 6).expect("increment");
let mut dst: PersistentARTrieChar<i64> =
PersistentARTrieChar::create(&path2).expect("create");
let count = dst.merge_from_parallel(&src, |a, b| a + b).expect("merge");
assert_eq!(count, 6);
assert!(dst.contains("日本語_001"));
assert!(dst.contains("日本語_002"));
assert!(dst.contains("中文_001"));
assert!(dst.contains("한글_001"));
assert!(dst.contains("🎉_emoji"));
assert!(dst.contains("ascii_test"));
}
#[cfg(feature = "group-commit")]
#[test]
fn test_group_commit_enable_disable() {
use crate::persistent_artrie::group_commit::GroupCommitConfig;
use tempfile::tempdir;
let dir = tempdir().expect("create temp dir");
let path = dir.path().join("test_group_commit.trie");
let mut trie: PersistentARTrieChar<()> =
PersistentARTrieChar::create(&path).expect("create");
assert!(!trie.is_group_commit_enabled());
assert!(trie.group_commit_stats().is_none());
trie.enable_group_commit(GroupCommitConfig::default())
.expect("enable group commit");
assert!(trie.is_group_commit_enabled());
assert!(trie.group_commit_stats().is_some());
let result = trie.enable_group_commit(GroupCommitConfig::default());
assert!(result.is_err());
trie.disable_group_commit().expect("disable group commit");
assert!(!trie.is_group_commit_enabled());
assert!(trie.group_commit_stats().is_none());
trie.disable_group_commit().expect("disable again");
}
#[cfg(feature = "group-commit")]
#[test]
fn test_group_commit_with_inserts() {
use crate::persistent_artrie::group_commit::GroupCommitConfig;
use tempfile::tempdir;
let dir = tempdir().expect("create temp dir");
let path = dir.path().join("test_group_commit_inserts.trie");
let mut trie: PersistentARTrieChar<()> =
PersistentARTrieChar::create(&path).expect("create");
let config = GroupCommitConfig {
max_batch_size: 10,
max_batch_delay_us: 1_000, dedicated_commit_thread: true,
adaptive_batching: false,
..Default::default()
};
trie.enable_group_commit(config)
.expect("enable group commit");
trie.insert("hello").expect("insert");
trie.insert("world").expect("insert");
trie.insert("foo").expect("insert");
trie.insert("bar").expect("insert");
trie.insert("baz").expect("insert");
assert!(trie.contains("hello"));
assert!(trie.contains("world"));
assert!(trie.contains("foo"));
assert!(trie.contains("bar"));
assert!(trie.contains("baz"));
assert_eq!(trie.len(), 5);
let stats = trie.group_commit_stats().expect("stats");
assert!(stats.records_committed > 0, "should have committed records");
trie.disable_group_commit().expect("disable");
trie.insert("after_disable").expect("insert");
assert!(trie.contains("after_disable"));
}
#[cfg(feature = "group-commit")]
#[test]
fn test_group_commit_with_unicode() {
use crate::persistent_artrie::group_commit::GroupCommitConfig;
use tempfile::tempdir;
let dir = tempdir().expect("create temp dir");
let path = dir.path().join("test_group_commit_unicode.trie");
let mut trie: PersistentARTrieChar<()> =
PersistentARTrieChar::create(&path).expect("create");
trie.enable_group_commit(GroupCommitConfig::low_latency())
.expect("enable group commit");
trie.insert("こんにちは").expect("insert");
trie.insert("你好").expect("insert");
trie.insert("안녕하세요").expect("insert");
trie.insert("🎉🎊🎋").expect("insert");
assert!(trie.contains("こんにちは"));
assert!(trie.contains("你好"));
assert!(trie.contains("안녕하세요"));
assert!(trie.contains("🎉🎊🎋"));
}
#[cfg(feature = "group-commit")]
#[test]
fn test_group_commit_high_throughput_config() {
use crate::persistent_artrie::group_commit::GroupCommitConfig;
use tempfile::tempdir;
let dir = tempdir().expect("create temp dir");
let path = dir.path().join("test_group_commit_throughput.trie");
let mut trie: PersistentARTrieChar<i64> =
PersistentARTrieChar::create(&path).expect("create");
trie.enable_group_commit(GroupCommitConfig::high_throughput())
.expect("enable group commit");
for i in 0..100 {
trie.increment(&format!("counter_{}", i), 1)
.expect("increment");
}
assert_eq!(trie.len(), 100);
for i in 0..100 {
assert!(trie.contains(&format!("counter_{}", i)));
}
let stats = trie.group_commit_stats().expect("stats");
let efficiency = stats.batching_efficiency();
println!(
"High throughput batching efficiency: {:.2} records/fsync",
efficiency
);
assert!(
stats.records_committed >= 100,
"should have committed at least 100 records"
);
}
#[cfg(feature = "group-commit")]
#[test]
fn test_group_commit_recovery() {
use crate::persistent_artrie::group_commit::GroupCommitConfig;
use tempfile::tempdir;
let dir = tempdir().expect("create temp dir");
let path = dir.path().join("test_group_commit_recovery.trie");
{
let mut trie: PersistentARTrieChar<()> =
PersistentARTrieChar::create(&path).expect("create");
trie.enable_group_commit(GroupCommitConfig::default())
.expect("enable group commit");
trie.insert("persisted_1").expect("insert");
trie.insert("persisted_2").expect("insert");
trie.insert("persisted_3").expect("insert");
trie.sync().expect("sync");
}
{
let trie: PersistentARTrieChar<()> = PersistentARTrieChar::open(&path).expect("open");
assert!(trie.contains("persisted_1"));
assert!(trie.contains("persisted_2"));
assert!(trie.contains("persisted_3"));
assert_eq!(trie.len(), 3);
}
}
#[cfg(feature = "group-commit")]
#[test]
fn test_group_commit_stats_tracking() {
use crate::persistent_artrie::group_commit::GroupCommitConfig;
use tempfile::tempdir;
let dir = tempdir().expect("create temp dir");
let path = dir.path().join("test_group_commit_stats.trie");
let mut trie: PersistentARTrieChar<()> =
PersistentARTrieChar::create(&path).expect("create");
trie.enable_group_commit(GroupCommitConfig::default())
.expect("enable group commit");
let initial_stats = trie.group_commit_stats().expect("stats");
let initial_committed = initial_stats.records_committed;
trie.insert("term1").expect("insert");
trie.insert("term2").expect("insert");
trie.remove("term1").expect("remove");
std::thread::sleep(std::time::Duration::from_millis(50));
let final_stats = trie.group_commit_stats().expect("stats");
assert!(
final_stats.records_committed > initial_committed,
"records_committed should have increased: {} -> {}",
initial_committed,
final_stats.records_committed
);
}
#[test]
fn test_cache_stats_basic() {
use tempfile::tempdir;
let dir = tempdir().expect("create temp dir");
let path = dir.path().join("test_cache_stats.trie");
let trie: PersistentARTrieChar<()> = PersistentARTrieChar::create(&path).expect("create");
let (hits, misses) = trie.cache_counts();
assert_eq!(hits, 0);
assert_eq!(misses, 0);
assert_eq!(trie.cache_total_accesses(), 0);
assert_eq!(trie.cache_hit_rate(), 1.0);
trie.record_cache_hit();
trie.record_cache_hit();
trie.record_cache_hit();
trie.record_cache_miss();
let (hits, misses) = trie.cache_counts();
assert_eq!(hits, 3);
assert_eq!(misses, 1);
assert_eq!(trie.cache_total_accesses(), 4);
let hit_rate = trie.cache_hit_rate();
assert!(
(hit_rate - 0.75).abs() < 0.001,
"Hit rate should be 0.75, got {}",
hit_rate
);
}
#[test]
fn test_cache_stats_and_reset() {
use tempfile::tempdir;
let dir = tempdir().expect("create temp dir");
let path = dir.path().join("test_cache_reset.trie");
let trie: PersistentARTrieChar<()> = PersistentARTrieChar::create(&path).expect("create");
trie.record_cache_hit();
trie.record_cache_hit();
trie.record_cache_miss();
let (hit_rate, hits, misses) = trie.cache_stats_and_reset();
assert_eq!(hits, 2);
assert_eq!(misses, 1);
assert!(
(hit_rate - 0.666).abs() < 0.01,
"Hit rate should be ~0.666, got {}",
hit_rate
);
let (hits, misses) = trie.cache_counts();
assert_eq!(hits, 0);
assert_eq!(misses, 0);
}
#[test]
fn test_memory_monitor_enable_disable() {
use crate::persistent_artrie::memory_monitor::MemoryPressureConfig;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use tempfile::tempdir;
let dir = tempdir().expect("create temp dir");
let path = dir.path().join("test_memory_monitor.trie");
let trie: PersistentARTrieChar<()> = PersistentARTrieChar::create(&path).expect("create");
assert!(!trie.has_memory_monitor());
assert!(trie.memory_stats().is_none());
assert!(trie.memory_pressure_level().is_none());
let callback_count = Arc::new(AtomicUsize::new(0));
let count_clone = Arc::clone(&callback_count);
let result =
trie.enable_memory_monitor(MemoryPressureConfig::default(), move |_level, _stats| {
count_clone.fetch_add(1, Ordering::Relaxed);
});
assert!(result.is_ok(), "enable_memory_monitor should succeed");
assert!(trie.has_memory_monitor());
let stats = trie.memory_stats();
assert!(stats.is_some(), "memory_stats should return Some");
let level = trie.memory_pressure_level();
assert!(level.is_some(), "memory_pressure_level should return Some");
trie.disable_memory_monitor();
assert!(!trie.has_memory_monitor());
assert!(trie.memory_stats().is_none());
}
#[test]
fn test_memory_monitor_default() {
use tempfile::tempdir;
let dir = tempdir().expect("create temp dir");
let path = dir.path().join("test_memory_default.trie");
let trie: PersistentARTrieChar<()> = PersistentARTrieChar::create(&path).expect("create");
let result = trie.enable_memory_monitor_default();
assert!(
result.is_ok(),
"enable_memory_monitor_default should succeed"
);
assert!(trie.has_memory_monitor());
let stats = trie.memory_stats().expect("stats should be available");
assert!(stats.mem_total > 0, "System should have some memory");
trie.disable_memory_monitor();
}
#[test]
fn test_epoch_checkpointing_enable_disable() {
use tempfile::tempdir;
let dir = tempdir().expect("create temp dir");
let path = dir.path().join("test_epoch_checkpointing.trie");
let trie: PersistentARTrieChar<()> = PersistentARTrieChar::create(&path).expect("create");
assert!(!trie.has_epoch_checkpointing());
assert!(trie.current_epoch_id().is_none());
assert!(trie.epoch_stats().is_none());
let result = trie.enable_epoch_checkpointing_default();
assert!(
result.is_ok(),
"enable_epoch_checkpointing_default should succeed"
);
assert!(trie.has_epoch_checkpointing());
let epoch_id = trie.current_epoch_id();
assert!(epoch_id.is_some(), "current_epoch_id should be Some");
let stats = trie.epoch_stats();
assert!(stats.is_some(), "epoch_stats should be Some");
trie.disable_epoch_checkpointing();
assert!(!trie.has_epoch_checkpointing());
assert!(trie.current_epoch_id().is_none());
}
#[test]
fn test_epoch_checkpointing_record_operations() {
use tempfile::tempdir;
let dir = tempdir().expect("create temp dir");
let path = dir.path().join("test_epoch_record_ops.trie");
let trie: PersistentARTrieChar<()> = PersistentARTrieChar::create(&path).expect("create");
trie.enable_epoch_checkpointing_default().expect("enable");
let initial_epoch = trie.current_epoch_id().expect("epoch_id");
for _ in 0..10 {
let epoch = trie.record_epoch_operation(100);
assert!(epoch.is_some());
}
let current_epoch = trie.current_epoch_id().expect("epoch_id");
assert_eq!(
initial_epoch, current_epoch,
"Epoch should not have advanced yet"
);
let metadata = trie.epoch_metadata().expect("metadata");
let current_epoch_meta = metadata
.iter()
.find(|m| m.id == current_epoch)
.expect("current epoch");
assert_eq!(
current_epoch_meta.operation_count, 10,
"Should have recorded 10 operations"
);
assert_eq!(
current_epoch_meta.wal_size_bytes, 1000,
"Should have recorded 1000 WAL bytes"
);
}
#[test]
fn test_epoch_checkpointing_high_throughput_config() {
use tempfile::tempdir;
let dir = tempdir().expect("create temp dir");
let path = dir.path().join("test_epoch_high_throughput.trie");
let trie: PersistentARTrieChar<()> = PersistentARTrieChar::create(&path).expect("create");
let result = trie.enable_epoch_checkpointing_high_throughput();
assert!(
result.is_ok(),
"enable_epoch_checkpointing_high_throughput should succeed"
);
assert!(trie.has_epoch_checkpointing());
let config = trie.epoch_config().expect("config");
assert!(
config.max_ops_per_epoch > 10_000,
"High-throughput should have high ops limit"
);
}
#[test]
fn test_epoch_checkpointing_low_latency_config() {
use tempfile::tempdir;
let dir = tempdir().expect("create temp dir");
let path = dir.path().join("test_epoch_low_latency.trie");
let trie: PersistentARTrieChar<()> = PersistentARTrieChar::create(&path).expect("create");
let result = trie.enable_epoch_checkpointing_low_latency();
assert!(
result.is_ok(),
"enable_epoch_checkpointing_low_latency should succeed"
);
assert!(trie.has_epoch_checkpointing());
let config = trie.epoch_config().expect("config");
assert!(
config.epoch_duration.as_millis() < 1000,
"Low-latency should have short epoch duration"
);
}
#[test]
fn test_epoch_metadata() {
use tempfile::tempdir;
let dir = tempdir().expect("create temp dir");
let path = dir.path().join("test_epoch_metadata.trie");
let trie: PersistentARTrieChar<()> = PersistentARTrieChar::create(&path).expect("create");
trie.enable_epoch_checkpointing_default().expect("enable");
let metadata = trie.epoch_metadata().expect("metadata");
assert!(
!metadata.is_empty(),
"Should have at least one epoch's metadata"
);
let first = &metadata[0];
assert_eq!(first.id, trie.current_epoch_id().expect("epoch_id"));
}
#[test]
fn test_enhanced_recovery_mode_is_normal() {
assert!(EnhancedRecoveryMode::Normal.is_normal());
assert!(EnhancedRecoveryMode::CreatedNew.is_normal());
assert!(!EnhancedRecoveryMode::RebuiltFromWal.is_normal());
assert!(!EnhancedRecoveryMode::RebuiltFromArchives.is_normal());
}
#[test]
fn test_enhanced_recovery_mode_required_rebuild() {
assert!(!EnhancedRecoveryMode::Normal.required_rebuild());
assert!(!EnhancedRecoveryMode::CreatedNew.required_rebuild());
assert!(EnhancedRecoveryMode::RebuiltFromWal.required_rebuild());
assert!(EnhancedRecoveryMode::RebuiltFromArchives.required_rebuild());
}
#[test]
fn test_enhanced_recovery_stats_normal() {
let stats = EnhancedRecoveryStats::normal();
assert!(stats.mode.is_normal());
assert_eq!(stats.records_replayed, 0);
assert_eq!(stats.epochs_recovered, 0);
}
#[test]
fn test_enhanced_recovery_stats_created_new() {
let stats = EnhancedRecoveryStats::created_new();
assert_eq!(stats.mode, EnhancedRecoveryMode::CreatedNew);
assert!(stats.mode.is_normal());
}
#[test]
fn test_open_with_full_recovery_creates_new() {
use tempfile::tempdir;
let dir = tempdir().expect("create temp dir");
let path = dir.path().join("new_full_recovery.trie");
let (trie, stats): (PersistentARTrieChar<i64>, _) =
PersistentARTrieChar::open_with_full_recovery(
&path,
None, WalConfig::default(),
)
.expect("open_with_full_recovery");
assert_eq!(stats.mode, EnhancedRecoveryMode::CreatedNew);
assert_eq!(stats.records_replayed, 0);
assert_eq!(trie.len(), 0); }
#[test]
fn test_open_with_full_recovery_normal_open() {
use tempfile::tempdir;
let dir = tempdir().expect("create temp dir");
let path = dir.path().join("existing_full_recovery.trie");
{
let trie: PersistentARTrieChar<()> =
PersistentARTrieChar::create(&path).expect("create");
trie.insert("hello").expect("ins");
trie.checkpoint().expect("checkpoint");
}
let (trie, stats): (PersistentARTrieChar<()>, _) =
PersistentARTrieChar::open_with_full_recovery(&path, None, WalConfig::default())
.expect("open_with_full_recovery");
assert_eq!(stats.mode, EnhancedRecoveryMode::Normal);
assert!(trie.contains("hello")); }
#[test]
fn test_incremental_recovery_empty_wal() {
use crate::persistent_artrie::recovery::IncrementalRecovery;
use crate::persistent_artrie::wal::WalWriter;
use tempfile::tempdir;
let dir = tempdir().expect("create temp dir");
let wal_path = dir.path().join("empty.wal");
{
let _wal = WalWriter::create(&wal_path).expect("create wal");
}
let mut recovery: IncrementalRecovery =
PersistentARTrieChar::<()>::incremental_recovery(&wal_path).expect("recovery");
let batch = recovery.next_batch(10).expect("next_batch");
assert!(batch.is_none(), "Empty WAL should return no batches");
}
mod lsn_api_tests {
use super::*;
use tempfile::tempdir;
#[test]
fn test_current_lsn_starts_at_one_for_persistent() {
let dir = tempdir().expect("create temp dir");
let path = dir.path().join("lsn_test.trie");
let inner: PersistentARTrieChar<i32> =
PersistentARTrieChar::create(&path).expect("create");
assert_eq!(inner.current_lsn(), 1);
}
#[test]
fn test_current_lsn_starts_at_one_for_in_memory() {
let inner: PersistentARTrieChar<i32> = PersistentARTrieChar::new();
assert_eq!(inner.current_lsn(), 1);
}
#[test]
fn test_current_lsn_increases_after_insert() {
let dir = tempdir().expect("create temp dir");
let path = dir.path().join("lsn_test.trie");
let inner: PersistentARTrieChar<i32> =
PersistentARTrieChar::create(&path).expect("create");
let before = inner.current_lsn();
inner.upsert("key1", 42).expect("upsert");
let after = inner.current_lsn();
assert!(
after > before,
"LSN should increase after insert: before={}, after={}",
before,
after
);
}
#[test]
fn test_current_lsn_increases_after_remove() {
let dir = tempdir().expect("create temp dir");
let path = dir.path().join("lsn_test.trie");
let inner: PersistentARTrieChar<i32> =
PersistentARTrieChar::create(&path).expect("create");
inner.upsert("key1", 42).expect("upsert");
let before = inner.current_lsn();
inner.remove("key1").expect("remove");
let after = inner.current_lsn();
assert!(
after > before,
"LSN should increase after remove: before={}, after={}",
before,
after
);
}
#[test]
fn test_synced_lsn_none_for_in_memory() {
let inner: PersistentARTrieChar<i32> = PersistentARTrieChar::new();
assert!(
inner.synced_lsn().is_none(),
"In-memory trie should have no synced LSN"
);
}
#[test]
fn test_synced_lsn_after_sync() {
let dir = tempdir().expect("create temp dir");
let path = dir.path().join("lsn_test.trie");
let inner: PersistentARTrieChar<i32> =
PersistentARTrieChar::create(&path).expect("create");
inner.upsert("key1", 42).expect("upsert");
inner.upsert("key2", 43).expect("upsert");
let synced_before = inner
.synced_lsn()
.expect("persistent trie should have synced_lsn");
assert_eq!(
synced_before,
inner.current_lsn().saturating_sub(1),
"Immediate durability should sync through the last acknowledged write"
);
inner.sync().expect("sync should succeed");
let synced_after = inner
.synced_lsn()
.expect("persistent trie should have synced_lsn");
assert!(
synced_after >= synced_before,
"synced_lsn should not go backwards after sync: before={}, after={}",
synced_before,
synced_after
);
}
#[test]
fn test_synced_lsn_invariant() {
let dir = tempdir().expect("create temp dir");
let path = dir.path().join("lsn_test.trie");
let inner: PersistentARTrieChar<i32> =
PersistentARTrieChar::create(&path).expect("create");
inner.upsert("key1", 42).expect("upsert");
inner.sync().expect("sync should succeed");
inner.upsert("key2", 43).expect("upsert");
let current = inner.current_lsn();
let synced = inner
.synced_lsn()
.expect("persistent trie should have synced_lsn");
assert!(
synced < current,
"synced_lsn ({}) should be less than current_lsn ({})",
synced,
current
);
}
#[test]
fn test_lsn_monotonically_increasing() {
let dir = tempdir().expect("create temp dir");
let path = dir.path().join("lsn_test.trie");
let inner: PersistentARTrieChar<i32> =
PersistentARTrieChar::create(&path).expect("create");
let mut prev_lsn = inner.current_lsn();
for i in 0..10 {
inner.upsert(&format!("key{}", i), i).expect("upsert");
let curr_lsn = inner.current_lsn();
assert!(
curr_lsn > prev_lsn,
"LSN should increase monotonically: prev={}, curr={}",
prev_lsn,
curr_lsn
);
prev_lsn = curr_lsn;
}
}
}
#[test]
fn test_shared_char_trie_current_lsn() {
use crate::artrie_trait::ARTrie;
let dir = tempfile::TempDir::new().expect("temp dir");
let path = dir.path().join("test_shared_lsn.artc");
let trie =
std::sync::Arc::new(PersistentARTrieChar::<()>::create(&path).expect("create trie"));
let lsn0 = trie.current_lsn();
trie.write().insert("hello");
let lsn1 = trie.current_lsn();
assert!(lsn1 > lsn0, "current_lsn must advance after insert");
}
#[test]
fn test_shared_char_trie_synced_lsn() {
use crate::artrie_trait::ARTrie;
let dir = tempfile::TempDir::new().expect("temp dir");
let path = dir.path().join("test_shared_synced.artc");
let trie =
std::sync::Arc::new(PersistentARTrieChar::<()>::create(&path).expect("create trie"));
let synced_before = trie.synced_lsn();
trie.write().insert("hello");
let current_after_insert = trie.current_lsn();
assert!(
synced_before.map_or(true, |s| s < current_after_insert),
"synced_lsn must lag current_lsn until sync() runs"
);
trie.sync().expect("sync");
assert!(
trie.synced_lsn().is_some(),
"synced_lsn must be Some(_) after sync()"
);
}
#[test]
fn test_shared_char_trie_upsert() {
use crate::artrie_trait::ARTrie;
let dir = tempfile::TempDir::new().expect("temp dir");
let path = dir.path().join("test_shared_upsert.artc");
let trie =
std::sync::Arc::new(PersistentARTrieChar::<i64>::create(&path).expect("create trie"));
assert!(
trie.upsert("k", 1).expect("upsert"),
"first upsert reports insert"
);
assert!(
!trie.upsert("k", 2).expect("upsert"),
"second upsert reports update"
);
assert_eq!(trie.read().get_value("k"), Some(2), "value updated");
}
#[test]
fn nf3_concurrent_checkpoints_lose_no_terms_on_reopen() {
use crate::artrie_trait::ARTrie;
std::fs::create_dir_all("ln").ok();
let dir = tempfile::Builder::new()
.prefix("nf3-concurrent-ckpt")
.tempdir_in("ln")
.expect("real-disk scratch under ln");
let path = dir.path().join("t.artc");
let n = 200usize;
{
let trie =
std::sync::Arc::new(PersistentARTrieChar::<u64>::create(&path).expect("create"));
assert!(
trie.read().route_overlay(),
"u64 trie auto-flips to the overlay (the overlay-arm checkpoint is the NF-3 race site)"
);
for i in 0..n {
trie.insert_with_value(&format!("term{i:04}"), i as u64);
}
let handles: Vec<_> = (0..2)
.map(|_| {
let t = std::sync::Arc::clone(&trie);
std::thread::spawn(move || {
for _ in 0..15 {
t.checkpoint().expect("concurrent checkpoint");
}
})
})
.collect();
for h in handles {
h.join().expect("join");
}
}
let reopened = PersistentARTrieChar::<u64>::open(&path).expect("reopen");
for i in 0..n {
assert_eq!(
reopened.get_value(&format!("term{i:04}")),
Some(i as u64),
"term{i} lost/corrupt after two concurrent checkpoints + reopen (NF-3 — \
checkpoint_lock must serialize the descriptor writes)"
);
}
}
#[test]
fn test_shared_char_trie_sync_persists() {
use crate::artrie_trait::ARTrie;
let dir = tempfile::TempDir::new().expect("temp dir");
let path = dir.path().join("test_shared_sync.artc");
let trie =
std::sync::Arc::new(PersistentARTrieChar::<()>::create(&path).expect("create trie"));
trie.write().insert("persistent");
trie.sync().expect("sync");
drop(trie);
let reopened = PersistentARTrieChar::<()>::open(&path).expect("reopen");
assert!(reopened.contains("persistent"));
}
#[test]
fn test_insert_cas_basic() {
let dir = tempfile::TempDir::new().expect("create temp dir");
let path = dir.path().join("test_insert_cas.artc");
let mut trie: PersistentARTrieChar<()> =
PersistentARTrieChar::create(&path).expect("create trie");
trie.install_overlay();
assert!(trie.insert_cas("hello"));
assert!(trie.insert_cas("world"));
assert!(!trie.insert_cas("hello"));
assert!(!trie.insert_cas("world"));
assert!(trie.insert_cas("rust"));
assert!(trie.insert_cas("cargo"));
}
#[test]
fn test_insert_cas_empty_term_publishes_root() {
let dir = tempfile::TempDir::new().expect("create temp dir");
let path = dir.path().join("test_insert_cas_empty.artc");
let mut trie: PersistentARTrieChar<()> =
PersistentARTrieChar::create(&path).expect("create trie");
trie.install_overlay();
assert!(
trie.insert_cas(""),
"first insert_cas(\"\") publishes the root final"
);
assert!(trie.contains_lockfree(""), "\"\" is a member after insert");
assert!(
!trie.insert_cas(""),
"second insert_cas(\"\") is a no-op (already present)"
);
}
#[test]
fn test_insert_cas_unicode() {
let dir = tempfile::TempDir::new().expect("create temp dir");
let path = dir.path().join("test_insert_cas_unicode.artc");
let mut trie: PersistentARTrieChar<()> =
PersistentARTrieChar::create(&path).expect("create trie");
trie.install_overlay();
assert!(trie.insert_cas("日本語"));
assert!(trie.insert_cas("中文"));
assert!(trie.insert_cas("한국어"));
assert!(trie.insert_cas("🦀"));
assert!(!trie.insert_cas("日本語"));
assert!(!trie.insert_cas("🦀"));
}
#[test]
fn test_insert_cas_concurrent() {
use std::sync::Arc;
use std::thread;
let dir = tempfile::TempDir::new().expect("create temp dir");
let path = dir.path().join("test_insert_cas_concurrent.artc");
let mut trie: PersistentARTrieChar<()> =
PersistentARTrieChar::create(&path).expect("create trie");
trie.install_overlay();
let trie = Arc::new(trie);
let num_threads = 4;
let terms_per_thread = 25;
let handles: Vec<_> = (0..num_threads)
.map(|t| {
let trie = Arc::clone(&trie);
thread::spawn(move || {
let mut inserted = 0;
for i in 0..terms_per_thread {
let term = format!("term_{}_{}", t, i);
if trie.insert_cas(&term) {
inserted += 1;
}
}
inserted
})
})
.collect();
let total_inserted: usize = handles
.into_iter()
.map(|h| h.join().expect("thread join"))
.sum();
assert!(total_inserted >= 1, "At least one term should be inserted");
let retries = trie.cas_retry_count();
println!(
"Inserted: {}/{}, CAS retries: {}",
total_inserted,
num_threads * terms_per_thread,
retries
);
}
#[test]
fn test_contains_lockfree() {
let dir = tempfile::TempDir::new().expect("create temp dir");
let path = dir.path().join("test_contains_lockfree.artc");
let mut trie: PersistentARTrieChar<()> =
PersistentARTrieChar::create(&path).expect("create trie");
trie.install_overlay();
trie.insert_cas("apple");
trie.insert_cas("banana");
assert!(trie.contains_lockfree("apple"));
assert!(trie.contains_lockfree("banana"));
assert!(!trie.contains_lockfree("cherry"));
}
}