diff --git a/utils/sqlite/c_gate_test.cpp b/utils/sqlite/c_gate_test.cpp index 9c9bea12..8afcaf1b 100644 --- a/utils/sqlite/c_gate_test.cpp +++ b/utils/sqlite/c_gate_test.cpp @@ -31,6 +31,7 @@ #include #include "utils/fs/path.hpp" +#include "utils/optional.ipp" #include "utils/sqlite/database.hpp" #include "utils/sqlite/test_utils.hpp" @@ -68,8 +69,28 @@ ATF_TEST_CASE_BODY(c_database) } +ATF_TEST_CASE(database__db_filename); +ATF_TEST_CASE_HEAD(database__db_filename) +{ + set_md_var("descr", "Ensure that the value of db_filename cached by the " + "sqlite::database class works after a connect() call"); +} +ATF_TEST_CASE_BODY(database__db_filename) +{ + ::sqlite3* raw_db; + ATF_REQUIRE_EQ(SQLITE_OK, ::sqlite3_open_v2( + "test.db", &raw_db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL)); + + sqlite::database database = sqlite::database_c_gate::connect(raw_db); + ATF_REQUIRE_EQ(utils::make_optional(fs::path("test.db").to_absolute()), + database.db_filename()); + ::sqlite3_close(raw_db); +} + + ATF_INIT_TEST_CASES(tcs) { ATF_ADD_TEST_CASE(tcs, c_database); ATF_ADD_TEST_CASE(tcs, connect); + ATF_ADD_TEST_CASE(tcs, database__db_filename); } diff --git a/utils/sqlite/database.cpp b/utils/sqlite/database.cpp index ed1904a2..32792ff6 100644 --- a/utils/sqlite/database.cpp +++ b/utils/sqlite/database.cpp @@ -32,22 +32,36 @@ extern "C" { #include } +#include #include #include "utils/format/macros.hpp" #include "utils/fs/path.hpp" #include "utils/logging/macros.hpp" #include "utils/noncopyable.hpp" +#include "utils/optional.ipp" #include "utils/sanity.hpp" #include "utils/sqlite/exceptions.hpp" #include "utils/sqlite/statement.ipp" #include "utils/sqlite/transaction.hpp" +namespace fs = utils::fs; namespace sqlite = utils::sqlite; +using utils::none; +using utils::optional; + /// Internal implementation for sqlite::database. struct utils::sqlite::database::impl : utils::noncopyable { + /// Path to the database as seen by sqlite3_db_filename after construction. + /// + /// We need to hold a copy of this value ourselves because we want to be + /// able to identify the database connection even after close() has been + /// called, but sqlite3_db_filename returns NULL after the connection is + /// closed. + optional< fs::path > db_filename; + /// The SQLite 3 internal database. ::sqlite3* db; @@ -63,6 +77,11 @@ struct utils::sqlite::database::impl : utils::noncopyable { db(db_), owned(owned_) { + const char* raw_db_filename = ::sqlite3_db_filename(db, "main"); + if (raw_db_filename == NULL || std::strlen(raw_db_filename) == 0) + db_filename = none; + else + db_filename = utils::make_optional(fs::path(raw_db_filename)); } /// Destructor. @@ -234,6 +253,20 @@ sqlite::database::close(void) } +/// Returns the path to the connected database. +/// +/// It is OK to call this function on a live database object, even after close() +/// has been called. The returned value is consistent at all times. +/// +/// \return The path to the file that matches the connected database or none if +/// the connection points to a transient database. +const optional< fs::path >& +sqlite::database::db_filename(void) const +{ + return _pimpl->db_filename; +} + + /// Executes an arbitrary SQL string. /// /// As the documentation explains, this is unsafe. The code should really be diff --git a/utils/sqlite/database.hpp b/utils/sqlite/database.hpp index 2f96c975..75bff024 100644 --- a/utils/sqlite/database.hpp +++ b/utils/sqlite/database.hpp @@ -44,6 +44,7 @@ extern "C" { #include #include "utils/fs/path_fwd.hpp" +#include "utils/optional_fwd.hpp" #include "utils/shared_ptr.hpp" #include "utils/sqlite/c_gate_fwd.hpp" #include "utils/sqlite/statement_fwd.hpp" @@ -93,6 +94,8 @@ class database { static database temporary(void); void close(void); + const utils::optional< utils::fs::path >& db_filename(void) const; + void exec(const std::string&); transaction begin_transaction(void); diff --git a/utils/sqlite/database_test.cpp b/utils/sqlite/database_test.cpp index 12ee994a..bbf39fc4 100644 --- a/utils/sqlite/database_test.cpp +++ b/utils/sqlite/database_test.cpp @@ -32,6 +32,7 @@ #include "utils/fs/operations.hpp" #include "utils/fs/path.hpp" +#include "utils/optional.ipp" #include "utils/sqlite/statement.ipp" #include "utils/sqlite/test_utils.hpp" #include "utils/sqlite/transaction.hpp" @@ -39,6 +40,8 @@ namespace fs = utils::fs; namespace sqlite = utils::sqlite; +using utils::optional; + ATF_TEST_CASE_WITHOUT_HEAD(in_memory); ATF_TEST_CASE_BODY(in_memory) @@ -151,6 +154,36 @@ ATF_TEST_CASE_BODY(copy) } +ATF_TEST_CASE_WITHOUT_HEAD(db_filename__in_memory); +ATF_TEST_CASE_BODY(db_filename__in_memory) +{ + const sqlite::database db = sqlite::database::in_memory(); + ATF_REQUIRE(!db.db_filename()); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(db_filename__file); +ATF_TEST_CASE_BODY(db_filename__file) +{ + const sqlite::database db = sqlite::database::open(fs::path("test.db"), + sqlite::open_readwrite | sqlite::open_create); + ATF_REQUIRE(db.db_filename()); + ATF_REQUIRE_EQ(fs::path("test.db").to_absolute(), db.db_filename().get()); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(db_filename__ok_after_close); +ATF_TEST_CASE_BODY(db_filename__ok_after_close) +{ + sqlite::database db = sqlite::database::open(fs::path("test.db"), + sqlite::open_readwrite | sqlite::open_create); + const optional< fs::path > db_filename = db.db_filename(); + ATF_REQUIRE(db_filename); + db.close(); + ATF_REQUIRE_EQ(db_filename, db.db_filename()); +} + + ATF_TEST_CASE_WITHOUT_HEAD(exec__ok); ATF_TEST_CASE_BODY(exec__ok) { @@ -229,6 +262,10 @@ ATF_INIT_TEST_CASES(tcs) ATF_ADD_TEST_CASE(tcs, copy); + ATF_ADD_TEST_CASE(tcs, db_filename__in_memory); + ATF_ADD_TEST_CASE(tcs, db_filename__file); + ATF_ADD_TEST_CASE(tcs, db_filename__ok_after_close); + ATF_ADD_TEST_CASE(tcs, exec__ok); ATF_ADD_TEST_CASE(tcs, exec__fail);