diff --git a/scripts/build_against_node.sh b/scripts/build_against_node.sh index 2982740f2..e599f68df 100755 --- a/scripts/build_against_node.sh +++ b/scripts/build_against_node.sh @@ -29,6 +29,7 @@ publish # now test building against shared sqlite export NODE_SQLITE3_JSON1=no if [[ $(uname -s) == 'Darwin' ]]; then + brew update brew install sqlite npm install --build-from-source --sqlite=$(brew --prefix) --clang=1 else diff --git a/src/database.cc b/src/database.cc index e7891cbd2..077cd3279 100644 --- a/src/database.cc +++ b/src/database.cc @@ -24,6 +24,7 @@ NAN_MODULE_INIT(Database::Init) { Nan::SetPrototypeMethod(t, "parallelize", Parallelize); Nan::SetPrototypeMethod(t, "configure", Configure); Nan::SetPrototypeMethod(t, "interrupt", Interrupt); + Nan::SetPrototypeMethod(t, "backup", Backup); NODE_SET_GETTER(t, "open", OpenGetter); @@ -143,6 +144,7 @@ NAN_METHOD(Database::New) { void Database::Work_BeginOpen(Baton* baton) { int status = uv_queue_work(uv_default_loop(), &baton->request, Work_Open, (uv_after_work_cb)Work_AfterOpen); + UNUSED_VARIABLE(status); assert(status == 0); } @@ -229,6 +231,7 @@ void Database::Work_BeginClose(Baton* baton) { int status = uv_queue_work(uv_default_loop(), &baton->request, Work_Close, (uv_after_work_cb)Work_AfterClose); + UNUSED_VARIABLE(status); assert(status == 0); } @@ -371,6 +374,83 @@ NAN_METHOD(Database::Interrupt) { info.GetReturnValue().Set(info.This()); } +NAN_METHOD(Database::Backup) { + Database* db = Nan::ObjectWrap::Unwrap(info.This()); + + REQUIRE_ARGUMENT_STRING(0, filename); + OPTIONAL_ARGUMENT_FUNCTION(1, callback); + + Baton* baton = new BackupBaton(db, callback, *filename); + db->Schedule(Work_BeginBackup, baton, false); + + info.GetReturnValue().Set(info.This()); +} + +void Database::Work_BeginBackup(Baton* baton) { + assert(baton->db->open); + assert(baton->db->_handle); + int status = uv_queue_work(uv_default_loop(), + &baton->request, Work_Backup, (uv_after_work_cb)Work_AfterExec); + UNUSED_VARIABLE(status); + assert(status == 0); +} + +void Database::Work_Backup(uv_work_t* req) { + BackupBaton* baton = static_cast(req->data); + sqlite3* file; + sqlite3_backup* backup; + + baton->status = sqlite3_open(baton->filename.c_str(), &file); + + if (baton->status == SQLITE_OK) { + backup = sqlite3_backup_init(file, "main", baton->db->_handle, "main"); + + if (backup) { + if (sqlite3_backup_step(backup, -1) == SQLITE_OK) { + sqlite3_backup_finish(backup); + } + } + + baton->status = sqlite3_errcode(file); + } + + sqlite3_close(file); + + if (baton->status != SQLITE_OK) { + baton->message = std::string(sqlite3_errmsg(file)); + } +} + +void Database::Work_AfterBackup(uv_work_t* req) { + Nan::HandleScope scope; + + BackupBaton* baton = static_cast(req->data); + Database* db = baton->db; + + Local cb = Nan::New(baton->callback); + + if (baton->status != SQLITE_OK) { + EXCEPTION(baton->message, baton->status, exception); + + if (!cb.IsEmpty() && cb->IsFunction()) { + Local argv[] = { exception }; + TRY_CATCH_CALL(db->handle(), cb, 1, argv); + } + else { + Local info[] = { Nan::New("error").ToLocalChecked(), exception }; + EMIT_EVENT(db->handle(), 2, info); + } + } + else if (!cb.IsEmpty() && cb->IsFunction()) { + Local argv[] = { Nan::Null() }; + TRY_CATCH_CALL(db->handle(), cb, 1, argv); + } + + db->Process(); + + delete baton; +} + void Database::SetBusyTimeout(Baton* baton) { assert(baton->db->open); assert(baton->db->_handle); @@ -524,6 +604,7 @@ void Database::Work_BeginExec(Baton* baton) { assert(baton->db->pending == 0); int status = uv_queue_work(uv_default_loop(), &baton->request, Work_Exec, (uv_after_work_cb)Work_AfterExec); + UNUSED_VARIABLE(status); assert(status == 0); } @@ -624,6 +705,7 @@ void Database::Work_BeginLoadExtension(Baton* baton) { assert(baton->db->pending == 0); int status = uv_queue_work(uv_default_loop(), &baton->request, Work_LoadExtension, reinterpret_cast(Work_AfterLoadExtension)); + UNUSED_VARIABLE(status); assert(status == 0); } diff --git a/src/database.h b/src/database.h index c5455abe3..40fb27267 100644 --- a/src/database.h +++ b/src/database.h @@ -68,6 +68,12 @@ class Database : public Nan::ObjectWrap { Baton(db_, cb_), filename(filename_) {} }; + struct BackupBaton : Baton { + std::string filename; + BackupBaton(Database* db_, Local cb_, const char* filename_) : + Baton(db_, cb_), filename(filename_) {} + }; + typedef void (*Work_Callback)(Baton* baton); struct Call { @@ -154,6 +160,11 @@ class Database : public Nan::ObjectWrap { static NAN_METHOD(Interrupt); + static NAN_METHOD(Backup); + static void Work_BeginBackup(Baton* baton); + static void Work_Backup(uv_work_t* req); + static void Work_AfterBackup(uv_work_t* req); + static void SetBusyTimeout(Baton* baton); static void RegisterTraceCallback(Baton* baton); diff --git a/src/macros.h b/src/macros.h index 38399ee86..63d75dc90 100644 --- a/src/macros.h +++ b/src/macros.h @@ -4,6 +4,7 @@ const char* sqlite_code_string(int code); const char* sqlite_authorizer_string(int type); +#define UNUSED_VARIABLE(var) (void)var #define REQUIRE_ARGUMENTS(n) \ if (info.Length() < (n)) { \ diff --git a/test/backup.test.js b/test/backup.test.js new file mode 100644 index 000000000..14fb122bc --- /dev/null +++ b/test/backup.test.js @@ -0,0 +1,14 @@ +var sqlite3 = require('..'); +var assert = require('assert'); + +describe('backup', function() { + var db; + before(function(done) { + db = new sqlite3.Database('test/support/prepare.db', sqlite3.OPEN_READONLY, done); + }); + + it('backup database', function(done) { + db.backup('test/support/backup.db', done); + }); +}); +