persy 1.8.0

Transactional Persistence Engine
Documentation
use tempfile::Builder;

use crate::{
    Config, OpenOptions, Persy, ValueMode,
    inspect::{JournalRead, PersyInspect, RecordState, TreeInspector, inspec_log},
    journal::{self, JournalId, records::Start},
};

#[derive(Default)]
struct Count {
    keys_count: u32,
    values_count: u32,
    enabled: bool,
}
impl<K, V> TreeInspector<K, V> for Count {
    fn start_node(
        &mut self,
        _node_id: crate::PersyId,
        _prev: Option<K>,
        _next: Option<K>,
    ) -> super::TreeInspectorResult<()> {
        Ok(())
    }

    fn end_node(&mut self, _node_id: crate::PersyId) -> super::TreeInspectorResult<()> {
        Ok(())
    }

    fn start_leaf(
        &mut self,
        _node_id: crate::PersyId,
        _prev: Option<K>,
        _next: Option<K>,
    ) -> super::TreeInspectorResult<()> {
        self.enabled = true;
        Ok(())
    }

    fn end_leaf(&mut self, _node_id: crate::PersyId) -> super::TreeInspectorResult<()> {
        self.enabled = false;
        Ok(())
    }

    fn start_key(&mut self, _node_pos: u32, _k: K) -> super::TreeInspectorResult<()> {
        if self.enabled {
            self.keys_count += 1;
        }
        Ok(())
    }

    fn end_key(&mut self, _node_pos: u32, _k: K) -> super::TreeInspectorResult<()> {
        Ok(())
    }

    fn value(&mut self, _node_pos: u32, _v: V) -> super::TreeInspectorResult<()> {
        self.values_count += 1;
        Ok(())
    }

    fn failed_load(&mut self, _node_id: crate::PersyId) -> super::TreeInspectorResult<()> {
        Ok(())
    }

    fn empty(&mut self) -> super::TreeInspectorResult<()> {
        Ok(())
    }
}

#[test]
pub fn test_tree_inspection() {
    let p = OpenOptions::new().memory().unwrap();
    let mut tx = p.begin().unwrap();
    tx.create_index::<u32, u32>("index", ValueMode::Cluster).unwrap();
    tx.prepare().unwrap().commit().unwrap();

    let mut tx = p.begin().unwrap();
    for x in 0..20000 {
        for z in 0..20 {
            tx.put::<u32, u32>("index", x, x * 100 + z).unwrap();
        }
    }
    tx.prepare().unwrap().commit().unwrap();
    let mut ti = Count::default();

    p.inspect_tree::<u32, u32, _>("index", &mut ti).unwrap();

    assert_eq!(ti.keys_count, 20000);
    assert_eq!(ti.values_count, 20000 * 20);
}

#[derive(Default)]
struct Value {
    key: Option<u32>,
    value: Option<u32>,
}
impl TreeInspector<u32, u32> for Value {
    fn start_node(
        &mut self,
        _node_id: crate::PersyId,
        _prev: Option<u32>,
        _next: Option<u32>,
    ) -> super::TreeInspectorResult<()> {
        Ok(())
    }

    fn end_node(&mut self, _node_id: crate::PersyId) -> super::TreeInspectorResult<()> {
        Ok(())
    }

    fn start_leaf(
        &mut self,
        _node_id: crate::PersyId,
        _prev: Option<u32>,
        _next: Option<u32>,
    ) -> super::TreeInspectorResult<()> {
        Ok(())
    }

    fn end_leaf(&mut self, _node_id: crate::PersyId) -> super::TreeInspectorResult<()> {
        Ok(())
    }

    fn start_key(&mut self, _node_pos: u32, k: u32) -> super::TreeInspectorResult<()> {
        self.key = Some(k);
        Ok(())
    }

    fn end_key(&mut self, _node_pos: u32, _k: u32) -> super::TreeInspectorResult<()> {
        Ok(())
    }

    fn value(&mut self, _node_pos: u32, v: u32) -> super::TreeInspectorResult<()> {
        self.value = Some(v);
        Ok(())
    }

    fn failed_load(&mut self, _node_id: crate::PersyId) -> super::TreeInspectorResult<()> {
        Ok(())
    }

    fn empty(&mut self) -> super::TreeInspectorResult<()> {
        Ok(())
    }
}

#[test]
pub fn test_tree_value_inspection() {
    let p = OpenOptions::new().memory().unwrap();
    let mut tx = p.begin().unwrap();
    tx.create_index::<u32, u32>("index", ValueMode::Cluster).unwrap();
    tx.prepare().unwrap().commit().unwrap();

    let mut tx = p.begin().unwrap();
    tx.put::<u32, u32>("index", 100, 200).unwrap();
    tx.prepare().unwrap().commit().unwrap();
    let mut ti = Value::default();

    p.inspect_tree::<u32, u32, _>("index", &mut ti).unwrap();

    assert_eq!(ti.key, Some(100));
    assert_eq!(ti.value, Some(200));
}

#[test]
pub fn test_page_scan() {
    let p = OpenOptions::new().memory().unwrap();
    let mut tx = p.begin().unwrap();
    tx.create_segment("test").unwrap();
    tx.prepare().unwrap().commit().unwrap();

    let mut tx = p.begin().unwrap();
    tx.insert("test", &vec![10, 10, 10]).unwrap();
    tx.prepare().unwrap().commit().unwrap();

    assert_eq!(p.page_state_scan().unwrap().count(), 9);
}

#[test]
pub fn test_inspect_record() {
    let p = OpenOptions::new().memory().unwrap();
    let mut tx = p.begin().unwrap();
    tx.create_segment("test").unwrap();
    tx.prepare().unwrap().commit().unwrap();

    let mut tx = p.begin().unwrap();
    let id = tx.insert("test", &vec![10, 10, 10]).unwrap();
    tx.prepare().unwrap().commit().unwrap();

    assert_eq!(
        p.inspect_record_direct(&id).unwrap(),
        RecordState::Exists(vec![10, 10, 10])
    );
}

#[derive(Default)]
struct TestLogInspector {
    start: u32,
    insert_record: u32,
    prepare_commit: u32,
    commit: u32,
    cleanup: u32,
    create_segment: u32,
    journal_page: u32,
}

impl JournalRead for TestLogInspector {
    fn journal_page(&mut self, _allocation: u64) {
        self.journal_page += 1;
    }

    fn start(&mut self, _journal_id: JournalId, _start: &Start) {
        self.start += 1;
    }

    fn insert_record(&mut self, _journal_id: JournalId, _insert: &super::InsertRecord) {
        self.insert_record += 1;
    }

    fn prepare_commit(&mut self, _journal_id: JournalId, _prepare_commit: &journal::records::PrepareCommit) {
        self.prepare_commit += 1;
    }

    fn commit(&mut self, _journal_id: JournalId, _commit: &super::Commit) {
        self.commit += 1;
    }

    fn update_record(&mut self, _journal_id: JournalId, _update_record: &super::UpdateRecord) {
        assert!(false)
    }

    fn delete_record(&mut self, _journal_id: JournalId, _delete_record: &super::DeleteRecord) {
        assert!(false)
    }

    fn rollback(&mut self, _journal_id: JournalId, _rollback: &super::Rollback) {
        assert!(false)
    }

    fn create_segment(&mut self, _journal_id: JournalId, _create_segment: &super::CreateSegment) {
        self.create_segment += 1;
    }

    fn drop_segment(&mut self, _journal_id: JournalId, _drop_segment: &super::DropSegment) {
        assert!(false)
    }

    fn read_record(&mut self, _journal_id: JournalId, _read_record: &super::ReadRecord) {
        assert!(false)
    }

    fn metadata(&mut self, _journal_id: JournalId, _metadata: &super::Metadata) {}

    fn free_page(&mut self, _journal_id: JournalId, _free_page: &super::FreedPage) {}

    fn new_segment_page(&mut self, _journal_id: JournalId, _new_segment_page: &super::NewSegmentPage) {}

    fn cleanup(&mut self, _journal_id: JournalId, _cleanup: &super::Cleanup) {
        self.cleanup += 1;
    }

    fn rollback_page(&mut self, _journal_id: JournalId, _rollback_page: &super::RollbackPage) {}
}

#[test]
pub fn test_simple_inspect_log() {
    let tmp_file = Builder::new()
        .prefix("inspect_log")
        .suffix(".persy")
        .tempfile()
        .unwrap();
    {
        Persy::create_from_file(tmp_file.reopen().unwrap()).unwrap();
        let p = Persy::open_from_file(tmp_file.reopen().unwrap(), Config::new()).unwrap();
        let mut tx = p.begin().unwrap();
        tx.create_segment("test").unwrap();
        tx.prepare().unwrap().commit().unwrap();

        let mut tx = p.begin().unwrap();
        tx.insert("test", &vec![10, 10, 10]).unwrap();
        tx.prepare().unwrap().commit().unwrap();
    }
    let mut log_inspector = TestLogInspector::default();
    inspec_log(tmp_file.path(), &mut log_inspector).unwrap();
    assert_eq!(log_inspector.start, 2);
    assert_eq!(log_inspector.create_segment, 1);
    assert_eq!(log_inspector.prepare_commit, 2);
    assert_eq!(log_inspector.commit, 2);
    assert_eq!(log_inspector.cleanup, 2);
    assert_eq!(log_inspector.insert_record, 1);
}