sqlite3: add schema versioning

To potentially be used with automatic migrations on version update
memfd
Manos Pitsidianakis 2020-08-25 14:02:30 +03:00
parent 8d50e83a33
commit 1ca0bd0d96
Signed by: Manos Pitsidianakis
GPG Key ID: 73627C2F690DF710
2 changed files with 44 additions and 12 deletions

View File

@ -23,6 +23,13 @@ use crate::{error::*, logging::log};
pub use rusqlite::{self, params, Connection};
use std::path::PathBuf;
#[derive(Copy, Clone, Debug)]
pub struct DatabaseDescription {
pub name: &'static str,
pub init_script: Option<&'static str>,
pub version: u32,
}
pub fn db_path(name: &str) -> Result<PathBuf> {
let data_dir =
xdg::BaseDirectories::with_prefix("meli").map_err(|e| MeliError::new(e.to_string()))?;
@ -38,12 +45,23 @@ pub fn open_db(db_path: PathBuf) -> Result<Connection> {
Connection::open(&db_path).map_err(|e| MeliError::new(e.to_string()))
}
pub fn open_or_create_db(name: &str, init_script: Option<&str>) -> Result<Connection> {
let db_path = db_path(name)?;
pub fn open_or_create_db(
description: &DatabaseDescription,
identifier: Option<&str>,
) -> Result<Connection> {
let db_path = if let Some(id) = identifier {
db_path(&format!("{}_{}", id, description.name))
} else {
db_path(description.name)
}?;
let mut set_mode = false;
if !db_path.exists() {
log(
format!("Creating {} database in {}", name, db_path.display()),
format!(
"Creating {} database in {}",
description.name,
db_path.display()
),
crate::INFO,
);
set_mode = true;
@ -58,8 +76,18 @@ pub fn open_or_create_db(name: &str, init_script: Option<&str>) -> Result<Connec
permissions.set_mode(0o600); // Read/write for owner only.
file.set_permissions(permissions)?;
}
let version: i32 = conn.pragma_query_value(None, "user_version", |row| row.get(0))?;
if version != 0_i32 && version as u32 != description.version {
return Err(MeliError::new(format!(
"Database version mismatch, is {} but expected {}",
version, description.version
)));
}
if let Some(s) = init_script {
if version == 0 {
conn.pragma_update(None, "user_version", &description.version)?;
}
if let Some(s) = description.init_script {
conn.execute_batch(s)
.map_err(|e| MeliError::new(e.to_string()))?;
}

View File

@ -33,6 +33,7 @@ use melib::{
sqlite3::{
self as melib_sqlite3,
rusqlite::{self, params},
DatabaseDescription,
},
thread::{SortField, SortOrder},
MeliError, Result, ERROR,
@ -43,8 +44,9 @@ use std::convert::TryInto;
use std::path::PathBuf;
use std::sync::{Arc, RwLock};
const DB_NAME: &str = "index.db";
const INIT_SCRIPT: &str = "CREATE TABLE IF NOT EXISTS envelopes (
const DB: DatabaseDescription = DatabaseDescription {
name: "index.db",
init_script:Some( "CREATE TABLE IF NOT EXISTS envelopes (
id INTEGER PRIMARY KEY,
account_id INTEGER REFERENCES accounts ON UPDATE CASCADE,
hash BLOB NOT NULL UNIQUE,
@ -106,10 +108,12 @@ END;
CREATE TRIGGER IF NOT EXISTS envelopes_au AFTER UPDATE ON envelopes BEGIN
INSERT INTO fts(fts, rowid, subject, body_text) VALUES('delete', old.id, old.subject, old.body_text);
INSERT INTO fts(rowid, subject, body_text) VALUES (new.id, new.subject, new.body_text);
END; ";
END; "),
version: 1,
};
pub fn db_path() -> Result<PathBuf> {
melib_sqlite3::db_path(DB_NAME)
melib_sqlite3::db_path(DB.name)
}
//#[inline(always)]
@ -141,7 +145,7 @@ pub fn insert(
backend: &Arc<RwLock<Box<dyn MailBackend>>>,
acc_name: &str,
) -> Result<()> {
let db_path = melib_sqlite3::db_path(DB_NAME)?;
let db_path = db_path()?;
if !db_path.exists() {
return Err(MeliError::new(
"Database hasn't been initialised. Run `reindex` command",
@ -231,7 +235,7 @@ pub fn insert(
}
pub fn remove(env_hash: EnvelopeHash) -> Result<()> {
let db_path = melib_sqlite3::db_path(DB_NAME)?;
let db_path = db_path()?;
if !db_path.exists() {
return Err(MeliError::new(
"Database hasn't been initialised. Run `reindex` command",
@ -271,7 +275,7 @@ pub fn index(context: &mut crate::state::Context, account_index: usize) -> Resul
account.collection.envelopes.clone(),
account.backend.clone(),
);
let conn = melib_sqlite3::open_or_create_db(DB_NAME, Some(INIT_SCRIPT))?;
let conn = melib_sqlite3::open_or_create_db(&DB, None)?;
let env_hashes = acc_mutex
.read()
.unwrap()
@ -340,7 +344,7 @@ pub fn search(
query: &Query,
(sort_field, sort_order): (SortField, SortOrder),
) -> ResultFuture<SmallVec<[EnvelopeHash; 512]>> {
let db_path = melib_sqlite3::db_path(DB_NAME)?;
let db_path = db_path()?;
if !db_path.exists() {
return Err(MeliError::new(
"Database hasn't been initialised. Run `reindex` command",