khive-db 0.10.0

SQLite storage backend: entities, edges, notes, events, FTS5, sqlite-vec vectors.
Documentation
use super::*;

fn single_target_section(parent_namespace: &str) {
    crate::extension::ensure_extensions_loaded();
    let mut conn = migrated();
    conn.pragma_update(None, "foreign_keys", "ON").unwrap();
    conn.execute(
        "INSERT INTO knowledge_atoms (id, namespace, slug, name, created_at, updated_at) \
         VALUES (?1, ?2, 'outside-parent', 'outside parent', 1, 1)",
        rusqlite::params![ATOM, parent_namespace],
    )
    .unwrap();
    section(&conn, SECTION, ATOM, "source");
    create_vectors(&conn, "vec_partition_model");
    vector(
        &conn,
        "vec_partition_model",
        SECTION,
        "source",
        "entity",
        "knowledge.section",
    );
    let before = places_and_bytes(&conn);
    let request = MoveRequest::new("source", vec![route("atom", "target")]);
    let transaction = conn.transaction().unwrap();
    let counts = move_namespace(&transaction, &request).unwrap();
    assert_eq!(counts.subjects.get("atom"), Some(&0));
    assert_eq!(counts.rows.get("knowledge_atoms"), Some(&0));
    assert_eq!(counts.rows.get("knowledge_sections"), Some(&1));
    assert_eq!(counts.rows.get("vec_partition_model"), Some(&1));
    assert_eq!(counts.ann_log_appended, 2);
    let section_namespace: String = transaction
        .query_row(
            "SELECT namespace FROM knowledge_sections WHERE id = ?1",
            [SECTION],
            |row| row.get(0),
        )
        .unwrap();
    assert_eq!(section_namespace, "target");
    let atom_namespace: String = transaction
        .query_row(
            "SELECT namespace FROM knowledge_atoms WHERE id = ?1",
            [ATOM],
            |row| row.get(0),
        )
        .unwrap();
    assert_eq!(atom_namespace, parent_namespace);
    assert_eq!(
        places_and_bytes(&transaction),
        [(SECTION.into(), "target".into(), before[0].2.clone())]
    );
    let log: Vec<(String, String)> = transaction
        .prepare("SELECT namespace, op FROM ann_write_log WHERE subject_id = ?1 ORDER BY seq")
        .unwrap()
        .query_map([SECTION], |row| Ok((row.get(0)?, row.get(1)?)))
        .unwrap()
        .collect::<rusqlite::Result<_>>()
        .unwrap();
    assert_eq!(
        log,
        [
            ("source".into(), "delete".into()),
            ("target".into(), "upsert".into())
        ]
    );
    let again = move_namespace(&transaction, &request).unwrap();
    assert_eq!(again.rows.get("knowledge_sections"), Some(&0));
    assert_eq!(again.rows.get("vec_partition_model"), Some(&0));
    assert_eq!(again.ann_log_appended, 0);
    transaction.commit().unwrap();
}

#[test]
fn single_target_carries_a_source_section_whose_parent_is_in_the_target() {
    single_target_section("target");
}

#[test]
fn single_target_carries_a_source_section_whose_parent_is_in_a_third_namespace() {
    single_target_section("third");
}

#[test]
fn domain_first_keeps_ordinary_atoms_sections_and_vectors_on_the_atom_route() {
    crate::extension::ensure_extensions_loaded();
    let mut conn = migrated();
    conn.pragma_update(None, "foreign_keys", "ON").unwrap();
    conn.execute(
        "INSERT INTO knowledge_atoms (id, namespace, slug, name, created_at, updated_at) \
         VALUES (?1, 'source', 'ordinary', 'ordinary atom', 1, 1)",
        [ATOM],
    )
    .unwrap();
    section(&conn, SECTION, ATOM, "source");
    domain_pair(&conn, DOMAIN, "source");
    const DOMAIN_SECTION: &str = "66666666-6666-4666-8666-000000000002";
    section(&conn, DOMAIN_SECTION, DOMAIN, "source");
    create_vectors(&conn, "vec_partition_model");
    for (id, field) in [
        (ATOM, "knowledge.atom"),
        (SECTION, "knowledge.section"),
        (DOMAIN, "knowledge.atom"),
        (DOMAIN_SECTION, "knowledge.section"),
    ] {
        vector(&conn, "vec_partition_model", id, "source", "entity", field);
    }
    let before = places_and_bytes(&conn);
    let request = MoveRequest::new(
        "source",
        vec![
            route("domain", "domain-target"),
            route("atom", "atom-target"),
        ],
    );
    let transaction = conn.transaction().unwrap();
    let counts = move_namespace(&transaction, &request).unwrap();
    assert_eq!(counts.subjects.get("atom"), Some(&1));
    assert_eq!(counts.subjects.get("domain"), Some(&1));
    assert_eq!(counts.rows.get("knowledge_atoms"), Some(&2));
    assert_eq!(counts.rows.get("knowledge_domains"), Some(&1));
    assert_eq!(counts.rows.get("knowledge_sections"), Some(&2));
    assert_eq!(counts.rows.get("vec_partition_model"), Some(&4));
    assert_eq!(counts.ann_log_appended, 8);
    for (table, id, target) in [
        ("knowledge_atoms", ATOM, "atom-target"),
        ("knowledge_sections", SECTION, "atom-target"),
        ("knowledge_domains", DOMAIN, "domain-target"),
        ("knowledge_atoms", DOMAIN, "domain-target"),
        ("knowledge_sections", DOMAIN_SECTION, "domain-target"),
    ] {
        let namespace: String = transaction
            .query_row(
                &format!("SELECT namespace FROM {table} WHERE id = ?1"),
                [id],
                |row| row.get(0),
            )
            .unwrap();
        assert_eq!(namespace, target, "physical route for {table}/{id}");
    }
    let after = places_and_bytes(&transaction);
    assert_eq!(after.len(), before.len());
    for ((id, _, bytes), (moved_id, namespace, moved_bytes)) in before.iter().zip(&after) {
        let target = if [ATOM, SECTION].contains(&id.as_str()) {
            "atom-target"
        } else {
            "domain-target"
        };
        assert_eq!(id, moved_id);
        assert_eq!(namespace, target);
        assert_eq!(bytes, moved_bytes);
        let log: Vec<(String, String)> = transaction
            .prepare("SELECT namespace, op FROM ann_write_log WHERE subject_id = ?1 ORDER BY seq")
            .unwrap()
            .query_map([id], |row| Ok((row.get(0)?, row.get(1)?)))
            .unwrap()
            .collect::<rusqlite::Result<_>>()
            .unwrap();
        assert_eq!(
            log,
            [
                ("source".into(), "delete".into()),
                (target.into(), "upsert".into())
            ]
        );
    }
    transaction.commit().unwrap();
}