sqlite3: add schema versioning
To potentially be used with automatic migrations on version updatememfd
parent
8d50e83a33
commit
1ca0bd0d96
|
@ -23,6 +23,13 @@ use crate::{error::*, logging::log};
|
||||||
pub use rusqlite::{self, params, Connection};
|
pub use rusqlite::{self, params, Connection};
|
||||||
use std::path::PathBuf;
|
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> {
|
pub fn db_path(name: &str) -> Result<PathBuf> {
|
||||||
let data_dir =
|
let data_dir =
|
||||||
xdg::BaseDirectories::with_prefix("meli").map_err(|e| MeliError::new(e.to_string()))?;
|
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()))
|
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> {
|
pub fn open_or_create_db(
|
||||||
let db_path = db_path(name)?;
|
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;
|
let mut set_mode = false;
|
||||||
if !db_path.exists() {
|
if !db_path.exists() {
|
||||||
log(
|
log(
|
||||||
format!("Creating {} database in {}", name, db_path.display()),
|
format!(
|
||||||
|
"Creating {} database in {}",
|
||||||
|
description.name,
|
||||||
|
db_path.display()
|
||||||
|
),
|
||||||
crate::INFO,
|
crate::INFO,
|
||||||
);
|
);
|
||||||
set_mode = true;
|
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.
|
permissions.set_mode(0o600); // Read/write for owner only.
|
||||||
file.set_permissions(permissions)?;
|
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)
|
conn.execute_batch(s)
|
||||||
.map_err(|e| MeliError::new(e.to_string()))?;
|
.map_err(|e| MeliError::new(e.to_string()))?;
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,6 +33,7 @@ use melib::{
|
||||||
sqlite3::{
|
sqlite3::{
|
||||||
self as melib_sqlite3,
|
self as melib_sqlite3,
|
||||||
rusqlite::{self, params},
|
rusqlite::{self, params},
|
||||||
|
DatabaseDescription,
|
||||||
},
|
},
|
||||||
thread::{SortField, SortOrder},
|
thread::{SortField, SortOrder},
|
||||||
MeliError, Result, ERROR,
|
MeliError, Result, ERROR,
|
||||||
|
@ -43,8 +44,9 @@ use std::convert::TryInto;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::{Arc, RwLock};
|
use std::sync::{Arc, RwLock};
|
||||||
|
|
||||||
const DB_NAME: &str = "index.db";
|
const DB: DatabaseDescription = DatabaseDescription {
|
||||||
const INIT_SCRIPT: &str = "CREATE TABLE IF NOT EXISTS envelopes (
|
name: "index.db",
|
||||||
|
init_script:Some( "CREATE TABLE IF NOT EXISTS envelopes (
|
||||||
id INTEGER PRIMARY KEY,
|
id INTEGER PRIMARY KEY,
|
||||||
account_id INTEGER REFERENCES accounts ON UPDATE CASCADE,
|
account_id INTEGER REFERENCES accounts ON UPDATE CASCADE,
|
||||||
hash BLOB NOT NULL UNIQUE,
|
hash BLOB NOT NULL UNIQUE,
|
||||||
|
@ -106,10 +108,12 @@ END;
|
||||||
CREATE TRIGGER IF NOT EXISTS envelopes_au AFTER UPDATE ON envelopes BEGIN
|
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(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);
|
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> {
|
pub fn db_path() -> Result<PathBuf> {
|
||||||
melib_sqlite3::db_path(DB_NAME)
|
melib_sqlite3::db_path(DB.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
//#[inline(always)]
|
//#[inline(always)]
|
||||||
|
@ -141,7 +145,7 @@ pub fn insert(
|
||||||
backend: &Arc<RwLock<Box<dyn MailBackend>>>,
|
backend: &Arc<RwLock<Box<dyn MailBackend>>>,
|
||||||
acc_name: &str,
|
acc_name: &str,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let db_path = melib_sqlite3::db_path(DB_NAME)?;
|
let db_path = db_path()?;
|
||||||
if !db_path.exists() {
|
if !db_path.exists() {
|
||||||
return Err(MeliError::new(
|
return Err(MeliError::new(
|
||||||
"Database hasn't been initialised. Run `reindex` command",
|
"Database hasn't been initialised. Run `reindex` command",
|
||||||
|
@ -231,7 +235,7 @@ pub fn insert(
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove(env_hash: EnvelopeHash) -> Result<()> {
|
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() {
|
if !db_path.exists() {
|
||||||
return Err(MeliError::new(
|
return Err(MeliError::new(
|
||||||
"Database hasn't been initialised. Run `reindex` command",
|
"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.collection.envelopes.clone(),
|
||||||
account.backend.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
|
let env_hashes = acc_mutex
|
||||||
.read()
|
.read()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
@ -340,7 +344,7 @@ pub fn search(
|
||||||
query: &Query,
|
query: &Query,
|
||||||
(sort_field, sort_order): (SortField, SortOrder),
|
(sort_field, sort_order): (SortField, SortOrder),
|
||||||
) -> ResultFuture<SmallVec<[EnvelopeHash; 512]>> {
|
) -> ResultFuture<SmallVec<[EnvelopeHash; 512]>> {
|
||||||
let db_path = melib_sqlite3::db_path(DB_NAME)?;
|
let db_path = db_path()?;
|
||||||
if !db_path.exists() {
|
if !db_path.exists() {
|
||||||
return Err(MeliError::new(
|
return Err(MeliError::new(
|
||||||
"Database hasn't been initialised. Run `reindex` command",
|
"Database hasn't been initialised. Run `reindex` command",
|
||||||
|
|
Loading…
Reference in New Issue