Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

iOS Sqlite DB Backup and Restore #1391

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG-Unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
- [adapters] Adapter objects now returns `dbName`
- [TypeScript] Add unsafeExecute method
- [TypeScript] Add localStorage property to Database
- [iOS] Added ability to backup and restore sqlite db with `database.writeToFile(filePath)` and `database.readFromFile(filePath)`.

### Performance

Expand Down
50 changes: 50 additions & 0 deletions native/shared/Database.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -914,4 +914,54 @@ jsi::Value Database::getLocal(jsi::String &key) {
return jsi::String::createFromUtf8(rt, text);
}

void Database::loadOrSaveDb(jsi::String &zFilename, int isSave) {

auto &rt = getRt();
sqlite3 *pInMemory = db_->sqlite;

int rc; /* Function return code */
sqlite3 *pFile; /* Database connection opened on zFilename */
sqlite3_backup *pBackup; /* Backup object used to copy data */
sqlite3 *pTo; /* Database to copy to (pFile or pInMemory) */
sqlite3 *pFrom; /* Database to copy from (pFile or pInMemory) */

/* Open the database file identified by zFilename. Exit early if this fails
** for any reason. */
rc = sqlite3_open(zFilename.utf8(rt).c_str(), &pFile);
if( rc==SQLITE_OK ){

/* If this is a 'load' operation (isSave==0), then data is copied
** from the database file just opened to database pInMemory.
** Otherwise, if this is a 'save' operation (isSave==1), then data
** is copied from pInMemory to pFile. Set the variables pFrom and
** pTo accordingly. */
pFrom = (isSave ? pInMemory : pFile);
pTo = (isSave ? pFile : pInMemory);

/* Set up the backup procedure to copy from the "main" database of
** connection pFile to the main database of connection pInMemory.
** If something goes wrong, pBackup will be set to NULL and an error
** code and message left in connection pTo.
**
** If the backup object is successfully created, call backup_step()
** to copy data from pFile to pInMemory. Then call backup_finish()
** to release resources associated with the pBackup object. If an
** error occurred, then an error code and message will be left in
** connection pTo. If no error occurred, then the error code belonging
** to pTo is set to SQLITE_OK.
*/
pBackup = sqlite3_backup_init(pTo, "main", pFrom, "main");
if( pBackup ){
(void)sqlite3_backup_step(pBackup, -1);
(void)sqlite3_backup_finish(pBackup);
}
rc = sqlite3_errcode(pTo);
}

/* Close the database connection opened on database file zFilename
** and return the result of this function. */
(void)sqlite3_close(pFile);
// return rc;
}

} // namespace watermelondb
1 change: 1 addition & 0 deletions native/shared/Database.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ class Database : public jsi::HostObject {
void unsafeResetDatabase(jsi::String &schema, int schemaVersion);
jsi::Value getLocal(jsi::String &key);
void executeMultiple(std::string sql);
void loadOrSaveDb(jsi::String &fileName, int isSave);

private:
bool initialized_;
Expand Down
8 changes: 8 additions & 0 deletions native/shared/DatabaseInstallation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,14 @@ void Database::install(jsi::Runtime *runtime) {
std::abort();
}
});
createMethod(rt, adapter, "loadOrSaveDb", 2, [database](jsi::Runtime &rt, const jsi::Value *args) {
assert(database->initialized_);
jsi::String filePath = args[0].getString(rt);
int isSave = (int)args[1].asBool();

database->loadOrSaveDb(filePath, isSave);
return jsi::Value::undefined();
});
createMethod(rt, adapter, "unsafeClose", 0, [database](jsi::Runtime &rt, const jsi::Value *args) {
assert(database->initialized_);
database->destroy();
Expand Down
10 changes: 10 additions & 0 deletions src/Database/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,16 @@ export default class Database {
}
}

async writeToFile(filePath: string): Promise<void> {
const result = await this.adapter.loadOrSaveDb(filePath, true)
return result
}

async readFromFile(filePath: string): Promise<void> {
const result = await this.adapter.loadOrSaveDb(filePath, false)
return result
}

_ensureInWriter(diagnosticMethodName: string): void {
invariant(
this._workQueue.isWriterRunning,
Expand Down
4 changes: 4 additions & 0 deletions src/adapters/compat.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@ export default class DatabaseAdapterCompat {
return toPromise((callback) => this.underlyingAdapter.removeLocal(key, callback))
}

loadOrSaveDb(filePath: string, isSave: boolean): Promise<void> {
return toPromise((callback) => this.underlyingAdapter.loadOrSaveDb(filePath, isSave, callback))
}

// untyped - test-only code
async testClone(options: any): Promise<any> {
// $FlowFixMe
Expand Down
4 changes: 4 additions & 0 deletions src/adapters/lokijs/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,10 @@ export default class LokiJSAdapter implements DatabaseAdapter {
this._dispatcher.call('removeLocal', [key], callback)
}

loadOrSaveDb(filePath: string, isSave: boolean, callback: ResultCallback<void>): void {
callback({ error: new Error('loadOrSaveDb unavailable on LokiJS') })
}

// dev/debug utility
get _driver(): any {
// $FlowFixMe
Expand Down
2 changes: 2 additions & 0 deletions src/adapters/sqlite/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ declare module '@nozbe/watermelondb/adapters/sqlite' {

setLocal(key: string, value: string): Promise<void>

loadOrSaveDb(filePath: string, isSave: Boolean): Promise<void>

unsafeClearCachedRecords(): Promise<void>

unsafeResetDatabase(): Promise<void>
Expand Down
4 changes: 4 additions & 0 deletions src/adapters/sqlite/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,10 @@ export default class SQLiteAdapter implements DatabaseAdapter {
this._dispatcher.call('batch', [[operation]], callback)
}

loadOrSaveDb(filePath: string, isSave: boolean, callback: ResultCallback<void>): void {
this._dispatcher.call('loadOrSaveDb', [filePath, isSave], callback)
}

removeLocal(key: string, callback: ResultCallback<void>): void {
const operation = [IGNORE_CACHE, null, `delete from "local_storage" where "key" == ?`, [[key]]]
this._dispatcher.call('batch', [[operation]], callback)
Expand Down
1 change: 1 addition & 0 deletions src/adapters/sqlite/type.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ export type SqliteDispatcherMethod =
| 'unsafeResetDatabase'
| 'getLocal'
| 'unsafeExecuteMultiple'
| 'loadOrSaveDb'

export interface SqliteDispatcher {
call(methodName: SqliteDispatcherMethod, args: any[], callback: ResultCallback<any>): void;
Expand Down
3 changes: 3 additions & 0 deletions src/adapters/type.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,7 @@ export interface DatabaseAdapter {

// Removes key from local storage
removeLocal(key: string, callback: ResultCallback<void>): void;

// Backup / restore db: load from or save into filePath
loadOrSaveDb(filePath: string, isSave: Boolean, callback: ResultCallback<void>): void;
}
3 changes: 3 additions & 0 deletions src/adapters/type.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,7 @@ export interface DatabaseAdapter {

// Removes key from local storage
removeLocal(key: string, callback: ResultCallback<void>): void;

// Backup / restore db: load from or save into filePath
loadOrSaveDb(filePath: string, isSave: boolean, callback: ResultCallback<void>): void;
}