use super::*;
#[test]
fn migration_replay_does_not_trip_on_already_present_column() {
use tempfile::NamedTempFile;
let tmp = NamedTempFile::new().unwrap();
let path = tmp.path().to_str().unwrap();
{
let _db = YantrikDB::new(path, 8).unwrap();
}
{
let conn = rusqlite::Connection::open(path).unwrap();
conn.execute(
"INSERT OR REPLACE INTO meta (key, value) VALUES ('schema_version', '23')",
[],
)
.unwrap();
}
let db = YantrikDB::new(path, 8)
.expect("v0.7.3 idempotent migration runner must heal rewound-meta deployments");
db.record(
"post-heal smoke",
"episodic",
0.5,
0.0,
604800.0,
&empty_meta(),
&vec_seed(1.0, 8),
"default",
0.8,
"general",
"user",
None,
)
.unwrap();
}
#[test]
fn migration_replay_does_not_trip_on_alter_table_against_view() {
use tempfile::NamedTempFile;
let tmp = NamedTempFile::new().unwrap();
let path = tmp.path().to_str().unwrap();
{
let _db = YantrikDB::new(path, 8).unwrap();
}
{
let conn = rusqlite::Connection::open(path).unwrap();
let kind: String = conn
.query_row(
"SELECT type FROM sqlite_master WHERE name = 'edges'",
[],
|row| row.get(0),
)
.unwrap();
assert_eq!(
kind, "view",
"fixture precondition: at current schema, edges should be a backward-compat view"
);
}
{
let conn = rusqlite::Connection::open(path).unwrap();
conn.execute(
"INSERT OR REPLACE INTO meta (key, value) VALUES ('schema_version', '14')",
[],
)
.unwrap();
}
let db = YantrikDB::new(path, 8)
.expect("v0.7.8 idempotent runner must heal rewound-meta DBs that hit ALTER-on-view");
db.record(
"post-heal smoke (issue 10)",
"episodic",
0.5,
0.0,
604800.0,
&empty_meta(),
&vec_seed(1.0, 8),
"default",
0.8,
"general",
"user",
None,
)
.unwrap();
}
#[test]
fn migration_meta_stamp_does_not_downgrade() {
use tempfile::NamedTempFile;
let tmp = NamedTempFile::new().unwrap();
let path = tmp.path().to_str().unwrap();
{
let conn = rusqlite::Connection::open(path).unwrap();
conn.execute_batch(
"CREATE TABLE IF NOT EXISTS meta (key TEXT PRIMARY KEY, value TEXT NOT NULL);
INSERT OR REPLACE INTO meta (key, value) VALUES ('schema_version', '999');",
)
.unwrap();
}
let _db = YantrikDB::new(path, 8).unwrap();
let stamped: String = {
let conn = rusqlite::Connection::open(path).unwrap();
conn.query_row(
"SELECT value FROM meta WHERE key = 'schema_version'",
[],
|row| row.get(0),
)
.unwrap()
};
assert_eq!(
stamped, "999",
"MAX-stamp invariant: open() must never rewind meta.schema_version below the on-disk value"
);
}
#[test]
fn schema_v26_fresh_install_has_provenance_columns_and_indexes() {
let db = YantrikDB::new(":memory:", 8).unwrap();
let conn = db.conn();
let cols = table_columns(&conn, "memories");
for required in [
"prior_rid",
"resolution_kind",
"dismissal_reason",
"confidence_at_write",
] {
assert!(
cols.iter().any(|c| c == required),
"v26: fresh-install memories table missing column {required}, got: {cols:?}"
);
}
assert!(
index_exists(&conn, "idx_memories_prior_rid"),
"v26: fresh-install missing partial index idx_memories_prior_rid"
);
assert!(
index_exists(&conn, "idx_memories_resolution_kind"),
"v26: fresh-install missing partial index idx_memories_resolution_kind"
);
}
#[test]
fn schema_v26_migration_from_v25_adds_columns_and_normalizes_source() {
use tempfile::NamedTempFile;
let tmp = NamedTempFile::new().unwrap();
let path = tmp.path().to_str().unwrap();
{
let db = YantrikDB::new(path, 8).unwrap();
let conn = db.conn();
for (rid, src) in [
("01900000-0000-7000-8000-000000000001", "user"),
("01900000-0000-7000-8000-000000000002", "inference"),
("01900000-0000-7000-8000-000000000003", "legacy-freetext"),
] {
conn.execute(
"INSERT INTO memories (rid, type, text, created_at, updated_at, last_access, source) \
VALUES (?1, 'episodic', 'test', 0.0, 0.0, 0.0, ?2)",
params![rid, src],
)
.unwrap();
}
}
{
let conn = rusqlite::Connection::open(path).unwrap();
conn.execute(
"INSERT OR REPLACE INTO meta (key, value) VALUES ('schema_version', '25')",
[],
)
.unwrap();
conn.execute("DROP INDEX IF EXISTS idx_memories_prior_rid", [])
.unwrap();
conn.execute("DROP INDEX IF EXISTS idx_memories_resolution_kind", [])
.unwrap();
}
let db = YantrikDB::new(path, 8)
.expect("v26 migration must run cleanly against a rewound-meta v25 DB");
let conn = db.conn();
let cols = table_columns(&conn, "memories");
for required in [
"prior_rid",
"resolution_kind",
"dismissal_reason",
"confidence_at_write",
] {
assert!(
cols.iter().any(|c| c == required),
"v26 migration: missing column {required} after re-open"
);
}
assert!(
index_exists(&conn, "idx_memories_prior_rid"),
"v26 migration: missing partial index idx_memories_prior_rid"
);
assert!(
index_exists(&conn, "idx_memories_resolution_kind"),
"v26 migration: missing partial index idx_memories_resolution_kind"
);
let normalized: String = conn
.query_row(
"SELECT source FROM memories WHERE rid = '01900000-0000-7000-8000-000000000003'",
[],
|row| row.get(0),
)
.unwrap();
assert_eq!(
normalized, "user",
"v26 migration must coerce non-enum source to 'user'"
);
let preserved: String = conn
.query_row(
"SELECT source FROM memories WHERE rid = '01900000-0000-7000-8000-000000000002'",
[],
|row| row.get(0),
)
.unwrap();
assert_eq!(
preserved, "inference",
"v26 migration must preserve enum-valid source values"
);
let log: String = conn
.query_row(
"SELECT value FROM meta WHERE key = 'source_normalization_log_v26'",
[],
|row| row.get(0),
)
.unwrap();
assert!(
log.contains("normalized 1 rows"),
"v26 migration must log normalization count, got: {log}"
);
}
#[test]
fn schema_v26_migration_replay_is_idempotent() {
use tempfile::NamedTempFile;
let tmp = NamedTempFile::new().unwrap();
let path = tmp.path().to_str().unwrap();
{
let _db = YantrikDB::new(path, 8).unwrap();
}
{
let conn = rusqlite::Connection::open(path).unwrap();
conn.execute(
"INSERT OR REPLACE INTO meta (key, value) VALUES ('schema_version', '25')",
[],
)
.unwrap();
}
let db = YantrikDB::new(path, 8)
.expect("v26 migration runner must heal rewound-meta deployments on a v26-schema DB");
db.record(
"post-v26-heal smoke",
"episodic",
0.5,
0.0,
604800.0,
&empty_meta(),
&vec_seed(1.0, 8),
"default",
0.8,
"general",
"user",
None,
)
.unwrap();
}
#[test]
fn schema_v43_fresh_install_has_typed_synthesis_lifecycle() {
let db = YantrikDB::new(":memory:", 8).unwrap();
let conn = db.conn();
let cols = table_columns(&conn, "memories");
for required in [
"synthesis_axis",
"synthesis_granularity",
"synthesis_logical_key",
"synthesis_evidence_version",
"synthesis_generation_hlc",
"synthesis_state",
] {
assert!(
cols.iter().any(|col| col == required),
"v43 fresh schema missing memories.{required}"
);
}
assert_eq!(
conn.query_row(
"SELECT COUNT(*) FROM sqlite_master \
WHERE type = 'table' AND name = 'synthesis_dependencies'",
[],
|row| row.get::<_, i64>(0),
)
.unwrap(),
1
);
for index in [
"idx_synthesis_dependencies_source",
"idx_synthesis_dependencies_synthesis",
] {
assert!(
index_exists(&conn, index),
"v42 fresh schema missing {index}"
);
}
}
#[test]
fn schema_v46_fresh_install_has_rollup_outcome_ledger() {
let db = YantrikDB::new(":memory:", 8).unwrap();
let conn = db.conn();
for table in [
"rollup_impressions",
"rollup_impression_children",
"rollup_impression_outcomes",
"rollup_impression_additions",
] {
assert_eq!(
conn.query_row(
"SELECT COUNT(*) FROM sqlite_master WHERE type = 'table' AND name = ?1",
[table],
|row| row.get::<_, i64>(0),
)
.unwrap(),
1,
"v46 fresh schema missing {table}"
);
}
for index in [
"idx_rollup_impressions_rollup",
"idx_rollup_impressions_query",
"idx_rollup_impression_children_child",
"idx_rollup_impression_outcomes_created",
"idx_rollup_impression_additions_child",
] {
assert!(
index_exists(&conn, index),
"v46 fresh schema missing {index}"
);
}
for expected in ["requested_count", "query_shape"] {
assert!(
table_columns(&conn, "rollup_impressions")
.iter()
.any(|column| column == expected),
"v46 fresh schema missing rollup_impressions.{expected}"
);
}
assert!(
table_columns(&conn, "rollup_impression_children")
.iter()
.any(|column| column == "score"),
"v46 fresh schema missing rollup_impression_children.score"
);
}
#[test]
fn schema_v46_migration_adds_omission_features() {
let conn = rusqlite::Connection::open_in_memory().unwrap();
conn.execute_batch(
"CREATE TABLE rollup_impressions (impression_id TEXT PRIMARY KEY); \
CREATE TABLE rollup_impression_children ( \
impression_id TEXT NOT NULL, child_rid TEXT NOT NULL, rank INTEGER NOT NULL, \
PRIMARY KEY (impression_id, child_rid) \
);",
)
.unwrap();
conn.execute_batch(crate::base::schema::MIGRATE_V45_TO_V46)
.unwrap();
let impression_cols = table_columns(&conn, "rollup_impressions");
assert!(impression_cols.iter().any(|col| col == "requested_count"));
assert!(impression_cols.iter().any(|col| col == "query_shape"));
assert!(table_columns(&conn, "rollup_impression_children")
.iter()
.any(|col| col == "score"));
assert_eq!(
conn.query_row(
"SELECT COUNT(*) FROM sqlite_master WHERE type = 'table' AND name = ?1",
["rollup_impression_additions"],
|row| row.get::<_, i64>(0),
)
.unwrap(),
1
);
assert!(index_exists(&conn, "idx_rollup_impression_additions_child"));
}
#[test]
fn schema_v46_migration_bootstraps_child_ledger_skipped_by_v43() {
let conn = rusqlite::Connection::open_in_memory().unwrap();
conn.execute_batch(crate::base::schema::MIGRATE_V44_TO_V45)
.unwrap();
conn.execute_batch(crate::base::schema::MIGRATE_V45_TO_V46)
.unwrap();
assert!(table_columns(&conn, "rollup_impression_children")
.iter()
.any(|column| column == "score"));
assert_eq!(
conn.query_row(
"SELECT COUNT(*) FROM sqlite_master WHERE type = 'table' AND name = ?1",
["rollup_impression_additions"],
|row| row.get::<_, i64>(0),
)
.unwrap(),
1
);
}
#[test]
fn schema_v45_migration_adds_rollup_outcome_finalization() {
let conn = rusqlite::Connection::open_in_memory().unwrap();
conn.execute(
"CREATE TABLE rollup_impressions (impression_id TEXT PRIMARY KEY)",
[],
)
.unwrap();
conn.execute_batch(crate::base::schema::MIGRATE_V44_TO_V45)
.unwrap();
let cols = table_columns(&conn, "rollup_impressions");
assert!(cols.iter().any(|col| col == "outcome_payload_hash"));
assert!(cols.iter().any(|col| col == "outcome_finalized_at"));
}
#[test]
fn schema_v45_migration_bootstraps_ledger_skipped_by_v43() {
let conn = rusqlite::Connection::open_in_memory().unwrap();
conn.execute_batch(crate::base::schema::MIGRATE_V44_TO_V45)
.unwrap();
let cols = table_columns(&conn, "rollup_impressions");
for expected in [
"rollup_rid",
"expansion_payload_hash",
"outcome_payload_hash",
"outcome_finalized_at",
] {
assert!(cols.iter().any(|col| col == expected), "missing {expected}");
}
}
#[test]
fn schema_v43_migration_adds_synthesis_generation_clock() {
let conn = rusqlite::Connection::open_in_memory().unwrap();
conn.execute("CREATE TABLE memories (rid TEXT PRIMARY KEY)", [])
.unwrap();
conn.execute_batch(crate::base::schema::MIGRATE_V42_TO_V43)
.unwrap();
let cols = table_columns(&conn, "memories");
assert!(cols.iter().any(|col| col == "synthesis_generation_hlc"));
}
#[test]
fn schema_v42_migration_adds_the_same_synthesis_surface() {
let conn = rusqlite::Connection::open_in_memory().unwrap();
conn.execute("CREATE TABLE memories (rid TEXT PRIMARY KEY)", [])
.unwrap();
conn.execute_batch(crate::base::schema::MIGRATE_V41_TO_V42)
.unwrap();
let cols = table_columns(&conn, "memories");
for required in [
"synthesis_axis",
"synthesis_granularity",
"synthesis_logical_key",
"synthesis_evidence_version",
"synthesis_state",
] {
assert!(cols.iter().any(|col| col == required));
}
assert!(index_exists(&conn, "idx_synthesis_dependencies_source"));
assert!(index_exists(&conn, "idx_synthesis_dependencies_synthesis"));
conn.execute(
"INSERT INTO memories (rid, synthesis_granularity, synthesis_state) \
VALUES ('ordinary', NULL, NULL), ('synth', 'atomic', 'verified')",
[],
)
.unwrap();
assert!(conn
.execute(
"INSERT INTO memories (rid, synthesis_granularity) VALUES ('bad-g', 'session')",
[],
)
.is_err());
assert!(conn
.execute(
"INSERT INTO memories (rid, synthesis_state) VALUES ('bad-s', 'active')",
[],
)
.is_err());
}
#[test]
fn failing_migration_statement_names_itself_in_the_error() {
use rusqlite::Connection;
let conn = Connection::open_in_memory().unwrap();
conn.execute("CREATE TABLE memories (rid TEXT PRIMARY KEY)", [])
.unwrap();
let err = YantrikDB::run_migration_idempotent(&conn, "ALTER TABLE memories ADD")
.expect_err("a truncated statement must not succeed");
let msg = err.to_string();
assert!(
msg.contains("migration statement"),
"error must be stage-tagged, got: {msg}"
);
assert!(
msg.contains("ALTER TABLE memories ADD"),
"error must carry the statement it choked on, got: {msg}"
);
}
#[test]
fn swallowed_replay_errors_still_do_not_leak_a_stage_error() {
use rusqlite::Connection;
let conn = Connection::open_in_memory().unwrap();
YantrikDB::run_migration_idempotent(&conn, "DROP TABLE definitely_not_a_table;")
.expect("swallowed replay errors must not become failures");
}
#[test]
fn migration_splitter_keeps_trigger_bodies_intact() {
use rusqlite::Connection;
let conn = Connection::open_in_memory().unwrap();
conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, txt TEXT)", [])
.unwrap();
conn.execute("CREATE TABLE t_log (txt TEXT)", []).unwrap();
let batch = "
CREATE INDEX IF NOT EXISTS idx_t_txt ON t(txt);
CREATE TRIGGER IF NOT EXISTS t_insert AFTER INSERT ON t BEGIN
INSERT INTO t_log(txt) VALUES (new.txt);
UPDATE t SET txt = txt WHERE id = new.id;
END;
CREATE INDEX IF NOT EXISTS idx_t_id ON t(id);
";
YantrikDB::run_migration_idempotent(&conn, batch)
.expect("a trigger body must survive the splitter");
conn.execute("INSERT INTO t (txt) VALUES ('hello')", [])
.unwrap();
let logged: String = conn
.query_row("SELECT txt FROM t_log LIMIT 1", [], |r| r.get(0))
.unwrap();
assert_eq!(logged, "hello");
YantrikDB::run_migration_idempotent(&conn, batch)
.expect("replay of a trigger-bearing batch must stay idempotent");
}
#[test]
fn quoted_begin_identifier_does_not_group_statements() {
use rusqlite::Connection;
let conn = Connection::open_in_memory().unwrap();
conn.execute("CREATE TABLE \"begin\" (id INTEGER)", [])
.unwrap();
let batch =
"CREATE TABLE \"begin\" (id INTEGER); CREATE TABLE applied_after_replay (id INTEGER);";
YantrikDB::run_migration_idempotent(&conn, batch).unwrap();
let n: i64 = conn
.query_row(
"SELECT COUNT(*) FROM sqlite_master WHERE name = 'applied_after_replay'",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(
n, 1,
"statement after a swallowed error must not be silently lost"
);
}
#[test]
fn semicolon_inside_quoted_identifier_does_not_split() {
use rusqlite::Connection;
let conn = Connection::open_in_memory().unwrap();
let batch = "CREATE TABLE \"semi;colon\" (id INTEGER); CREATE TABLE after_semi (id INTEGER);";
YantrikDB::run_migration_idempotent(&conn, batch).unwrap();
for t in ["semi;colon", "after_semi"] {
let n: i64 = conn
.query_row(
"SELECT COUNT(*) FROM sqlite_master WHERE name = ?1",
rusqlite::params![t],
|r| r.get(0),
)
.unwrap();
assert_eq!(n, 1, "table {t:?} must exist");
}
}
#[test]
fn block_comments_with_semicolons_do_not_split() {
use rusqlite::Connection;
let conn = Connection::open_in_memory().unwrap();
let batch =
"CREATE TABLE c1 (id INTEGER) /* note; with; semis */; CREATE TABLE c2 (id INTEGER);";
YantrikDB::run_migration_idempotent(&conn, batch).unwrap();
let n: i64 = conn
.query_row(
"SELECT COUNT(*) FROM sqlite_master WHERE name IN ('c1','c2')",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(n, 2);
}
#[test]
fn schema_v48_fresh_install_has_event_time_columns_and_index() {
let db = YantrikDB::new(":memory:", 8).unwrap();
let conn = db.conn();
let cols = table_columns(&conn, "memories");
for required in ["event_time_min", "event_time_max"] {
assert!(
cols.iter().any(|c| c == required),
"v48: fresh-install memories table missing column {required}"
);
}
assert!(
index_exists(&conn, "idx_memories_event_time"),
"v48: fresh-install missing partial index idx_memories_event_time"
);
}
#[test]
fn schema_v48_migration_backfills_event_time_and_creates_index() {
use tempfile::NamedTempFile;
let tmp = NamedTempFile::new().unwrap();
let path = tmp.path().to_str().unwrap();
let rid;
{
let db = YantrikDB::new(path, 8).unwrap();
rid = db
.record(
"met Alice on 2024-03-15",
"episodic",
0.5,
0.0,
604800.0,
&empty_meta(),
&vec_seed(1.0, 8),
"default",
0.8,
"general",
"user",
None,
)
.unwrap();
}
{
let conn = rusqlite::Connection::open(path).unwrap();
conn.execute(
"UPDATE memories SET event_time_min = NULL, event_time_max = NULL",
[],
)
.unwrap();
conn.execute("DROP INDEX idx_memories_event_time", [])
.unwrap();
conn.execute(
"INSERT OR REPLACE INTO meta (key, value) VALUES ('schema_version', '47')",
[],
)
.unwrap();
}
{
let db = YantrikDB::new(path, 8).unwrap();
let conn = db.conn();
let (col_min, json_min, col_max, json_max): (
Option<f64>,
Option<f64>,
Option<f64>,
Option<f64>,
) = conn
.query_row(
"SELECT event_time_min, json_extract(metadata, '$.event_time_min'), \
event_time_max, json_extract(metadata, '$.event_time_max') \
FROM memories WHERE rid = ?1",
[rid.as_str()],
|r| Ok((r.get(0)?, r.get(1)?, r.get(2)?, r.get(3)?)),
)
.unwrap();
assert!(
json_min.is_some(),
"precondition: the date-bearing record's metadata JSON carries event_time_min"
);
assert_eq!(
col_min, json_min,
"v48 backfill must copy event_time_min out of the metadata JSON"
);
assert_eq!(
col_max, json_max,
"v48 backfill must copy event_time_max out of the metadata JSON"
);
let idx: String = conn
.query_row(
"SELECT name FROM sqlite_master WHERE type='index' AND name='idx_memories_event_time'",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(idx, "idx_memories_event_time");
}
}
#[test]
fn schema_v48_backfill_skips_ciphertext_metadata() {
let conn = rusqlite::Connection::open_in_memory().unwrap();
conn.execute_batch(
"CREATE TABLE memories (rid TEXT PRIMARY KEY, \
namespace TEXT NOT NULL DEFAULT 'default', metadata TEXT);",
)
.unwrap();
conn.execute(
"INSERT INTO memories (rid, metadata) VALUES \
('plain', '{\"event_time_min\": 100.5, \"event_time_max\": 200.5}'), \
('cipher', 'AGEv1:definitely-not-json'), \
('absent', NULL)",
[],
)
.unwrap();
conn.execute_batch(crate::base::schema::MIGRATE_V47_TO_V48)
.unwrap();
let get = |rid: &str| -> (Option<f64>, Option<f64>) {
conn.query_row(
"SELECT event_time_min, event_time_max FROM memories WHERE rid = ?1",
[rid],
|r| Ok((r.get(0)?, r.get(1)?)),
)
.unwrap()
};
assert_eq!(get("plain"), (Some(100.5), Some(200.5)));
assert_eq!(
get("cipher"),
(None, None),
"ciphertext metadata must be skipped, not extracted"
);
assert_eq!(get("absent"), (None, None));
assert!(index_exists(&conn, "idx_memories_event_time"));
}
#[test]
fn schema_v49_fresh_install_has_entity_norm_column_and_index() {
let db = YantrikDB::new(":memory:", 8).unwrap();
let conn = db.conn();
let cols = table_columns(&conn, "memory_entities");
assert!(
cols.iter().any(|c| c == "entity_name_norm"),
"v49: fresh-install memory_entities table missing column entity_name_norm"
);
assert!(
index_exists(&conn, "idx_memory_entities_norm"),
"v49: fresh-install missing index idx_memory_entities_norm"
);
}
#[test]
fn schema_v49_migration_backfills_unicode_lowercase_in_rust() {
use tempfile::NamedTempFile;
let tmp = NamedTempFile::new().unwrap();
let path = tmp.path().to_str().unwrap();
{
let _db = YantrikDB::new(path, 8).unwrap();
}
{
let conn = rusqlite::Connection::open(path).unwrap();
conn.execute(
"INSERT INTO memory_entities (memory_rid, entity_name) VALUES \
('m1', 'Münster'), ('m2', 'MÜNSTER'), ('m3', 'Alpha')",
[],
)
.unwrap();
conn.execute("DROP INDEX idx_memory_entities_norm", [])
.unwrap();
conn.execute(
"INSERT OR REPLACE INTO meta (key, value) VALUES ('schema_version', '48')",
[],
)
.unwrap();
}
{
let db = YantrikDB::new(path, 8).unwrap();
let conn = db.conn();
let get_norm = |rid: &str| -> Option<String> {
conn.query_row(
"SELECT entity_name_norm FROM memory_entities WHERE memory_rid = ?1",
[rid],
|r| r.get(0),
)
.unwrap()
};
assert_eq!(
get_norm("m1").as_deref(),
Some("münster"),
"backfill must be Rust to_lowercase, not ASCII-only SQL LOWER()"
);
assert_eq!(
get_norm("m2").as_deref(),
Some("münster"),
"'MÜNSTER' must fold to 'münster' — SQL LOWER() would give 'mÜnster'"
);
assert_eq!(get_norm("m3").as_deref(), Some("alpha"));
assert!(
index_exists(&conn, "idx_memory_entities_norm"),
"v49 migration replay must recreate idx_memory_entities_norm"
);
}
}
#[test]
fn schema_v49_backfill_skips_already_stamped_rows() {
use tempfile::NamedTempFile;
let tmp = NamedTempFile::new().unwrap();
let path = tmp.path().to_str().unwrap();
{
let _db = YantrikDB::new(path, 8).unwrap();
}
{
let conn = rusqlite::Connection::open(path).unwrap();
conn.execute(
"INSERT INTO memory_entities (memory_rid, entity_name, entity_name_norm) \
VALUES ('m1', 'Kept', 'kept')",
[],
)
.unwrap();
conn.execute(
"INSERT OR REPLACE INTO meta (key, value) VALUES ('schema_version', '48')",
[],
)
.unwrap();
}
{
let _db = YantrikDB::new(path, 8).unwrap();
}
let conn = rusqlite::Connection::open(path).unwrap();
let norm: Option<String> = conn
.query_row(
"SELECT entity_name_norm FROM memory_entities WHERE memory_rid = 'm1'",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(norm.as_deref(), Some("kept"));
}