use crate::error::Result;
use rusqlite::Connection;
pub const VERSION: i64 = 2;
const DDL: &str = "
CREATE TABLE meta (
key TEXT PRIMARY KEY,
value TEXT NOT NULL
);
-- `num` is the rowid under a name, so it survives a VACUUM and can be a
-- foreign key. It is also the FTS5 rowid, which is what ties a message to its
-- indexed headers.
CREATE TABLE messages (
num INTEGER PRIMARY KEY,
id TEXT NOT NULL UNIQUE,
thread TEXT NOT NULL,
timestamp INTEGER NOT NULL,
subject TEXT NOT NULL,
author TEXT NOT NULL
);
CREATE INDEX messages_thread ON messages (thread, timestamp);
CREATE INDEX messages_timestamp ON messages (timestamp);
CREATE TABLE tags (
message INTEGER NOT NULL REFERENCES messages (num) ON DELETE CASCADE,
tag TEXT NOT NULL,
PRIMARY KEY (message, tag)
) WITHOUT ROWID;
CREATE INDEX tags_tag ON tags (tag, message);
";
pub fn prepare(conn: &Connection) -> Result<()> {
conn.pragma_update(None, "journal_mode", "WAL")?;
conn.pragma_update(None, "synchronous", "NORMAL")?;
conn.pragma_update(None, "foreign_keys", true)?;
conn.busy_timeout(std::time::Duration::from_secs(5))?;
let version: i64 = conn.pragma_query_value(None, "user_version", |row| row.get(0))?;
if version == VERSION && has_tables(conn)? {
return Ok(());
}
reset(conn)
}
pub fn reset(conn: &Connection) -> Result<()> {
conn.execute_batch(
"DROP TABLE IF EXISTS search;
DROP TABLE IF EXISTS tags;
DROP TABLE IF EXISTS messages;
DROP TABLE IF EXISTS meta;",
)?;
conn.execute_batch(DDL)?;
conn.pragma_update(None, "user_version", VERSION)?;
Ok(())
}
fn has_tables(conn: &Connection) -> Result<bool> {
let found: i64 = conn.query_row(
"SELECT COUNT(*) FROM sqlite_master
WHERE name IN ('meta', 'messages', 'tags')",
[],
|row| row.get(0),
)?;
Ok(found == 3)
}