Merge pull request #71 from hongshibao/master

Support backup of DB file
This commit is contained in:
Sébastien Rombauts 2015-10-28 21:35:43 +01:00
commit d51633836a
4 changed files with 332 additions and 0 deletions

View File

@ -90,6 +90,7 @@ set(SQLITECPP_SRC
${PROJECT_SOURCE_DIR}/src/Database.cpp ${PROJECT_SOURCE_DIR}/src/Database.cpp
${PROJECT_SOURCE_DIR}/src/Statement.cpp ${PROJECT_SOURCE_DIR}/src/Statement.cpp
${PROJECT_SOURCE_DIR}/src/Transaction.cpp ${PROJECT_SOURCE_DIR}/src/Transaction.cpp
${PROJECT_SOURCE_DIR}/src/Backup.cpp
) )
source_group(src FILES ${SQLITECPP_SRC}) 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/Exception.h
${PROJECT_SOURCE_DIR}/include/SQLiteCpp/Statement.h ${PROJECT_SOURCE_DIR}/include/SQLiteCpp/Statement.h
${PROJECT_SOURCE_DIR}/include/SQLiteCpp/Transaction.h ${PROJECT_SOURCE_DIR}/include/SQLiteCpp/Transaction.h
${PROJECT_SOURCE_DIR}/include/SQLiteCpp/Backup.h
) )
source_group(inc FILES ${SQLITECPP_INC}) source_group(inc FILES ${SQLITECPP_INC})
@ -110,6 +112,7 @@ set(SQLITECPP_TESTS
tests/Column_test.cpp tests/Column_test.cpp
tests/Database_test.cpp tests/Database_test.cpp
tests/Statement_test.cpp tests/Statement_test.cpp
tests/Backup_test.cpp
) )
source_group(tests FILES ${SQLITECPP_TESTS}) source_group(tests FILES ${SQLITECPP_TESTS})

156
include/SQLiteCpp/Backup.h Normal file
View File

@ -0,0 +1,156 @@
/**
* @file Backup.h
* @ingroup SQLiteCpp
* @brief Backup is used to backup a database file in a safe and online way.
*
* 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)
*/
#pragma once
#include <sqlite3.h>
#include <SQLiteCpp/Database.h>
#include <string>
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:
/**
* @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
{
return mpSQLiteBackup;
}
private:
/// @{ Backup must be non-copyable
Backup(const Backup&);
Backup& operator=(const Backup&);
/// @}
private:
sqlite3_backup* mpSQLiteBackup; //!< Pointer to SQLite Database Backup Handle
};
} // namespace SQLite

110
src/Backup.cpp Normal file
View File

@ -0,0 +1,110 @@
/**
* @file Backup.cpp
* @ingroup SQLiteCpp
* @brief Backup is used to backup a database file in a safe and online way.
*
* 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 <SQLiteCpp/Backup.h>
#include <SQLiteCpp/Exception.h>
#include <string>
namespace SQLite
{
// Initialize resource for SQLite database backup
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);
}
}
// Initialize resource for SQLite database backup
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);
}
}
// Initialize resource for SQLite database backup
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);
}
}
// Release resource for SQLite database backup
Backup::~Backup() noexcept
{
if (NULL != mpSQLiteBackup)
{
sqlite3_backup_finish(mpSQLiteBackup);
}
}
// 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);
if (SQLITE_OK != res && SQLITE_DONE != res &&
SQLITE_BUSY != res && SQLITE_LOCKED != res)
{
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;
}
// 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);
}
} // namespace SQLite

63
tests/Backup_test.cpp Normal file
View File

@ -0,0 +1,63 @@
/**
* @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 <SQLiteCpp/Backup.h>
#include <SQLiteCpp/Database.h>
#include <SQLiteCpp/Statement.h>
#include <SQLiteCpp/Exception.h>
#include <gtest/gtest.h>
#include <cstdio>
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));
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");
}