Skip to content

Commit 514d7d6

Browse files
os12SRombauts
authored andcommitted
Implement Database::backup() (SRombauts#211)
Easily backup/restore a live DB via the sqlite3_backup_init() API - https://www.sqlite.org/backup.html - https://www.sqlite.org/c3ref/backup_finish.html#sqlite3backupinit Use the reference implementation from the documentation
1 parent 3ba20a3 commit 514d7d6

File tree

3 files changed

+89
-11
lines changed

3 files changed

+89
-11
lines changed

include/SQLiteCpp/Database.h

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,7 @@ class Database
313313
}
314314

315315
/**
316-
* @brief Create or redefine a SQL function or aggregate in the sqlite database.
316+
* @brief Create or redefine a SQL function or aggregate in the sqlite database.
317317
*
318318
* This is the equivalent of the sqlite3_create_function_v2 command.
319319
* @see http://www.sqlite.org/c3ref/create_function.html
@@ -341,7 +341,7 @@ class Database
341341
void (*apDestroy)(void *));
342342

343343
/**
344-
* @brief Create or redefine a SQL function or aggregate in the sqlite database.
344+
* @brief Create or redefine a SQL function or aggregate in the sqlite database.
345345
*
346346
* This is the equivalent of the sqlite3_create_function_v2 command.
347347
* @see http://www.sqlite.org/c3ref/create_function.html
@@ -373,7 +373,7 @@ class Database
373373
}
374374

375375
/**
376-
* @brief Load a module into the current sqlite database instance.
376+
* @brief Load a module into the current sqlite database instance.
377377
*
378378
* This is the equivalent of the sqlite3_load_extension call, but additionally enables
379379
* module loading support prior to loading the requested module.
@@ -392,8 +392,8 @@ class Database
392392
/**
393393
* @brief Set the key for the current sqlite database instance.
394394
*
395-
* This is the equivalent of the sqlite3_key call and should thus be called
396-
* directly after opening the database.
395+
* This is the equivalent of the sqlite3_key call and should thus be called
396+
* directly after opening the database.
397397
* Open encrypted database -> call db.key("secret") -> database ready
398398
*
399399
* @param[in] aKey Key to decode/encode the database
@@ -421,10 +421,10 @@ class Database
421421
/**
422422
* @brief Test if a file contains an unencrypted database.
423423
*
424-
* This is a simple test that reads the first bytes of a database file and
425-
* compares them to the standard header for unencrypted databases. If the
426-
* header does not match the standard string, we assume that we have an
427-
* encrypted file.
424+
* This is a simple test that reads the first bytes of a database file and
425+
* compares them to the standard header for unencrypted databases. If the
426+
* header does not match the standard string, we assume that we have an
427+
* encrypted file.
428428
*
429429
* @param[in] aFilename path/uri to a file
430430
*
@@ -434,6 +434,18 @@ class Database
434434
*/
435435
static bool isUnencrypted(const std::string& aFilename);
436436

437+
/**
438+
* @brief Load or save the database content.
439+
*
440+
* This function is used to load the contents of a database file on disk
441+
* into the "main" database of open database connection, or to save the current
442+
* contents of the database into a database file on disk.
443+
*
444+
* @return SQLITE_OK on success or an error code from SQLite.
445+
*/
446+
enum class BackupType { Save, Load };
447+
int backup(const char* zFilename, BackupType type);
448+
437449
private:
438450
/// @{ Database must be non-copyable
439451
Database(const Database&);

src/Database.cpp

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,4 +300,48 @@ bool Database::isUnencrypted(const std::string& aFilename)
300300
throw exception;
301301
}
302302

303+
// This is a reference implementation of live backup taken from the official sit:
304+
// https://www.sqlite.org/backup.html
305+
306+
int Database::backup(const char* zFilename, BackupType type) {
307+
/* Open the database file identified by zFilename. Exit early if this fails. */
308+
sqlite3* pFile;
309+
int rc = sqlite3_open(zFilename, &pFile);
310+
if (rc == SQLITE_OK)
311+
{
312+
/* If this is a 'load' operation (isSave==0), then data is copied
313+
** from the database file just opened to database mpSQLite.
314+
** Otherwise, if this is a 'save' operation (isSave==1), then data
315+
** is copied from mpSQLite to pFile. Set the variables pFrom and
316+
** pTo accordingly. */
317+
sqlite3* pFrom = (type == BackupType::Save ? mpSQLite : pFile);
318+
sqlite3* pTo = (type == BackupType::Save ? pFile : mpSQLite);
319+
320+
/* Set up the backup procedure to copy from the "main" database of
321+
** connection pFile to the main database of connection mpSQLite.
322+
** If something goes wrong, pBackup will be set to NULL and an error
323+
** code and message left in connection pTo.
324+
**
325+
** If the backup object is successfully created, call backup_step()
326+
** to copy data from pFile to mpSQLite. Then call backup_finish()
327+
** to release resources associated with the pBackup object. If an
328+
** error occurred, then an error code and message will be left in
329+
** connection pTo. If no error occurred, then the error code belonging
330+
** to pTo is set to SQLITE_OK.
331+
*/
332+
sqlite3_backup *pBackup = sqlite3_backup_init(pTo, "main", pFrom, "main");
333+
if (pBackup)
334+
{
335+
sqlite3_backup_step(pBackup, -1);
336+
sqlite3_backup_finish(pBackup);
337+
}
338+
rc = sqlite3_errcode(pTo);
339+
}
340+
341+
/* Close the database connection opened on database file zFilename
342+
** and return the result of this function. */
343+
sqlite3_close(pFile);
344+
return rc;
345+
}
346+
303347
} // namespace SQLite

tests/Database_test.cpp

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ TEST(Database, ctorExecCreateDropExist)
5656
EXPECT_TRUE(db.tableExists("test"));
5757
EXPECT_TRUE(db.tableExists(std::string("test")));
5858
EXPECT_EQ(0, db.getLastInsertRowid());
59-
59+
6060
EXPECT_EQ(0, db.exec("DROP TABLE IF EXISTS test"));
6161
EXPECT_FALSE(db.tableExists("test"));
6262
EXPECT_FALSE(db.tableExists(std::string("test")));
@@ -88,7 +88,7 @@ TEST(Database, moveConstructor)
8888
remove("test.db3");
8989
}
9090

91-
#endif
91+
#endif
9292

9393
TEST(Database, createCloseReopen)
9494
{
@@ -130,6 +130,28 @@ TEST(Database, inMemory)
130130
} // Close an destroy DB
131131
}
132132

133+
TEST(Database, import_export)
134+
{
135+
// Create a new in-memory database
136+
SQLite::Database db(":memory:", SQLite::OPEN_READWRITE);
137+
EXPECT_FALSE(db.tableExists("test"));
138+
db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT)");
139+
EXPECT_TRUE(db.tableExists("test"));
140+
141+
// Export the data into a file
142+
remove("backup");
143+
EXPECT_EQ(db.backup("backup", SQLite::Database::BackupType::Save), SQLITE_OK);
144+
145+
// Trash the table
146+
db.exec("DROP TABLE test;");
147+
EXPECT_FALSE(db.tableExists("test"));
148+
149+
// Import the data back from the file
150+
EXPECT_EQ(db.backup("backup", SQLite::Database::BackupType::Load), SQLITE_OK);
151+
152+
EXPECT_TRUE(db.tableExists("test"));
153+
}
154+
133155
#if SQLITE_VERSION_NUMBER >= 3007015 // SQLite v3.7.15 is first version with PRAGMA busy_timeout
134156
TEST(Database, busyTimeout)
135157
{

0 commit comments

Comments
 (0)