Add CRC32C checksums to WAL record format (v2), implement crash recovery with automatic truncation of corrupt records, add feature-gated group commit buffer for batched fsync under concurrent load, and implement log rotation via segment files with global offset addressing. Key changes: - Record format v2: [len:u32][crc32c:u32][blake3:32][payload:N] - recover_file() scans and truncates corrupt tail records - GroupCommitBuffer batches fsync via MPSC channel (tokio feature gate) - SegmentManager with binary search resolution and cursor-based cleanup - Journal::read() auto-refreshes segments on miss for writer/reader split - Split recovery.rs and key_codec.rs into directory modules for 500-line max Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
81 lines
2.4 KiB
Rust
81 lines
2.4 KiB
Rust
//! Test suite for QueryEngine.
|
|
|
|
use std::sync::Arc;
|
|
|
|
use stemedb_core::testing::AssertionBuilder;
|
|
use stemedb_core::types::{Assertion, LifecycleStage, MaterializedView};
|
|
use stemedb_storage::{key_codec, GenericIndexStore, HybridStore, IndexStore, KVStore};
|
|
|
|
use super::QueryEngine;
|
|
|
|
mod basic;
|
|
mod changelog;
|
|
mod conflict_score;
|
|
mod indexes;
|
|
mod materialized_views;
|
|
mod similarity;
|
|
mod time_travel;
|
|
|
|
/// Helper to create a test assertion with standard values.
|
|
pub(super) fn create_test_assertion(
|
|
subject: &str,
|
|
predicate: &str,
|
|
lifecycle: LifecycleStage,
|
|
) -> Assertion {
|
|
AssertionBuilder::new()
|
|
.subject(subject)
|
|
.predicate(predicate)
|
|
.confidence(0.95)
|
|
.lifecycle(lifecycle)
|
|
.build()
|
|
}
|
|
|
|
/// Helper to store an assertion in the KV store and update indexes.
|
|
pub(super) async fn store_assertion(store: &Arc<HybridStore>, assertion: &Assertion) {
|
|
let bytes = stemedb_core::serde::serialize(assertion).expect("serialize");
|
|
|
|
let hash = blake3::hash(&bytes);
|
|
let key = key_codec::assertion_key(&assertion.subject, &hash.to_hex());
|
|
store.put(&key, &bytes).await.expect("put");
|
|
|
|
// Update indexes using IndexStore
|
|
let index_store = GenericIndexStore::new(store.clone());
|
|
let assertion_hash: [u8; 32] = *hash.as_bytes();
|
|
index_store
|
|
.add_to_indexes(&assertion.subject, &assertion.predicate, &assertion_hash)
|
|
.await
|
|
.expect("add to indexes");
|
|
}
|
|
|
|
/// Helper to store a materialized view directly in the KV store.
|
|
pub(super) async fn store_materialized_view(
|
|
store: &Arc<HybridStore>,
|
|
subject: &str,
|
|
predicate: &str,
|
|
winner: &Assertion,
|
|
) {
|
|
store_materialized_view_with_time(store, subject, predicate, winner, 1000).await;
|
|
}
|
|
|
|
/// Helper to store a materialized view with a custom materialized_at timestamp.
|
|
pub(super) async fn store_materialized_view_with_time(
|
|
store: &Arc<HybridStore>,
|
|
subject: &str,
|
|
predicate: &str,
|
|
winner: &Assertion,
|
|
materialized_at: u64,
|
|
) {
|
|
let view = MaterializedView {
|
|
winner: winner.clone(),
|
|
lens_name: "VoteAwareConsensus".to_string(),
|
|
resolution_confidence: 0.95,
|
|
candidates_count: 3,
|
|
materialized_at,
|
|
conflict_score: 0.1,
|
|
};
|
|
|
|
let key = key_codec::mv_key(subject, predicate);
|
|
let bytes = stemedb_core::serde::serialize(&view).expect("serialize MV");
|
|
store.put(&key, &bytes).await.expect("put MV");
|
|
}
|