From 67fc422311eec67de8077497fe5b8e8e854e69e2 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Mon, 10 Oct 2022 15:59:45 +0000 Subject: [PATCH] Add support for _not_ creating a DB if one does not exist --- .../src/bindings_tests/replica.c | 2 +- taskchampion/lib/src/replica.rs | 2 + taskchampion/lib/taskchampion.h | 4 +- .../taskchampion/src/storage/config.rs | 8 ++- .../taskchampion/src/storage/sqlite.rs | 55 +++++++++++-------- 5 files changed, 46 insertions(+), 25 deletions(-) diff --git a/taskchampion/integration-tests/src/bindings_tests/replica.c b/taskchampion/integration-tests/src/bindings_tests/replica.c index b45b810f2..0fc0c1f54 100644 --- a/taskchampion/integration-tests/src/bindings_tests/replica.c +++ b/taskchampion/integration-tests/src/bindings_tests/replica.c @@ -14,7 +14,7 @@ static void test_replica_creation(void) { // creating an on-disk replica does not crash static void test_replica_creation_disk(void) { - TCReplica *rep = tc_replica_new_on_disk(tc_string_borrow("test-db"), NULL); + TCReplica *rep = tc_replica_new_on_disk(tc_string_borrow("test-db"), true, NULL); TEST_ASSERT_NOT_NULL(rep); TEST_ASSERT_NULL(tc_replica_error(rep).ptr); tc_replica_free(rep); diff --git a/taskchampion/lib/src/replica.rs b/taskchampion/lib/src/replica.rs index 53b3beb74..d97b679b2 100644 --- a/taskchampion/lib/src/replica.rs +++ b/taskchampion/lib/src/replica.rs @@ -140,6 +140,7 @@ pub unsafe extern "C" fn tc_replica_new_in_memory() -> *mut TCReplica { #[no_mangle] pub unsafe extern "C" fn tc_replica_new_on_disk( path: TCString, + create_if_missing: bool, error_out: *mut TCString, ) -> *mut TCReplica { wrap_constructor( @@ -150,6 +151,7 @@ pub unsafe extern "C" fn tc_replica_new_on_disk( let mut path = unsafe { TCString::val_from_arg(path) }; let storage = StorageConfig::OnDisk { taskdb_dir: path.to_path_buf_mut()?, + create_if_missing, } .into_storage()?; diff --git a/taskchampion/lib/taskchampion.h b/taskchampion/lib/taskchampion.h index ccf5744be..ee40f3997 100644 --- a/taskchampion/lib/taskchampion.h +++ b/taskchampion/lib/taskchampion.h @@ -492,7 +492,9 @@ struct TCReplica *tc_replica_new_in_memory(void); * is written to the error_out parameter (if it is not NULL) and NULL is returned. The caller * must free this string. */ -struct TCReplica *tc_replica_new_on_disk(struct TCString path, struct TCString *error_out); +struct TCReplica *tc_replica_new_on_disk(struct TCString path, + bool create_if_missing, + struct TCString *error_out); /** * Get a list of all tasks in the replica. diff --git a/taskchampion/taskchampion/src/storage/config.rs b/taskchampion/taskchampion/src/storage/config.rs index a802e4a09..52dcad7c9 100644 --- a/taskchampion/taskchampion/src/storage/config.rs +++ b/taskchampion/taskchampion/src/storage/config.rs @@ -7,6 +7,9 @@ pub enum StorageConfig { OnDisk { /// Path containing the task DB. taskdb_dir: PathBuf, + + /// Create the DB if it does not already exist + create_if_missing: bool, }, /// Store the data in memory. This is only useful for testing. InMemory, @@ -15,7 +18,10 @@ pub enum StorageConfig { impl StorageConfig { pub fn into_storage(self) -> anyhow::Result> { Ok(match self { - StorageConfig::OnDisk { taskdb_dir } => Box::new(SqliteStorage::new(taskdb_dir)?), + StorageConfig::OnDisk { + taskdb_dir, + create_if_missing, + } => Box::new(SqliteStorage::new(taskdb_dir, create_if_missing)?), StorageConfig::InMemory => Box::new(InMemoryStorage::new()), }) } diff --git a/taskchampion/taskchampion/src/storage/sqlite.rs b/taskchampion/taskchampion/src/storage/sqlite.rs index 88d2ae676..bc51e5e2b 100644 --- a/taskchampion/taskchampion/src/storage/sqlite.rs +++ b/taskchampion/taskchampion/src/storage/sqlite.rs @@ -1,7 +1,7 @@ use crate::storage::{ReplicaOp, Storage, StorageTxn, TaskMap, VersionId, DEFAULT_BASE_VERSION}; use anyhow::Context; use rusqlite::types::{FromSql, ToSql}; -use rusqlite::{params, Connection, OptionalExtension}; +use rusqlite::{params, Connection, OpenFlags, OptionalExtension}; use std::path::Path; use uuid::Uuid; @@ -76,13 +76,24 @@ pub struct SqliteStorage { } impl SqliteStorage { - pub fn new>(directory: P) -> anyhow::Result { - // Ensure parent folder exists - std::fs::create_dir_all(&directory)?; + pub fn new>( + directory: P, + create_if_missing: bool, + ) -> anyhow::Result { + if create_if_missing { + // Ensure parent folder exists + std::fs::create_dir_all(&directory)?; + } // Open (or create) database let db_file = directory.as_ref().join("taskchampion.sqlite3"); - let con = Connection::open(db_file)?; + let mut flags = OpenFlags::SQLITE_OPEN_READ_WRITE + | OpenFlags::SQLITE_OPEN_NO_MUTEX + | OpenFlags::SQLITE_OPEN_URI; + if create_if_missing { + flags |= OpenFlags::SQLITE_OPEN_CREATE; + } + let con = Connection::open_with_flags(db_file, flags)?; // Initialize database let queries = vec![ @@ -369,7 +380,7 @@ mod test { fn test_empty_dir() -> anyhow::Result<()> { let tmp_dir = TempDir::new()?; let non_existant = tmp_dir.path().join("subdir"); - let mut storage = SqliteStorage::new(&non_existant)?; + let mut storage = SqliteStorage::new(&non_existant, true)?; let uuid = Uuid::new_v4(); { let mut txn = storage.txn()?; @@ -387,7 +398,7 @@ mod test { #[test] fn drop_transaction() -> anyhow::Result<()> { let tmp_dir = TempDir::new()?; - let mut storage = SqliteStorage::new(&tmp_dir.path())?; + let mut storage = SqliteStorage::new(&tmp_dir.path(), true)?; let uuid1 = Uuid::new_v4(); let uuid2 = Uuid::new_v4(); @@ -416,7 +427,7 @@ mod test { #[test] fn test_create() -> anyhow::Result<()> { let tmp_dir = TempDir::new()?; - let mut storage = SqliteStorage::new(&tmp_dir.path())?; + let mut storage = SqliteStorage::new(&tmp_dir.path(), true)?; let uuid = Uuid::new_v4(); { let mut txn = storage.txn()?; @@ -434,7 +445,7 @@ mod test { #[test] fn test_create_exists() -> anyhow::Result<()> { let tmp_dir = TempDir::new()?; - let mut storage = SqliteStorage::new(&tmp_dir.path())?; + let mut storage = SqliteStorage::new(&tmp_dir.path(), true)?; let uuid = Uuid::new_v4(); { let mut txn = storage.txn()?; @@ -452,7 +463,7 @@ mod test { #[test] fn test_get_missing() -> anyhow::Result<()> { let tmp_dir = TempDir::new()?; - let mut storage = SqliteStorage::new(&tmp_dir.path())?; + let mut storage = SqliteStorage::new(&tmp_dir.path(), true)?; let uuid = Uuid::new_v4(); { let mut txn = storage.txn()?; @@ -465,7 +476,7 @@ mod test { #[test] fn test_set_task() -> anyhow::Result<()> { let tmp_dir = TempDir::new()?; - let mut storage = SqliteStorage::new(&tmp_dir.path())?; + let mut storage = SqliteStorage::new(&tmp_dir.path(), true)?; let uuid = Uuid::new_v4(); { let mut txn = storage.txn()?; @@ -486,7 +497,7 @@ mod test { #[test] fn test_delete_task_missing() -> anyhow::Result<()> { let tmp_dir = TempDir::new()?; - let mut storage = SqliteStorage::new(&tmp_dir.path())?; + let mut storage = SqliteStorage::new(&tmp_dir.path(), true)?; let uuid = Uuid::new_v4(); { let mut txn = storage.txn()?; @@ -498,7 +509,7 @@ mod test { #[test] fn test_delete_task_exists() -> anyhow::Result<()> { let tmp_dir = TempDir::new()?; - let mut storage = SqliteStorage::new(&tmp_dir.path())?; + let mut storage = SqliteStorage::new(&tmp_dir.path(), true)?; let uuid = Uuid::new_v4(); { let mut txn = storage.txn()?; @@ -515,7 +526,7 @@ mod test { #[test] fn test_all_tasks_empty() -> anyhow::Result<()> { let tmp_dir = TempDir::new()?; - let mut storage = SqliteStorage::new(&tmp_dir.path())?; + let mut storage = SqliteStorage::new(&tmp_dir.path(), true)?; { let mut txn = storage.txn()?; let tasks = txn.all_tasks()?; @@ -527,7 +538,7 @@ mod test { #[test] fn test_all_tasks_and_uuids() -> anyhow::Result<()> { let tmp_dir = TempDir::new()?; - let mut storage = SqliteStorage::new(&tmp_dir.path())?; + let mut storage = SqliteStorage::new(&tmp_dir.path(), true)?; let uuid1 = Uuid::new_v4(); let uuid2 = Uuid::new_v4(); { @@ -581,7 +592,7 @@ mod test { #[test] fn test_base_version_default() -> anyhow::Result<()> { let tmp_dir = TempDir::new()?; - let mut storage = SqliteStorage::new(&tmp_dir.path())?; + let mut storage = SqliteStorage::new(&tmp_dir.path(), true)?; { let mut txn = storage.txn()?; assert_eq!(txn.base_version()?, DEFAULT_BASE_VERSION); @@ -592,7 +603,7 @@ mod test { #[test] fn test_base_version_setting() -> anyhow::Result<()> { let tmp_dir = TempDir::new()?; - let mut storage = SqliteStorage::new(&tmp_dir.path())?; + let mut storage = SqliteStorage::new(&tmp_dir.path(), true)?; let u = Uuid::new_v4(); { let mut txn = storage.txn()?; @@ -609,7 +620,7 @@ mod test { #[test] fn test_operations() -> anyhow::Result<()> { let tmp_dir = TempDir::new()?; - let mut storage = SqliteStorage::new(&tmp_dir.path())?; + let mut storage = SqliteStorage::new(&tmp_dir.path(), true)?; let uuid1 = Uuid::new_v4(); let uuid2 = Uuid::new_v4(); let uuid3 = Uuid::new_v4(); @@ -694,7 +705,7 @@ mod test { #[test] fn get_working_set_empty() -> anyhow::Result<()> { let tmp_dir = TempDir::new()?; - let mut storage = SqliteStorage::new(&tmp_dir.path())?; + let mut storage = SqliteStorage::new(&tmp_dir.path(), true)?; { let mut txn = storage.txn()?; @@ -708,7 +719,7 @@ mod test { #[test] fn add_to_working_set() -> anyhow::Result<()> { let tmp_dir = TempDir::new()?; - let mut storage = SqliteStorage::new(&tmp_dir.path())?; + let mut storage = SqliteStorage::new(&tmp_dir.path(), true)?; let uuid1 = Uuid::new_v4(); let uuid2 = Uuid::new_v4(); @@ -731,7 +742,7 @@ mod test { #[test] fn clear_working_set() -> anyhow::Result<()> { let tmp_dir = TempDir::new()?; - let mut storage = SqliteStorage::new(&tmp_dir.path())?; + let mut storage = SqliteStorage::new(&tmp_dir.path(), true)?; let uuid1 = Uuid::new_v4(); let uuid2 = Uuid::new_v4(); @@ -762,7 +773,7 @@ mod test { #[test] fn set_working_set_item() -> anyhow::Result<()> { let tmp_dir = TempDir::new()?; - let mut storage = SqliteStorage::new(&tmp_dir.path())?; + let mut storage = SqliteStorage::new(&tmp_dir.path(), true)?; let uuid1 = Uuid::new_v4(); let uuid2 = Uuid::new_v4();