use rusqlite::Connection;
use crate::error::{Error, Result};
pub const SCHEMA_VERSION: i64 = 4;
const SCHEMA_LATEST: &str = include_str!("schema.sql");
const MIGRATE_1_TO_2: &str = "
CREATE TABLE node_updates (
node_id TEXT NOT NULL,
field TEXT NOT NULL,
value TEXT NOT NULL,
hlc TEXT NOT NULL,
PRIMARY KEY (node_id, field)
) STRICT, WITHOUT ROWID;
";
const MIGRATE_2_TO_3: &str = "
ALTER TABLE episodes ADD COLUMN kind TEXT NOT NULL DEFAULT '';
";
const MIGRATE_3_TO_4: &str = "
CREATE VIRTUAL TABLE nodes_fts_tri USING fts5(
name, summary,
content='nodes', content_rowid='rowid',
tokenize='trigram'
);
CREATE TRIGGER nodes_fts_tri_ai AFTER INSERT ON nodes BEGIN
INSERT INTO nodes_fts_tri(rowid, name, summary)
VALUES (new.rowid, new.name, new.summary);
END;
CREATE TRIGGER nodes_fts_tri_ad AFTER DELETE ON nodes BEGIN
INSERT INTO nodes_fts_tri(nodes_fts_tri, rowid, name, summary)
VALUES ('delete', old.rowid, old.name, old.summary);
END;
CREATE TRIGGER nodes_fts_tri_au AFTER UPDATE OF name, summary ON nodes BEGIN
INSERT INTO nodes_fts_tri(nodes_fts_tri, rowid, name, summary)
VALUES ('delete', old.rowid, old.name, old.summary);
INSERT INTO nodes_fts_tri(rowid, name, summary)
VALUES (new.rowid, new.name, new.summary);
END;
CREATE VIRTUAL TABLE edges_fts_tri USING fts5(
fact,
content='edges', content_rowid='rowid',
tokenize='trigram'
);
CREATE TRIGGER edges_fts_tri_ai AFTER INSERT ON edges BEGIN
INSERT INTO edges_fts_tri(rowid, fact) VALUES (new.rowid, new.fact);
END;
CREATE TRIGGER edges_fts_tri_ad AFTER DELETE ON edges BEGIN
INSERT INTO edges_fts_tri(edges_fts_tri, rowid, fact)
VALUES ('delete', old.rowid, old.fact);
END;
CREATE TRIGGER edges_fts_tri_au AFTER UPDATE OF fact ON edges BEGIN
INSERT INTO edges_fts_tri(edges_fts_tri, rowid, fact)
VALUES ('delete', old.rowid, old.fact);
INSERT INTO edges_fts_tri(rowid, fact) VALUES (new.rowid, new.fact);
END;
CREATE VIRTUAL TABLE episodes_fts_tri USING fts5(
content,
content='episodes', content_rowid='rowid',
tokenize='trigram'
);
CREATE TRIGGER episodes_fts_tri_ai AFTER INSERT ON episodes BEGIN
INSERT INTO episodes_fts_tri(rowid, content) VALUES (new.rowid, new.content);
END;
CREATE TRIGGER episodes_fts_tri_ad AFTER DELETE ON episodes BEGIN
INSERT INTO episodes_fts_tri(episodes_fts_tri, rowid, content)
VALUES ('delete', old.rowid, old.content);
END;
CREATE TRIGGER episodes_fts_tri_au AFTER UPDATE OF content ON episodes BEGIN
INSERT INTO episodes_fts_tri(episodes_fts_tri, rowid, content)
VALUES ('delete', old.rowid, old.content);
INSERT INTO episodes_fts_tri(rowid, content) VALUES (new.rowid, new.content);
END;
INSERT INTO nodes_fts_tri(nodes_fts_tri) VALUES ('rebuild');
INSERT INTO edges_fts_tri(edges_fts_tri) VALUES ('rebuild');
INSERT INTO episodes_fts_tri(episodes_fts_tri) VALUES ('rebuild');
";
pub(crate) fn migrate(conn: &mut Connection) -> Result<()> {
loop {
let version: i64 = conn.query_row("PRAGMA user_version", [], |r| r.get(0))?;
match version {
SCHEMA_VERSION => return Ok(()),
v if v > SCHEMA_VERSION => {
return Err(Error::SchemaTooNew {
found: v,
supported: SCHEMA_VERSION,
});
}
0 => apply_step(conn, SCHEMA_LATEST, SCHEMA_VERSION)?,
1 => apply_step(conn, MIGRATE_1_TO_2, 2)?,
2 => apply_step(conn, MIGRATE_2_TO_3, 3)?,
3 => apply_step(conn, MIGRATE_3_TO_4, 4)?,
v => unreachable!("no migration path from version {v}"),
}
}
}
fn apply_step(conn: &mut Connection, sql: &str, target: i64) -> Result<()> {
let tx = conn.transaction()?;
tx.execute_batch(sql)?;
tx.pragma_update(None, "user_version", target)?;
tx.commit()?;
Ok(())
}