factstr-sqlite 0.3.1

Embedded SQLite store for FACTSTR with append, query, streams, and durable streams.
Documentation
mod support;

use factstr::{EventFilter, EventQuery, EventStore, EventStoreError, NewEvent};
use factstr_conformance as store_conformance;
use factstr_sqlite::SqliteStore;
use serde_json::json;

use support::TemporaryDatabaseFile;

#[test]
fn conditional_append_succeeds_for_matching_payload_filtered_context_version() {
    support::run_store_test(
        store_conformance::conditional_append_succeeds_for_matching_payload_filtered_context_version,
    );
}

#[test]
fn conditional_append_fails_for_stale_payload_filtered_context_version() {
    support::run_store_test(
        store_conformance::conditional_append_fails_for_stale_payload_filtered_context_version,
    );
}

#[test]
fn failed_conditional_append_does_not_append_any_part_of_the_batch() {
    support::run_store_test(
        store_conformance::failed_conditional_append_does_not_append_any_part_of_the_batch,
    );
}

#[test]
fn failed_conditional_append_does_not_consume_sequence_numbers_for_later_commits() {
    support::run_store_test(
        store_conformance::failed_conditional_append_does_not_consume_sequence_numbers_for_later_commits,
    );
}

#[test]
fn successful_conditional_append_persists_across_reopen() {
    let database_file = TemporaryDatabaseFile::new("conditional-append-reopen-success");
    let first_store = SqliteStore::open(database_file.path()).expect("sqlite store should open");

    first_store
        .append(vec![NewEvent::new(
            "account-opened",
            json!({ "accountId": "a1" }),
        )])
        .expect("append should succeed");

    let append_result = first_store
        .append_if(
            vec![NewEvent::new(
                "account-renamed",
                json!({ "accountId": "a1", "name": "Rico" }),
            )],
            &EventQuery::all().with_filters([
                EventFilter::default().with_payload_predicates([json!({ "accountId": "a1" })])
            ]),
            Some(1),
        )
        .expect("conditional append should succeed");
    drop(first_store);

    assert_eq!(append_result.first_sequence_number, 2);
    assert_eq!(append_result.last_sequence_number, 2);

    let reopened_store =
        SqliteStore::open(database_file.path()).expect("sqlite store should reopen");
    let query_result = reopened_store
        .query(&EventQuery::all())
        .expect("query should succeed after reopen");

    assert_eq!(query_result.event_records.len(), 2);
    assert_eq!(query_result.event_records[0].sequence_number, 1);
    assert_eq!(query_result.event_records[1].sequence_number, 2);
}

#[test]
fn failed_conditional_append_leaves_database_unchanged_across_reopen() {
    let database_file = TemporaryDatabaseFile::new("conditional-append-reopen-conflict");
    let first_store = SqliteStore::open(database_file.path()).expect("sqlite store should open");

    first_store
        .append(vec![NewEvent::new(
            "account-opened",
            json!({ "accountId": "a1" }),
        )])
        .expect("append should succeed");

    let conflict = first_store
        .append_if(
            vec![
                NewEvent::new("account-renamed", json!({ "accountId": "a1" })),
                NewEvent::new("account-credited", json!({ "accountId": "a1" })),
            ],
            &EventQuery::all()
                .with_filters([
                    EventFilter::default().with_payload_predicates([json!({ "accountId": "a1" })])
                ])
                .with_min_sequence_number(1),
            None,
        )
        .expect_err("conditional append should fail");
    drop(first_store);

    assert_eq!(
        conflict,
        EventStoreError::ConditionalAppendConflict {
            expected: None,
            actual: Some(1),
        }
    );

    let reopened_store =
        SqliteStore::open(database_file.path()).expect("sqlite store should reopen");
    let query_result = reopened_store
        .query(&EventQuery::all())
        .expect("query should succeed after reopen");

    assert_eq!(query_result.event_records.len(), 1);
    assert_eq!(query_result.event_records[0].sequence_number, 1);

    let later_append = reopened_store
        .append(vec![NewEvent::new(
            "account-credited",
            json!({ "accountId": "a1" }),
        )])
        .expect("later append should succeed");

    assert_eq!(later_append.first_sequence_number, 2);
    assert_eq!(later_append.last_sequence_number, 2);
}