diff --git a/src/database.cc b/src/database.cc index 5f1e197d3..e7891cbd2 100644 --- a/src/database.cc +++ b/src/database.cc @@ -23,6 +23,7 @@ NAN_MODULE_INIT(Database::Init) { Nan::SetPrototypeMethod(t, "serialize", Serialize); Nan::SetPrototypeMethod(t, "parallelize", Parallelize); Nan::SetPrototypeMethod(t, "configure", Configure); + Nan::SetPrototypeMethod(t, "interrupt", Interrupt); NODE_SET_GETTER(t, "open", OpenGetter); @@ -224,6 +225,8 @@ void Database::Work_BeginClose(Baton* baton) { assert(baton->db->pending == 0); baton->db->RemoveCallbacks(); + baton->db->closing = true; + int status = uv_queue_work(uv_default_loop(), &baton->request, Work_Close, (uv_after_work_cb)Work_AfterClose); assert(status == 0); @@ -249,6 +252,8 @@ void Database::Work_AfterClose(uv_work_t* req) { Baton* baton = static_cast(req->data); Database* db = baton->db; + db->closing = false; + Local argv[1]; if (baton->status != SQLITE_OK) { EXCEPTION(Nan::New(baton->message.c_str()).ToLocalChecked(), baton->status, exception); @@ -351,6 +356,21 @@ NAN_METHOD(Database::Configure) { info.GetReturnValue().Set(info.This()); } +NAN_METHOD(Database::Interrupt) { + Database* db = Nan::ObjectWrap::Unwrap(info.This()); + + if (!db->open) { + return Nan::ThrowError("Database is not open"); + } + + if (db->closing) { + return Nan::ThrowError("Database is closing"); + } + + sqlite3_interrupt(db->_handle); + info.GetReturnValue().Set(info.This()); +} + void Database::SetBusyTimeout(Baton* baton) { assert(baton->db->open); assert(baton->db->_handle); diff --git a/src/database.h b/src/database.h index 8aeabfd5c..c5455abe3 100644 --- a/src/database.h +++ b/src/database.h @@ -103,6 +103,7 @@ class Database : public Nan::ObjectWrap { Database() : Nan::ObjectWrap(), _handle(NULL), open(false), + closing(false), locked(false), pending(0), serialize(false), @@ -151,6 +152,8 @@ class Database : public Nan::ObjectWrap { static NAN_METHOD(Configure); + static NAN_METHOD(Interrupt); + static void SetBusyTimeout(Baton* baton); static void RegisterTraceCallback(Baton* baton); @@ -171,6 +174,7 @@ class Database : public Nan::ObjectWrap { sqlite3* _handle; bool open; + bool closing; bool locked; unsigned int pending; diff --git a/test/interrupt.test.js b/test/interrupt.test.js new file mode 100644 index 000000000..c769349c7 --- /dev/null +++ b/test/interrupt.test.js @@ -0,0 +1,75 @@ +var sqlite3 = require('..'); +var assert = require('assert'); + +describe('interrupt', function() { + it('should interrupt queries', function(done) { + var interrupted = false; + var saved = null; + + var db = new sqlite3.Database(':memory:', function() { + db.serialize(); + + var setup = 'create table t (n int);'; + for (var i = 0; i < 8; i += 1) { + setup += 'insert into t values (' + i + ');'; + } + db.exec(setup); + + var query = 'select last.n ' + + 'from t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t as last'; + + db.each(query, function(err) { + if (err) { + saved = err; + } else if (!interrupted) { + interrupted = true; + db.interrupt(); + } + }); + + db.close(function() { + if (saved) { + assert.equal(saved.message, 'SQLITE_INTERRUPT: interrupted'); + assert.equal(saved.errno, sqlite3.INTERRUPT); + assert.equal(saved.code, 'SQLITE_INTERRUPT'); + done(); + } else { + done(new Error('Completed query without error, but expected error')); + } + }); + }); + }); + + it('should throw if interrupt is called before open', function(done) { + var db = new sqlite3.Database(':memory:'); + + assert.throws(function() { + db.interrupt(); + }, (/Database is not open/)); + + db.close(); + done(); + }); + + it('should throw if interrupt is called after close', function(done) { + var db = new sqlite3.Database(':memory:'); + + db.close(function() { + assert.throws(function() { + db.interrupt(); + }, (/Database is not open/)); + + done(); + }); + }); + + it('should throw if interrupt is called during close', function(done) { + var db = new sqlite3.Database(':memory:', function() { + db.close(); + assert.throws(function() { + db.interrupt(); + }, (/Database is closing/)); + done(); + }); + }); +});