From 05d304b7c9f594f4b40ac6fbe398e870fc2adb8f Mon Sep 17 00:00:00 2001 From: hongshibao Date: Sun, 25 Oct 2015 18:31:16 +0800 Subject: [PATCH 1/6] add Backup class --- CMakeLists.txt | 2 + include/SQLiteCpp/Backup.h | 66 +++++++++++++++++++++++++++ src/Backup.cpp | 93 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 161 insertions(+) create mode 100644 include/SQLiteCpp/Backup.h create mode 100644 src/Backup.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 3e198ba..90a328f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -90,6 +90,7 @@ set(SQLITECPP_SRC ${PROJECT_SOURCE_DIR}/src/Database.cpp ${PROJECT_SOURCE_DIR}/src/Statement.cpp ${PROJECT_SOURCE_DIR}/src/Transaction.cpp + ${PROJECT_SOURCE_DIR}/src/Backup.cpp ) source_group(src FILES ${SQLITECPP_SRC}) @@ -102,6 +103,7 @@ set(SQLITECPP_INC ${PROJECT_SOURCE_DIR}/include/SQLiteCpp/Exception.h ${PROJECT_SOURCE_DIR}/include/SQLiteCpp/Statement.h ${PROJECT_SOURCE_DIR}/include/SQLiteCpp/Transaction.h + ${PROJECT_SOURCE_DIR}/include/SQLiteCpp/Backup.h ) source_group(inc FILES ${SQLITECPP_INC}) diff --git a/include/SQLiteCpp/Backup.h b/include/SQLiteCpp/Backup.h new file mode 100644 index 0000000..d4dd5e0 --- /dev/null +++ b/include/SQLiteCpp/Backup.h @@ -0,0 +1,66 @@ +/** + * @file Backup.h + * @ingroup SQLiteCpp + * @brief Management of a SQLite Database Backup. + * + * Copyright (c) 2015 Shibao HONG (shibaohong@outlook.com) + * + * Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt + * or copy at http://opensource.org/licenses/MIT) + */ +#pragma once + +#include + +#include + +#include + +namespace SQLite +{ + +class Backup +{ +public: + Backup(Database& aDestDatabase, + const char* apDestDatabaseName, + Database& aSrcDatabase, + const char* apSrcDatabaseName); + + Backup(Database& aDestDatabase, + const std::string& aDestDatabaseName, + Database& aSrcDatabase, + const std::string& aSrcDatabaseName); + + Backup(Database& aDestDatabase, + Database& aSrcDatabase); + + virtual ~Backup() noexcept; + + int executeStep(const int aNumPage = -1); + + int remainingPageCount(); + + int totalPageCount(); + + /** + * @brief Return raw pointer to SQLite Database Backup Handle. + * + * This is often needed to mix this wrapper with other libraries or for advance usage not supported by SQLiteCpp. + */ + inline sqlite3_backup* getHandle() const noexcept // nothrow + { + return mpSQLiteBackup; + } + +private: + /// @{ Backup must be non-copyable + Backup(const Backup&); + Backup& operator=(const Backup&); + /// @} + +private: + sqlite3_backup* mpSQLiteBackup; +}; + +} // namespace SQLite diff --git a/src/Backup.cpp b/src/Backup.cpp new file mode 100644 index 0000000..60f22e6 --- /dev/null +++ b/src/Backup.cpp @@ -0,0 +1,93 @@ +/** + * @file Backup.cpp + * @ingroup SQLiteCpp + * @brief Management of a SQLite Database Backup. + * + * Copyright (c) 2015 Shibao HONG (shibaohong@outlook.com) + * + * Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt + * or copy at http://opensource.org/licenses/MIT) + */ + +#include + +#include + +#include + +namespace SQLite +{ + +Backup::Backup(Database& aDestDatabase, + const char *apDestDatabaseName, + Database& aSrcDatabase, + const char *apSrcDatabaseName) : + mpSQLiteBackup(NULL) +{ + mpSQLiteBackup = sqlite3_backup_init(aDestDatabase.getHandle(), + apDestDatabaseName, + aSrcDatabase.getHandle(), + apSrcDatabaseName); + if (NULL == mpSQLiteBackup) + { + std::string strerr = sqlite3_errmsg(aDestDatabase.getHandle()); + throw SQLite::Exception(strerr); + } +} + +Backup::Backup(Database &aDestDatabase, + const std::string &aDestDatabaseName, + Database &aSrcDatabase, + const std::string &aSrcDatabaseName) : + mpSQLiteBackup(NULL) +{ + mpSQLiteBackup = sqlite3_backup_init(aDestDatabase.getHandle(), + aDestDatabaseName.c_str(), + aSrcDatabase.getHandle(), + aSrcDatabaseName.c_str()); + if (NULL == mpSQLiteBackup) + { + std::string strerr = sqlite3_errmsg(aDestDatabase.getHandle()); + throw SQLite::Exception(strerr); + } +} + +Backup::Backup(Database &aDestDatabase, Database &aSrcDatabase) : + mpSQLiteBackup(NULL) +{ + mpSQLiteBackup = sqlite3_backup_init(aDestDatabase.getHandle(), + "main", + aSrcDatabase.getHandle(), + "main"); + if (NULL == mpSQLiteBackup) + { + std::string strerr = sqlite3_errmsg(aDestDatabase.getHandle()); + throw SQLite::Exception(strerr); + } +} + +Backup::~Backup() noexcept +{ + if (NULL != mpSQLiteBackup) + { + sqlite3_backup_finish(mpSQLiteBackup); + } +} + +int Backup::executeStep(const int aNumPage) +{ + const int res = sqlite3_backup_step(mpSQLiteBackup, aNumPage); + return res; +} + +int Backup::remainingPageCount() +{ + return sqlite3_backup_remaining(mpSQLiteBackup); +} + +int Backup::totalPageCount() +{ + return sqlite3_backup_pagecount(mpSQLiteBackup); +} + +} // namespace SQLite From c9dcf64cd082d46bad7d9265bfac3c846ae38980 Mon Sep 17 00:00:00 2001 From: hongshibao Date: Mon, 26 Oct 2015 00:35:36 +0800 Subject: [PATCH 2/6] Add throw exception case in executeStep Add Comments --- include/SQLiteCpp/Backup.h | 19 ++++++++++++++++++- src/Backup.cpp | 11 +++++++++-- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/include/SQLiteCpp/Backup.h b/include/SQLiteCpp/Backup.h index d4dd5e0..e260d1d 100644 --- a/include/SQLiteCpp/Backup.h +++ b/include/SQLiteCpp/Backup.h @@ -1,7 +1,7 @@ /** * @file Backup.h * @ingroup SQLiteCpp - * @brief Management of a SQLite Database Backup. + * @brief Backup is used to backup a database file in a safe and online way. * * Copyright (c) 2015 Shibao HONG (shibaohong@outlook.com) * @@ -19,6 +19,23 @@ namespace SQLite { +/** + * @brief RAII encapsulation of a SQLite Database Backup process. + * + * A Backup object is used to backup a source database file to a destination database file + * in a safe and online way. + * + * Resource Acquisition Is Initialization (RAII) means that the Backup Resource + * is allocated in the constructor and released in the destructor, so that there is + * no need to worry about memory management or the validity of the underlying SQLite Backup. + * + * Thread-safety: a Backup object shall not be shared by multiple threads, because : + * 1) in the SQLite "Thread Safe" mode, "SQLite can be safely used by multiple threads + * provided that no single database connection is used simultaneously in two or more threads." + * 2) the SQLite "Serialized" mode is not supported by SQLiteC++, + * because of the way it shares the underling SQLite precompiled statement + * in a custom shared pointer (See the inner class "Statement::Ptr"). + */ class Backup { public: diff --git a/src/Backup.cpp b/src/Backup.cpp index 60f22e6..4f2e414 100644 --- a/src/Backup.cpp +++ b/src/Backup.cpp @@ -1,7 +1,7 @@ /** * @file Backup.cpp * @ingroup SQLiteCpp - * @brief Management of a SQLite Database Backup. + * @brief Backup is used to backup a database file in a safe and online way. * * Copyright (c) 2015 Shibao HONG (shibaohong@outlook.com) * @@ -74,9 +74,16 @@ Backup::~Backup() noexcept } } -int Backup::executeStep(const int aNumPage) +int Backup::executeStep(const int aNumPage /* = -1 */) { const int res = sqlite3_backup_step(mpSQLiteBackup, aNumPage); + if (SQLITE_OK != res && SQLITE_DONE != res && + SQLITE_BUSY != res && SQLITE_LOCKED != res) + { + std::string strerr("Backup executeStep error with message "); + strerr += sqlite3_errstr(res); + throw SQLite::Exception(strerr); + } return res; } From 5b312edb31f5b42c3089be8e6a3cb63c6e6f0a6f Mon Sep 17 00:00:00 2001 From: hongshibao Date: Mon, 26 Oct 2015 01:38:29 +0800 Subject: [PATCH 3/6] Add Comments --- include/SQLiteCpp/Backup.h | 75 +++++++++++++++++++++++++++++++++++++- 1 file changed, 74 insertions(+), 1 deletion(-) diff --git a/include/SQLiteCpp/Backup.h b/include/SQLiteCpp/Backup.h index e260d1d..11ca01f 100644 --- a/include/SQLiteCpp/Backup.h +++ b/include/SQLiteCpp/Backup.h @@ -39,31 +39,104 @@ namespace SQLite class Backup { public: + /** + * @brief Initialize a SQLite Backup object. + * + * Initialize a SQLite Backup object for the source database and destination database. + * The database name is "main" for the main database, "temp" for the temporary database, + * or the name specified after the AS keyword in an ATTACH statement for an attached database. + * + * Exception is thrown in case of error, then the Backup object is NOT constructed. + * + * @param[in] aDestDatabase Destination database connection + * @param[in] apDestDatabaseName Destination database name + * @param[in] aSrcDatabase Source database connection + * @param[in] apSrcDatabaseName Source database name + * + * @throw SQLite::Exception in case of error + */ Backup(Database& aDestDatabase, const char* apDestDatabaseName, Database& aSrcDatabase, const char* apSrcDatabaseName); + /** + * @brief Initialize a SQLite Backup object. + * + * Initialize a SQLite Backup object for source database and destination database. + * The database name is "main" for the main database, "temp" for the temporary database, + * or the name specified after the AS keyword in an ATTACH statement for an attached database. + * + * Exception is thrown in case of error, then the Backup object is NOT constructed. + * + * @param[in] aDestDatabase Destination database connection + * @param[in] aDestDatabaseName Destination database name + * @param[in] aSrcDatabase Source database connection + * @param[in] aSrcDatabaseName Source database name + * + * @throw SQLite::Exception in case of error + */ Backup(Database& aDestDatabase, const std::string& aDestDatabaseName, Database& aSrcDatabase, const std::string& aSrcDatabaseName); + /** + * @brief Initialize a SQLite Backup object for main databases. + * + * Initialize a SQLite Backup object for source database and destination database. + * Backup the main databases between the source and the destination. + * + * Exception is thrown in case of error, then the Backup object is NOT constructed. + * + * @param[in] aDestDatabase Destination database connection + * @param[in] aSrcDatabase Source database connection + * + * @throw SQLite::Exception in case of error + */ Backup(Database& aDestDatabase, Database& aSrcDatabase); + /** + * @brief Release the SQLite Backup resource. + */ virtual ~Backup() noexcept; + /** + * @brief Execute a step of backup with a given number of source pages to be copied + * + * Exception is thrown when SQLITE_IOERR_XXX, SQLITE_NOMEM, or SQLITE_READONLY is returned + * in sqlite3_backup_step(). These errors are considered fatal, so there is no point + * in retrying the call to executeStep(). + * + * @param[in] aNumPage The number of source pages to be copied, with a negative value meaning all remaining source pages + * + * @return SQLITE_OK/SQLITE_DONE/SQLITE_BUSY/SQLITE_LOCKED + * + * @throw SQLite::Exception in case of error + */ int executeStep(const int aNumPage = -1); + /** + * @brief Get the remaining number of source pages to be copied. + * + * @return the remaining number of source pages to be copied + */ int remainingPageCount(); + /** + * @brief Get the total number of source pages. + * + * @return the total number of source pages + */ int totalPageCount(); /** * @brief Return raw pointer to SQLite Database Backup Handle. * * This is often needed to mix this wrapper with other libraries or for advance usage not supported by SQLiteCpp. + * + * @return Raw pointer to SQLite Backup Handle */ inline sqlite3_backup* getHandle() const noexcept // nothrow { @@ -77,7 +150,7 @@ private: /// @} private: - sqlite3_backup* mpSQLiteBackup; + sqlite3_backup* mpSQLiteBackup; //!< Pointer to SQLite Database Backup Handle }; } // namespace SQLite From 08716b8938ed6715a476f50d88d22f23fa3bff2b Mon Sep 17 00:00:00 2001 From: hongshibao Date: Wed, 28 Oct 2015 02:01:32 +0800 Subject: [PATCH 4/6] Add comments --- src/Backup.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Backup.cpp b/src/Backup.cpp index 4f2e414..e14898a 100644 --- a/src/Backup.cpp +++ b/src/Backup.cpp @@ -18,6 +18,7 @@ namespace SQLite { +// Initialize resource for SQLite database backup Backup::Backup(Database& aDestDatabase, const char *apDestDatabaseName, Database& aSrcDatabase, @@ -35,6 +36,7 @@ Backup::Backup(Database& aDestDatabase, } } +// Initialize resource for SQLite database backup Backup::Backup(Database &aDestDatabase, const std::string &aDestDatabaseName, Database &aSrcDatabase, @@ -52,6 +54,7 @@ Backup::Backup(Database &aDestDatabase, } } +// Initialize resource for SQLite database backup Backup::Backup(Database &aDestDatabase, Database &aSrcDatabase) : mpSQLiteBackup(NULL) { @@ -66,6 +69,7 @@ Backup::Backup(Database &aDestDatabase, Database &aSrcDatabase) : } } +// Release resource for SQLite database backup Backup::~Backup() noexcept { if (NULL != mpSQLiteBackup) @@ -74,6 +78,7 @@ Backup::~Backup() noexcept } } +// Execute backup step with a given number of source pages to be copied int Backup::executeStep(const int aNumPage /* = -1 */) { const int res = sqlite3_backup_step(mpSQLiteBackup, aNumPage); @@ -87,11 +92,13 @@ int Backup::executeStep(const int aNumPage /* = -1 */) return res; } +// Get the number of remaining source pages to be copied in this backup process int Backup::remainingPageCount() { return sqlite3_backup_remaining(mpSQLiteBackup); } +// Get the number of total source pages to be copied in this backup process int Backup::totalPageCount() { return sqlite3_backup_pagecount(mpSQLiteBackup); From e8f956be22d0931b73d4352c953cb2e6587fbd50 Mon Sep 17 00:00:00 2001 From: hongshibao Date: Wed, 28 Oct 2015 03:04:13 +0800 Subject: [PATCH 5/6] Add Backup test --- CMakeLists.txt | 1 + include/SQLiteCpp/Backup.h | 2 +- src/Backup.cpp | 7 +++++-- tests/Backup_test.cpp | 38 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 45 insertions(+), 3 deletions(-) create mode 100644 tests/Backup_test.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 90a328f..4d8279c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -112,6 +112,7 @@ set(SQLITECPP_TESTS tests/Column_test.cpp tests/Database_test.cpp tests/Statement_test.cpp + tests/Backup_test.cpp ) source_group(tests FILES ${SQLITECPP_TESTS}) diff --git a/include/SQLiteCpp/Backup.h b/include/SQLiteCpp/Backup.h index 11ca01f..660ce80 100644 --- a/include/SQLiteCpp/Backup.h +++ b/include/SQLiteCpp/Backup.h @@ -3,7 +3,7 @@ * @ingroup SQLiteCpp * @brief Backup is used to backup a database file in a safe and online way. * - * Copyright (c) 2015 Shibao HONG (shibaohong@outlook.com) + * Copyright (c) 2015-2015 Shibao HONG (shibaohong@outlook.com) * * Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt * or copy at http://opensource.org/licenses/MIT) diff --git a/src/Backup.cpp b/src/Backup.cpp index e14898a..504042f 100644 --- a/src/Backup.cpp +++ b/src/Backup.cpp @@ -3,7 +3,7 @@ * @ingroup SQLiteCpp * @brief Backup is used to backup a database file in a safe and online way. * - * Copyright (c) 2015 Shibao HONG (shibaohong@outlook.com) + * Copyright (c) 2015-2015 Shibao HONG (shibaohong@outlook.com) * * Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt * or copy at http://opensource.org/licenses/MIT) @@ -85,8 +85,11 @@ int Backup::executeStep(const int aNumPage /* = -1 */) if (SQLITE_OK != res && SQLITE_DONE != res && SQLITE_BUSY != res && SQLITE_LOCKED != res) { - std::string strerr("Backup executeStep error with message "); + std::string strerr("Backup executeStep error"); +#if SQLITE_VERSION_NUMBER >= 3007015 // SQLite v3.7.15 is the first version with sqlite3_errstr() interface + strerr += "with error message "; strerr += sqlite3_errstr(res); +#endif throw SQLite::Exception(strerr); } return res; diff --git a/tests/Backup_test.cpp b/tests/Backup_test.cpp new file mode 100644 index 0000000..b3f3b3e --- /dev/null +++ b/tests/Backup_test.cpp @@ -0,0 +1,38 @@ +/** + * @file Backup_test.cpp + * @ingroup tests + * @brief Test of a SQLite Backup. + * + * Copyright (c) 2015-2015 Shibao HONG (shibaohong@outlook.com) + * + * Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt + * or copy at http://opensource.org/licenses/MIT) + */ + +#include +#include +#include + +#include + +#include + +TEST(Backup, executeStep) { + remove("backup_test.db3"); + remove("backup_test.db3.backup"); + SQLite::Database srcDB("backup_test.db3", SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE); + srcDB.exec("CREATE TABLE backup_test (id INTEGER PRIMARY KEY, value TEXT)"); + ASSERT_EQ(1, srcDB.exec("INSERT INTO backup_test VALUES (1, \"first\")")); + ASSERT_EQ(1, srcDB.exec("INSERT INTO backup_test VALUES (2, \"second\")")); + SQLite::Database destDB("backup_test.db3.backup", SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE); + SQLite::Backup backup(destDB, srcDB); + const int res = backup.executeStep(); + ASSERT_EQ(res, SQLITE_DONE); + SQLite::Statement query(destDB, "SELECT * FROM backup_test ORDER BY id ASC"); + ASSERT_TRUE(query.executeStep()); + EXPECT_EQ(1, query.getColumn(0).getInt()); + EXPECT_STREQ("first", query.getColumn(1)); + ASSERT_TRUE(query.executeStep()); + EXPECT_EQ(2, query.getColumn(0).getInt()); + EXPECT_STREQ("second", query.getColumn(1)); +} From df995f71a6af56ff8ff58a91a3abc41fc3073fc4 Mon Sep 17 00:00:00 2001 From: hongshibao Date: Thu, 29 Oct 2015 00:27:22 +0800 Subject: [PATCH 6/6] Add executeStepException testcase --- tests/Backup_test.cpp | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/Backup_test.cpp b/tests/Backup_test.cpp index b3f3b3e..68888bd 100644 --- a/tests/Backup_test.cpp +++ b/tests/Backup_test.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include @@ -24,10 +25,12 @@ TEST(Backup, executeStep) { srcDB.exec("CREATE TABLE backup_test (id INTEGER PRIMARY KEY, value TEXT)"); ASSERT_EQ(1, srcDB.exec("INSERT INTO backup_test VALUES (1, \"first\")")); ASSERT_EQ(1, srcDB.exec("INSERT INTO backup_test VALUES (2, \"second\")")); + SQLite::Database destDB("backup_test.db3.backup", SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE); SQLite::Backup backup(destDB, srcDB); const int res = backup.executeStep(); ASSERT_EQ(res, SQLITE_DONE); + SQLite::Statement query(destDB, "SELECT * FROM backup_test ORDER BY id ASC"); ASSERT_TRUE(query.executeStep()); EXPECT_EQ(1, query.getColumn(0).getInt()); @@ -35,4 +38,26 @@ TEST(Backup, executeStep) { ASSERT_TRUE(query.executeStep()); EXPECT_EQ(2, query.getColumn(0).getInt()); EXPECT_STREQ("second", query.getColumn(1)); + remove("backup_test.db3"); + remove("backup_test.db3.backup"); +} + +TEST(Backup, executeStepException) { + remove("backup_test.db3"); + remove("backup_test.db3.backup"); + SQLite::Database srcDB("backup_test.db3", SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE); + srcDB.exec("CREATE TABLE backup_test (id INTEGER PRIMARY KEY, value TEXT)"); + ASSERT_EQ(1, srcDB.exec("INSERT INTO backup_test VALUES (1, \"first\")")); + ASSERT_EQ(1, srcDB.exec("INSERT INTO backup_test VALUES (2, \"second\")")); + { + SQLite::Database destDB("backup_test.db3.backup", SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE); + (void)destDB; + } + { + SQLite::Database destDB("backup_test.db3.backup", SQLITE_OPEN_READONLY); + SQLite::Backup backup(destDB, srcDB); + EXPECT_THROW(backup.executeStep(), SQLite::Exception); + } + remove("backup_test.db3"); + remove("backup_test.db3.backup"); }