From e512ea08c10087d6068f64946a16eb40c10ccf64 Mon Sep 17 00:00:00 2001 From: Joshua Wise Date: Mon, 5 Sep 2016 04:54:34 -0400 Subject: [PATCH] overhauled scope of module --- TODO | 5 +- binding.gyp | 10 ++ deps/sqlite3.gyp | 2 +- src/objects/database/close.cc | 34 ++++-- src/objects/database/create-transaction.cc | 10 +- src/objects/database/database.cc | 44 ++++---- src/objects/database/database.h | 17 ++- src/objects/database/prepare.cc | 38 +++---- src/objects/statement/all.cc | 7 +- src/objects/statement/bind.cc | 15 +-- src/objects/statement/busy.cc | 5 + src/objects/statement/cache.cc | 38 ------- src/objects/statement/close-handles.cc | 4 - src/objects/statement/each.cc | 9 +- src/objects/statement/get.cc | 7 +- src/objects/statement/new-handle.cc | 7 -- src/objects/statement/run.cc | 7 +- src/objects/statement/statement.cc | 43 ++++--- src/objects/statement/statement.h | 37 +++--- src/objects/transaction/close-handles.cc | 9 -- src/objects/transaction/run.cc | 27 +---- src/objects/transaction/transaction.cc | 33 +++++- src/objects/transaction/transaction.h | 22 +++- src/util/handle-manager.h | 85 -------------- src/util/list.h | 106 +++++------------- src/util/macros.h | 86 +++++++------- src/workers/database-workers/close.cc | 2 +- src/workers/database-workers/open.cc | 12 +- src/workers/query-worker.h | 50 +++++++++ src/workers/statement-workers/all.cc | 31 ++--- src/workers/statement-workers/all.h | 8 +- src/workers/statement-workers/each.cc | 38 ++++--- src/workers/statement-workers/each.h | 6 +- src/workers/statement-workers/get.cc | 27 ++--- src/workers/statement-workers/get.h | 6 +- src/workers/statement-workers/run.cc | 27 ++--- src/workers/statement-workers/run.h | 6 +- .../statement-workers/statement-worker.h | 62 ---------- src/workers/transaction-worker.cc | 49 +++----- src/workers/transaction-worker.h | 13 +-- 40 files changed, 437 insertions(+), 607 deletions(-) create mode 100644 src/objects/statement/busy.cc delete mode 100644 src/objects/statement/cache.cc delete mode 100644 src/objects/statement/close-handles.cc delete mode 100644 src/objects/statement/new-handle.cc delete mode 100644 src/objects/transaction/close-handles.cc delete mode 100644 src/util/handle-manager.h create mode 100644 src/workers/query-worker.h delete mode 100644 src/workers/statement-workers/statement-worker.h diff --git a/TODO b/TODO index 23ddb4e2..97ced243 100644 --- a/TODO +++ b/TODO @@ -1,9 +1,8 @@ TODO SHORTLIST +remove emitAsync and AsyncEventEmitter? review statement reset policy (write statements dont commit until reset?) investigate BEGIN IMMEDIATE as an alternative to mutexes -transaction concurrency -better transaction destruction/CloseHandles -------------------------------------------------------------------------------- @@ -62,6 +61,8 @@ and GetNamedParameterIndex() functions we could use ExternalStringResource to make trans->source only exist in one place, to reduce memory consumption of Statement objects +consolidate bool state/config info into single bitwise flag property (Statement and Transaction) + if we promisify run(), get(), all(), or each(), their performance will suffer if more than 3 arguments are passed (more than 2 bound arguments for each()) diff --git a/binding.gyp b/binding.gyp index aeec41c8..bd26af4e 100644 --- a/binding.gyp +++ b/binding.gyp @@ -7,6 +7,16 @@ "dependencies": [ "deps/sqlite3.gyp:sqlite3" ], + 'cflags': [ + '-std=c++11', + '-stdlib=libc++' + ], + 'xcode_settings': { + 'OTHER_CPLUSPLUSFLAGS': [ + '-std=c++11', + '-stdlib=libc++' + ], + }, "sources": [ "src/objects/database/database.cc", "src/objects/statement/statement.cc", diff --git a/deps/sqlite3.gyp b/deps/sqlite3.gyp index 60f06a83..85c976c7 100755 --- a/deps/sqlite3.gyp +++ b/deps/sqlite3.gyp @@ -2,7 +2,7 @@ 'includes': [ 'common-sqlite.gypi' ], 'target_defaults': { 'default_configuration': 'Release', - 'cflags':[ + 'cflags': [ '-std=c99' ], 'configurations': { diff --git a/src/objects/database/close.cc b/src/objects/database/close.cc index ce264a1c..959cd9dd 100644 --- a/src/objects/database/close.cc +++ b/src/objects/database/close.cc @@ -7,14 +7,30 @@ NAN_METHOD(Database::Close) { db->Ref(); db->workers += 1; - // If there are no open requests on either database handle, close now. - // Otherwise, the final StatementWorker will invoke ActuallyClose() when - // it finishes executing. - if (db->requests == 0) { - db->ActuallyClose(); + // Try to close as many query objects as possible. If some queries are + // busy, we'll have to wait for the last QueryWorker to actually close + // the database. + std::set::iterator stmts_it = db->stmts.begin(); + std::set::iterator stmts_end = db->stmts.end(); + while (stmts_it != stmts_end) { + if ((*stmts_it)->CloseIfPossible()) { + stmts_it = db->stmts.erase(stmts_it); + } else { + ++stmts_it; + } } + std::set::iterator transs_it = db->transs.begin(); + std::set::iterator transs_end = db->transs.end(); + while (transs_it != transs_end) { + if ((*transs_it)->CloseIfPossible()) { + transs_it = db->transs.erase(transs_it); + } else { + ++transs_it; + } + } + db->MaybeClose(); - // This must be after the ActuallyClose() attempt, so the CloseWorker + // This must be after the TryToClose() attempt, so the CloseWorker // can detect if the database is still connecting. db->state = DB_DONE; } @@ -22,6 +38,8 @@ NAN_METHOD(Database::Close) { info.GetReturnValue().Set(info.This()); } -void Database::ActuallyClose() { - Nan::AsyncQueueWorker(new CloseWorker(this, state == DB_CONNECTING)); +void Database::MaybeClose() { + if (stmts.empty() && transs.empty()) { + Nan::AsyncQueueWorker(new CloseWorker(this, state == DB_CONNECTING)); + } } diff --git a/src/objects/database/create-transaction.cc b/src/objects/database/create-transaction.cc index 107c6ebe..1c3baaef 100644 --- a/src/objects/database/create-transaction.cc +++ b/src/objects/database/create-transaction.cc @@ -52,9 +52,12 @@ NAN_METHOD(Database::CreateTransaction) { CONSTRUCTING_PRIVILEGES = true; v8::Local cons = Nan::New(Transaction::constructor); - v8::Local transaction = cons->NewInstance(0, NULL); + Nan::MaybeLocal maybeTransaction = Nan::NewInstance(cons); CONSTRUCTING_PRIVILEGES = false; + if (maybeTransaction.IsEmpty()) {return;} + v8::Local transaction = maybeTransaction.ToLocalChecked(); + // Initializes C++ object properties. Transaction* trans = Nan::ObjectWrap::Unwrap(transaction); trans->db = db; @@ -91,8 +94,9 @@ NAN_METHOD(Database::CreateTransaction) { transaction->SetHiddenValue(Nan::New("database").ToLocalChecked(), info.This()); Nan::ForceSet(transaction, Nan::New("source").ToLocalChecked(), joinedSource, FROZEN); - // Pushes onto transs list. - db->transs.Add(trans); + // Pushes onto transs set. + trans->id = NEXT_TRANSACTION_ID++; + db->transs.insert(db->transs.end(), trans); info.GetReturnValue().Set(transaction); } diff --git a/src/objects/database/database.cc b/src/objects/database/database.cc index f078d8c4..a49bd929 100644 --- a/src/objects/database/database.cc +++ b/src/objects/database/database.cc @@ -6,12 +6,12 @@ #include "../../workers/database-workers/open.h" #include "../../workers/database-workers/close.h" #include "../../util/macros.h" -#include "../../util/handle-manager.h" -#include "../../util/frozen-buffer.h" #include "../../util/transaction-handles.h" const v8::PropertyAttribute FROZEN = static_cast(v8::DontDelete | v8::ReadOnly); bool CONSTRUCTING_PRIVILEGES = false; +sqlite3_uint64 NEXT_STATEMENT_ID = 0; +sqlite3_uint64 NEXT_TRANSACTION_ID = 0; #include "new.cc" #include "open.cc" @@ -24,13 +24,21 @@ Database::Database() : Nan::ObjectWrap(), write_handle(NULL), t_handles(NULL), state(DB_CONNECTING), - requests(0), - workers(0), - stmts(false), - transs(false) {} + workers(0) {} Database::~Database() { state = DB_DONE; - CloseHandles(this); + + // This is necessary in the case that a database and its statements are + // garbage collected at the same time. The database might be destroyed + // first, so it needs to tell all of its statements "hey, I don't exist + // anymore". By the same nature, statements must remove themselves from a + // database's sets when they are garbage collected first. + for (Statement* stmt : stmts) {stmt->CloseHandles();} + for (Transaction* trans : transs) {trans->CloseHandles();} + stmts.clear(); + transs.clear(); + + CloseHandles(); } NAN_MODULE_INIT(Database::Init) { Nan::HandleScope scope; @@ -48,22 +56,14 @@ NAN_MODULE_INIT(Database::Init) { Nan::GetFunction(t).ToLocalChecked()); } -int Database::CloseHandles(Database* db) { - // This is necessary in the case that a database and its statements are - // garbage collected at the same time. The database might be destroyed - // first, so it needs to tell all of its statements "hey, I don't exist - // anymore". By the same nature, statements must remove themselves from a - // database's list when they are garbage collected first. - db->stmts.Flush(Statement::CloseHandles()); - db->transs.Flush(Transaction::CloseHandles()); - - delete db->t_handles; - db->t_handles = NULL; +int Database::CloseHandles() { + delete t_handles; + int status1 = sqlite3_close(read_handle); + int status2 = sqlite3_close(write_handle); - int status1 = sqlite3_close(db->read_handle); - int status2 = sqlite3_close(db->write_handle); - db->read_handle = NULL; - db->write_handle = NULL; + t_handles = NULL; + read_handle = NULL; + write_handle = NULL; return status1 != SQLITE_OK ? status1 : status2; } diff --git a/src/objects/database/database.h b/src/objects/database/database.h index 1fef598a..3dafd0b7 100644 --- a/src/objects/database/database.h +++ b/src/objects/database/database.h @@ -2,13 +2,13 @@ #define NODE_SQLITE3_PLUS_DATABASE_H // Dependencies +#include #include #include +#include "../statement/statement.h" +#include "../transaction/transaction.h" #include "../../util/macros.h" -#include "../../util/list.h" #include "../../util/transaction-handles.h" -class Statement; -class Transaction; // Globals extern bool CONSTRUCTING_PRIVILEGES; @@ -26,7 +26,7 @@ class Database : public Nan::ObjectWrap { friend class CloseWorker; friend class Statement; friend class Transaction; - template friend class StatementWorker; + template friend class QueryWorker; friend class TransactionWorker; private: @@ -35,8 +35,8 @@ class Database : public Nan::ObjectWrap { static NAN_METHOD(Close); static NAN_METHOD(Prepare); static NAN_METHOD(CreateTransaction); - static int CloseHandles(Database*); - void ActuallyClose(); + int CloseHandles(); + void MaybeClose(); // Sqlite3 interfacing sqlite3* read_handle; @@ -45,12 +45,11 @@ class Database : public Nan::ObjectWrap { // State DB_STATE state; - unsigned int requests; unsigned int workers; // Associated Statements and Transactions - List stmts; - List transs; + std::set stmts; + std::set transs; }; #endif \ No newline at end of file diff --git a/src/objects/database/prepare.cc b/src/objects/database/prepare.cc index 17d9f5cd..4aca408e 100644 --- a/src/objects/database/prepare.cc +++ b/src/objects/database/prepare.cc @@ -10,26 +10,25 @@ NAN_METHOD(Database::Prepare) { CONSTRUCTING_PRIVILEGES = true; v8::Local cons = Nan::New(Statement::constructor); - v8::Local statement = cons->NewInstance(0, NULL); + Nan::MaybeLocal maybeStatement = Nan::NewInstance(cons); CONSTRUCTING_PRIVILEGES = false; - TRIM_STRING(source); - Nan::Utf8String utf8(source); + if (maybeStatement.IsEmpty()) {return;} + v8::Local statement = maybeStatement.ToLocalChecked(); - // Initializes object properties. + // Initializes c++ object properties. Statement* stmt = Nan::ObjectWrap::Unwrap(statement); stmt->db = db; - stmt->source = new FrozenBuffer(*utf8, utf8.length() + 1); - stmt->handles = new HandleManager(stmt, 1); + + // Initializes JavaScript object properties. + TRIM_STRING(source); + Nan::Utf8String utf8(source); statement->SetHiddenValue(Nan::New("database").ToLocalChecked(), info.This()); - Nan::ForceSet(statement, Nan::New("source").ToLocalChecked(), source, FROZEN); // Builds actual sqlite3_stmt handle. const char* tail; - sqlite3_stmt* handle; LOCK_DB(db->read_handle); - int status = sqlite3_prepare_v2(db->read_handle, stmt->source->data, stmt->source->length, &handle, &tail); - stmt->handles->SetFirst(handle); + int status = sqlite3_prepare_v2(db->read_handle, *utf8, utf8.length() + 1, &stmt->st_handle, &tail); // Validates the sqlite3_stmt. if (status != SQLITE_OK) { @@ -38,19 +37,18 @@ NAN_METHOD(Database::Prepare) { return Nan::ThrowError(message); } UNLOCK_DB(db->read_handle); - if (handle == NULL) { + if (stmt->st_handle == NULL) { return Nan::ThrowTypeError("The supplied SQL string contains no statements."); } - if (tail != stmt->source->data + stmt->source->length - 1) { + if (tail != *utf8 + utf8.length()) { return Nan::ThrowTypeError("The db.prepare() method only accepts a single SQL statement."); } // If the sqlite3_stmt is not read-only, replaces the handle with a proper one. - if (!sqlite3_stmt_readonly(handle)) { - sqlite3_finalize(handle); + if (!sqlite3_stmt_readonly(stmt->st_handle)) { + sqlite3_finalize(stmt->st_handle); LOCK_DB(db->write_handle); - status = sqlite3_prepare_v2(db->write_handle, stmt->source->data, stmt->source->length, &handle, NULL); - stmt->handles->SetFirst(handle); + status = sqlite3_prepare_v2(db->write_handle, *utf8, utf8.length() + 1, &stmt->st_handle, NULL); if (status != SQLITE_OK) { CONCAT3(message, "Failed to construct SQL statement (", sqlite3_errmsg(db->write_handle), ")."); @@ -65,14 +63,16 @@ NAN_METHOD(Database::Prepare) { stmt->db_handle = db->read_handle; stmt->readonly = true; - if (sqlite3_column_count(handle) < 1) { + if (sqlite3_column_count(stmt->st_handle) < 1) { return Nan::ThrowTypeError("This read-only SQL statement returns no result columns."); } } Nan::ForceSet(statement, Nan::New("readonly").ToLocalChecked(), stmt->readonly ? Nan::True() : Nan::False(), FROZEN); + Nan::ForceSet(statement, Nan::New("source").ToLocalChecked(), source, FROZEN); - // Pushes onto stmts list. - db->stmts.Add(stmt); + // Pushes onto stmts set. + stmt->id = NEXT_STATEMENT_ID++; + db->stmts.insert(db->stmts.end(), stmt); info.GetReturnValue().Set(statement); } diff --git a/src/objects/statement/all.cc b/src/objects/statement/all.cc index 1deee157..4285e157 100644 --- a/src/objects/statement/all.cc +++ b/src/objects/statement/all.cc @@ -6,8 +6,7 @@ NAN_METHOD(Statement::All) { return Nan::ThrowTypeError("This statement is not read-only. Use run() instead."); } REQUIRE_LAST_ARGUMENT_FUNCTION(func_index, func); - STATEMENT_START(stmt); - STATEMENT_BIND(stmt, func_index); - AllWorker* worker = new AllWorker(stmt, _handle, _i, new Nan::Callback(func)); - STATEMENT_END(stmt, worker); + WORKER_START(stmt, info, func_index, STATEMENT_BIND, statement); + AllWorker* worker = new AllWorker(stmt, new Nan::Callback(func)); + WORKER_END(stmt, worker); } diff --git a/src/objects/statement/bind.cc b/src/objects/statement/bind.cc index fe7a28b3..3708b9a2 100644 --- a/src/objects/statement/bind.cc +++ b/src/objects/statement/bind.cc @@ -12,20 +12,7 @@ NAN_METHOD(Statement::Bind) { return Nan::ThrowError("The associated database connection is closed."); } - int info_length = info.Length(); - int len = stmt->handles->count; - for (int i=0; ihandles->Get(i); - Binder binder(handle); - binder.Bind(info, info_length); - const char* err = binder.GetError(); - if (err) { - for (; i>=0; --i) { - sqlite3_clear_bindings(stmt->handles->Get(i)); - } - return Nan::ThrowError(err); - } - } + STATEMENT_BIND(stmt, info, info.Length()); stmt->bound = true; info.GetReturnValue().Set(info.This()); diff --git a/src/objects/statement/busy.cc b/src/objects/statement/busy.cc new file mode 100644 index 00000000..69d99f5d --- /dev/null +++ b/src/objects/statement/busy.cc @@ -0,0 +1,5 @@ +// get .busy -> boolean + +NAN_GETTER(Statement::Busy) { + info.GetReturnValue().Set(Nan::ObjectWrap::Unwrap(info.This())->busy); +} diff --git a/src/objects/statement/cache.cc b/src/objects/statement/cache.cc deleted file mode 100644 index d6abb3d8..00000000 --- a/src/objects/statement/cache.cc +++ /dev/null @@ -1,38 +0,0 @@ -// .cache(number cacheSize) -> this - -NAN_METHOD(Statement::Cache) { - REQUIRE_ARGUMENT_NUMBER(0, number); - Statement* stmt = Nan::ObjectWrap::Unwrap(info.This()); - if (stmt->config_locked) { - return Nan::ThrowTypeError("A statement's cache cannot be altered after it has been executed."); - } - if (stmt->bound) { - return Nan::ThrowTypeError("A statement's cache cannot be altered after parameters have been bound."); - } - if (stmt->db->state != DB_READY) { - return Nan::ThrowError("The associated database connection is closed."); - } - - double numberValue = number->Value(); - if (!IS_POSITIVE_INTEGER(numberValue)) { - return Nan::ThrowRangeError("Argument 0 must be a positive, finite integer."); - } - if (numberValue > (double)0x7ffffffe) { - return Nan::ThrowRangeError("The specified cache size is too large."); - } - if (numberValue < 1) { - numberValue = 1; - } - - HandleManager* handles = new HandleManager(stmt, (int)numberValue); - - if (handles->Fill([&stmt] {return stmt->NewHandle();})) { - delete handles; - return Nan::ThrowError("SQLite failed to create a prepared statement."); - } - - delete stmt->handles; - stmt->handles = handles; - - info.GetReturnValue().Set(info.This()); -} diff --git a/src/objects/statement/close-handles.cc b/src/objects/statement/close-handles.cc deleted file mode 100644 index b4bd2bad..00000000 --- a/src/objects/statement/close-handles.cc +++ /dev/null @@ -1,4 +0,0 @@ -void Statement::CloseHandles::operator() (Statement* stmt) { - delete stmt->handles; - stmt->handles = NULL; -} diff --git a/src/objects/statement/each.cc b/src/objects/statement/each.cc index e2801193..ae4aa07b 100644 --- a/src/objects/statement/each.cc +++ b/src/objects/statement/each.cc @@ -1,4 +1,4 @@ -// .each(Function callbackForEach, Function callback) -> this +// .each(Function dataHandler, Function callback) -> this NAN_METHOD(Statement::Each) { Statement* stmt = Nan::ObjectWrap::Unwrap(info.This()); @@ -6,8 +6,7 @@ NAN_METHOD(Statement::Each) { return Nan::ThrowTypeError("This statement is not read-only. Use run() instead."); } REQUIRE_LAST_TWO_ARGUMENTS_FUNCTIONS(func_index, func1, func2) - STATEMENT_START(stmt); - STATEMENT_BIND(stmt, func_index); - EachWorker* worker = new EachWorker(stmt, _handle, _i, new Nan::Callback(func2), new Nan::Callback(func1)); - STATEMENT_END(stmt, worker); + WORKER_START(stmt, info, func_index, STATEMENT_BIND, statement); + EachWorker* worker = new EachWorker(stmt, new Nan::Callback(func2), new Nan::Callback(func1)); + WORKER_END(stmt, worker); } diff --git a/src/objects/statement/get.cc b/src/objects/statement/get.cc index 1b1bbc65..0ae13274 100644 --- a/src/objects/statement/get.cc +++ b/src/objects/statement/get.cc @@ -6,8 +6,7 @@ NAN_METHOD(Statement::Get) { return Nan::ThrowTypeError("This statement is not read-only. Use run() instead."); } REQUIRE_LAST_ARGUMENT_FUNCTION(func_index, func); - STATEMENT_START(stmt); - STATEMENT_BIND(stmt, func_index); - GetWorker* worker = new GetWorker(stmt, _handle, _i, new Nan::Callback(func)); - STATEMENT_END(stmt, worker); + WORKER_START(stmt, info, func_index, STATEMENT_BIND, statement); + GetWorker* worker = new GetWorker(stmt, new Nan::Callback(func)); + WORKER_END(stmt, worker); } diff --git a/src/objects/statement/new-handle.cc b/src/objects/statement/new-handle.cc deleted file mode 100644 index eb43c0f5..00000000 --- a/src/objects/statement/new-handle.cc +++ /dev/null @@ -1,7 +0,0 @@ -// Only use while db.state == DB_READY - -sqlite3_stmt* Statement::NewHandle() { - sqlite3_stmt* handle; - sqlite3_prepare_v2(db_handle, source->data, source->length, &handle, NULL); - return handle; -} diff --git a/src/objects/statement/run.cc b/src/objects/statement/run.cc index de080ad1..fd0011f9 100644 --- a/src/objects/statement/run.cc +++ b/src/objects/statement/run.cc @@ -6,8 +6,7 @@ NAN_METHOD(Statement::Run) { return Nan::ThrowTypeError("This statement is read-only. Use get(), all(), or each() instead."); } REQUIRE_LAST_ARGUMENT_FUNCTION(func_index, func); - STATEMENT_START(stmt); - STATEMENT_BIND(stmt, func_index); - RunWorker* worker = new RunWorker(stmt, _handle, _i, new Nan::Callback(func)); - STATEMENT_END(stmt, worker); + WORKER_START(stmt, info, func_index, STATEMENT_BIND, statement); + RunWorker* worker = new RunWorker(stmt, new Nan::Callback(func)); + WORKER_END(stmt, worker); } diff --git a/src/objects/statement/statement.cc b/src/objects/statement/statement.cc index abfa1fe8..eea0b69f 100644 --- a/src/objects/statement/statement.cc +++ b/src/objects/statement/statement.cc @@ -1,3 +1,4 @@ +#include #include #include #include "statement.h" @@ -7,35 +8,27 @@ #include "../../workers/statement-workers/all.h" #include "../../workers/statement-workers/each.h" #include "../../util/macros.h" -#include "../../util/handle-manager.h" -#include "../../util/frozen-buffer.h" #include "../../binder/binder.h" #include "new.cc" -#include "cache.cc" +#include "busy.cc" #include "bind.cc" #include "pluck.cc" #include "run.cc" #include "get.cc" #include "all.cc" #include "each.cc" -#include "new-handle.cc" -#include "close-handles.cc" Statement::Statement() : Nan::ObjectWrap(), - db(NULL), - handles(NULL), - source(NULL), + st_handle(NULL), config_locked(false), bound(false), - requests(0), + busy(false), pluck_column(false) {} Statement::~Statement() { - if (handles && db) { - db->stmts.Remove(this); + if (CloseHandles()) { + db->stmts.erase(this); } - delete handles; - delete source; } void Statement::Init() { Nan::HandleScope scope; @@ -44,7 +37,7 @@ void Statement::Init() { t->InstanceTemplate()->SetInternalFieldCount(1); t->SetClassName(Nan::New("Statement").ToLocalChecked()); - Nan::SetPrototypeMethod(t, "cache", Cache); + Nan::SetAccessor(t->InstanceTemplate(), Nan::New("busy").ToLocalChecked(), Busy); Nan::SetPrototypeMethod(t, "bind", Bind); Nan::SetPrototypeMethod(t, "pluck", Pluck); Nan::SetPrototypeMethod(t, "run", Run); @@ -55,3 +48,25 @@ void Statement::Init() { constructor.Reset(Nan::GetFunction(t).ToLocalChecked()); } CONSTRUCTOR(Statement::constructor); + +bool Statement::Compare::operator() (const Statement* a, const Statement* b) { + return a->id < b->id; +} +bool Statement::CloseHandles() { + if (st_handle) { + sqlite3_finalize(st_handle); + st_handle = NULL; + return true; + } + return false; +} +bool Statement::CloseIfPossible() { + if (!busy) { + CloseHandles(); + return true; + } + return false; +} +void Statement::EraseFromSet() { + db->stmts.erase(this); +} diff --git a/src/objects/statement/statement.h b/src/objects/statement/statement.h index d7434ca1..4849df3a 100644 --- a/src/objects/statement/statement.h +++ b/src/objects/statement/statement.h @@ -2,12 +2,11 @@ #define NODE_SQLITE3_PLUS_STATEMENT_H // Dependencies +#include #include #include #include "../../util/macros.h" class Database; -class HandleManager; -class FrozenBuffer; // Class Declaration class Statement : public Nan::ObjectWrap { @@ -16,43 +15,55 @@ class Statement : public Nan::ObjectWrap { ~Statement(); static void Init(); - class CloseHandles { public: - void operator() (Statement*); + class Compare { public: + bool operator() (const Statement*, const Statement*); }; - // Friends - friend class CloseHandles; + friend class Compare; friend class Database; - friend class HandleManager; - template friend class StatementWorker; + template friend class QueryWorker; + friend class RunWorker; + friend class GetWorker; + friend class AllWorker; + friend class EachWorker; private: static CONSTRUCTOR(constructor); static NAN_METHOD(New); - static NAN_METHOD(Cache); + static NAN_GETTER(Busy); static NAN_METHOD(Bind); static NAN_METHOD(Pluck); static NAN_METHOD(Run); static NAN_METHOD(Get); static NAN_METHOD(All); static NAN_METHOD(Each); - sqlite3_stmt* NewHandle(); // This should only be invoked while db.state == DB_READY + bool CloseHandles(); // Returns true if the handles were not previously closed + bool CloseIfPossible(); // Returns true if the statement is not busy + + // Tools for QueryWorker + inline void ClearBindings() { + sqlite3_clear_bindings(st_handle); + } + void EraseFromSet(); + // Sqlite3 interfacing Database* db; sqlite3* db_handle; - HandleManager* handles; - FrozenBuffer* source; + sqlite3_stmt* st_handle; // State bool config_locked; bool bound; - unsigned int requests; + bool busy; // Config bool readonly; bool pluck_column; + + // Unique Statement Id + sqlite3_uint64 id; }; #endif \ No newline at end of file diff --git a/src/objects/transaction/close-handles.cc b/src/objects/transaction/close-handles.cc deleted file mode 100644 index 3228bcbb..00000000 --- a/src/objects/transaction/close-handles.cc +++ /dev/null @@ -1,9 +0,0 @@ -void Transaction::CloseHandles::operator() (Transaction* trans) { - if (trans->handles) { - for (unsigned int i=0; ihandle_count; ++i) { - sqlite3_finalize(trans->handles[i]); - } - delete[] trans->handles; - trans->handles = NULL; - } -} diff --git a/src/objects/transaction/run.cc b/src/objects/transaction/run.cc index 4c58880b..ef1f1da3 100644 --- a/src/objects/transaction/run.cc +++ b/src/objects/transaction/run.cc @@ -2,31 +2,8 @@ NAN_METHOD(Transaction::Run) { Transaction* trans = Nan::ObjectWrap::Unwrap(info.This()); - if (trans->busy) { - return Nan::ThrowTypeError("This transaction is mid-execution. You must wait for it to finish."); - } REQUIRE_LAST_ARGUMENT_FUNCTION(func_index, func); - if (trans->db->state != DB_READY) { - return Nan::ThrowError("The associated database connection is closed."); - } - if (!trans->config_locked) { - trans->config_locked = true; - } - - // Bind parameters. - if (!trans->bound) { - TRANSACTION_BIND(trans, info, func_index); - } else if (func_index > 0) { - return Nan::ThrowTypeError("This transaction already has bound parameters."); - } - + WORKER_START(trans, info, func_index, TRANSACTION_BIND, transaction); TransactionWorker* worker = new TransactionWorker(trans, new Nan::Callback(func)); - - // Queue worker. - trans->busy = true; - trans->db->requests += 1; - trans->Ref(); - Nan::AsyncQueueWorker(worker); - - info.GetReturnValue().Set(info.This()); + WORKER_END(trans, worker); } diff --git a/src/objects/transaction/transaction.cc b/src/objects/transaction/transaction.cc index 13859cda..4dd2883e 100644 --- a/src/objects/transaction/transaction.cc +++ b/src/objects/transaction/transaction.cc @@ -1,3 +1,4 @@ +#include #include #include #include "transaction.h" @@ -9,19 +10,16 @@ #include "busy.cc" #include "bind.cc" #include "run.cc" -#include "close-handles.cc" Transaction::Transaction() : Nan::ObjectWrap(), - db(NULL), handles(NULL), config_locked(false), bound(false), busy(false) {} Transaction::~Transaction() { - if (handles && db) { - db->transs.Remove(this); + if (CloseHandles()) { + db->transs.erase(this); } - CloseHandles()(this); } void Transaction::Init() { Nan::HandleScope scope; @@ -37,3 +35,28 @@ void Transaction::Init() { constructor.Reset(Nan::GetFunction(t).ToLocalChecked()); } CONSTRUCTOR(Transaction::constructor); + +bool Transaction::Compare::operator() (const Transaction* a, const Transaction* b) { + return a->id < b->id; +} +bool Transaction::CloseHandles() { + if (handles) { + for (unsigned int i=0; itranss.erase(this); +} diff --git a/src/objects/transaction/transaction.h b/src/objects/transaction/transaction.h index 0a1d771f..d1ab81ab 100644 --- a/src/objects/transaction/transaction.h +++ b/src/objects/transaction/transaction.h @@ -2,6 +2,7 @@ #define NODE_SQLITE3_PLUS_TRANSACTION_H // Dependencies +#include #include #include #include "../../util/macros.h" @@ -13,13 +14,14 @@ class Transaction : public Nan::ObjectWrap { ~Transaction(); static void Init(); - class CloseHandles { public: - void operator() (Transaction*); + class Compare { public: + bool operator() (const Transaction*, const Transaction*); }; // Friends - friend class CloseHandles; + friend class Compare; friend class Database; + template friend class QueryWorker; friend class TransactionWorker; private: @@ -28,6 +30,17 @@ class Transaction : public Nan::ObjectWrap { static NAN_GETTER(Busy); static NAN_METHOD(Bind); static NAN_METHOD(Run); + bool CloseHandles(); // Returns true if the handles were not previously closed + bool CloseIfPossible(); // Returns true if the transaction is not busy + + // Tools for QueryWorker + inline void ClearBindings() { + for (unsigned int i=0; i -#include "../objects/statement/statement.h" - -// This is used by Statements to manage their cache of sqlite3_stmt handles. -// Use the Request() method to get a reference to an available handle. If a -// handle is not available, a new one is created. If a handle could not be -// created, the handle argument is set to NULL. The return value is an integer -// that must be eventually passed to Release(), in order to make that handle -// available again (or free the memory of a temporary handle). -// -// When you create a new HandleManager, you need to invoke Fill(), passing a -// function whose return values are the handles to fill the HandleManager with. -// If Fill() returns non-zero, it's because one of the handles was NULL, and -// therefore the HandleManager should be considered unusable. -class HandleManager { - public: - HandleManager(Statement* stmt, int count) - : count(count) - , stmt(stmt) - , next_handle(0) { - handles = new sqlite3_stmt* [count](); - handle_states = new bool [count](); - } - ~HandleManager() { - for (int i=0; i int Fill(F fn) { - for (int i=0; i= count) { - next_handle = 0; - } - return ret; - } - *handle = stmt->NewHandle(); - return -1; - } - void Release(int index, sqlite3_stmt* handle) { - if (index == -1) { - sqlite3_finalize(handle); - } else { - if (!stmt->bound) { - sqlite3_clear_bindings(handle); - } - handle_states[index] = false; - sqlite3_reset(handle); - } - } - sqlite3_stmt* GetFirst() { - return handles[0]; - } - void SetFirst(sqlite3_stmt* handle) { - handles[0] = handle; - } - sqlite3_stmt* Get(int i) { - return handles[i]; - } - - const int count; - - private: - Statement* stmt; - int next_handle; - sqlite3_stmt** handles; - bool* handle_states; -}; - -#endif \ No newline at end of file diff --git a/src/util/list.h b/src/util/list.h index ea6ad717..23a059e4 100644 --- a/src/util/list.h +++ b/src/util/list.h @@ -6,35 +6,23 @@ class List { private: typedef struct Node { T* item; - Node* prev; + Node* next; } Node; - Node* front; - Node* end; - bool owner; + Node* first; + Node* last; public: - List(bool owner = true) : front(NULL), end(NULL), owner(owner) {} + List() : first(NULL), last(NULL) {} - // Unless `owner` is false when destruction occurs, items that were - // added to the list are automatically `delete`d. This is not - // appropriate if the item's memory must be freed with free() or - // delete[]. In such cases, `owner` should be set to false and manual - // deallocation is required. + // Items that were added to the list are automatically `delete`d. + // This is not appropriate if the item's memory must be freed with + // free() or delete[]. In such cases, List should not be used. ~List() { - Node* node = front; - if (owner) { - while (node != NULL) { - front = node->prev; - delete node->item; - delete node; - node = front; - } - } else { - while (node != NULL) { - front = node->prev; - delete node; - node = front; - } + while (first != NULL) { + Node* next = first->next; + delete first->item; + delete first; + first = next; } } @@ -42,72 +30,28 @@ class List { void Add(T* item) { Node* new_node = new Node; new_node->item = item; - new_node->prev = NULL; - if (end == NULL) { - front = end = new_node; + new_node->next = NULL; + if (last == NULL) { + first = last = new_node; } else { - end->prev = new_node; - end = new_node; - } - } - - // Removes the given item from the list, if found. If the item exists in - // the list multiple times, only the first instance (first added) is - // removed. Unless `owner` is false, the removed item is `delete`d. - void Remove(T* item) { - if (front == NULL) {return;} - - if (front->item == item) { - Node* temp = front->prev; - if (owner) {delete item;} - delete front; - front = temp; - if (front == NULL) { - end = NULL; - } - return; - } - - Node* node = front; - while (node->prev != NULL) { - if (node->prev->item == item) { - if (node->prev == end) { - end = node; - } - Node* temp = node->prev->prev; - if (owner) {delete item;} - delete node->prev; - node->prev = temp; - break; - } - node = node->prev; + last->next = new_node; + last = new_node; } } // Executes a function for each item in the list, and removes them all // from the list. The passed function must not modify the list. // The execution order goes from first-added to last-added. - // Unless `owner` is false, each item is `delete`d after their callback - // function returns. + // Each item is `delete`d after their callback function returns. template void Flush(F fn) { - Node* node = front; - if (owner) { - while (node != NULL) { - fn(node->item); - front = node->prev; - delete node->item; - delete node; - node = front; - } - } else { - while (node != NULL) { - fn(node->item); - front = node->prev; - delete node; - node = front; - } + while (first != NULL) { + fn(first->item); + Node* next = first->next; + delete first->item; + delete first; + first = next; } - end = NULL; + last = NULL; } }; diff --git a/src/util/macros.h b/src/util/macros.h index d499db8e..3a037350 100644 --- a/src/util/macros.h +++ b/src/util/macros.h @@ -165,59 +165,58 @@ inline bool IS_POSITIVE_INTEGER(double num) { #define CONSTRUCTOR(name) \ Nan::Persistent name; -// The first macro-instruction for setting up an asynchronous SQLite request. -// _i is the index returned by HandleManager#Request. -// _handle if the handle set given by HandleManager#Request. -#define STATEMENT_START(stmt) \ - if (stmt->db->state != DB_READY) { \ - return Nan::ThrowError( \ - "The associated database connection is closed."); \ - } \ - if (!stmt->config_locked) {stmt->config_locked = true;} \ - \ - sqlite3_stmt* _handle; \ - int _i = stmt->handles->Request(&_handle); \ - if (_handle == NULL) { \ - return Nan::ThrowError( \ - "SQLite failed to create a prepared statement"); \ +// Common bind logic for statements. +#define STATEMENT_BIND(stmt, info, info_length) \ + if (info_length > 0) { \ + Binder _binder(stmt->st_handle); \ + _binder.Bind(info, info_length); \ + const char* _err = _binder.GetError(); \ + if (_err) { \ + sqlite3_clear_bindings(stmt->st_handle); \ + return Nan::ThrowError(_err); \ + } \ } -#define STATEMENT_BIND(stmt, info_length) \ - if (!stmt->bound) { \ - Binder _binder(_handle); \ +// Common bind logic for transactions. +#define TRANSACTION_BIND(trans, info, info_length) \ + if (info_length > 0) { \ + MultiBinder _binder(trans->handles, trans->handle_count); \ _binder.Bind(info, info_length); \ - const char* err = _binder.GetError(); \ - if (err) { \ - stmt->handles->Release(_i, _handle); \ - return Nan::ThrowError(err); \ + const char* _err = _binder.GetError(); \ + if (_err) { \ + for (unsigned int i=0; ihandle_count; ++i) { \ + sqlite3_clear_bindings(trans->handles[i]); \ + } \ + return Nan::ThrowError(_err); \ } \ + } + +// The first macro-instruction for setting up an asynchronous SQLite request. +#define WORKER_START(obj, info, info_length, BIND_MACRO, object_name) \ + if (obj->busy) { \ + return Nan::ThrowTypeError( \ + "This " #object_name " is mid-execution. You must wait for it to finish.");\ + } \ + if (obj->db->state != DB_READY) { \ + return Nan::ThrowError( \ + "The associated database connection is closed."); \ + } \ + if (!obj->config_locked) {obj->config_locked = true;} \ + if (!obj->bound) { \ + BIND_MACRO(obj, info, info_length); \ } else if (info_length > 0) { \ - stmt->handles->Release(_i, _handle); \ return Nan::ThrowTypeError( \ - "This statement already has bound parameters."); \ + "This " #object_name " already has bound parameters."); \ } // The second macro-instruction for setting up an asynchronous SQLite request. // Returns the statement object making the request. -#define STATEMENT_END(stmt, worker) \ - stmt->requests += 1; \ - stmt->db->requests += 1; \ - stmt->Ref(); \ +#define WORKER_END(obj, worker) \ + obj->busy = true; \ + obj->Ref(); \ Nan::AsyncQueueWorker(worker); \ info.GetReturnValue().Set(info.This()); -// Common bind logic for transactions. -#define TRANSACTION_BIND(trans, info, info_length) \ - MultiBinder _binder(trans->handles, trans->handle_count); \ - _binder.Bind(info, info_length); \ - const char* _err = _binder.GetError(); \ - if (_err) { \ - for (unsigned int i=0; ihandle_count; ++i) { \ - sqlite3_clear_bindings(trans->handles[i]); \ - } \ - return Nan::ThrowError(_err); \ - } - // Enters the mutex for the sqlite3 database handle. #define LOCK_DB(db_handle) \ sqlite3_mutex_enter(sqlite3_db_mutex(db_handle)); @@ -229,12 +228,13 @@ inline bool IS_POSITIVE_INTEGER(double num) { // When used in a StatementWorker, gives the number of columns that the // statement returns, based on the sqlite3_stmt handle and pluck_column. #define GET_COLUMN_COUNT(len) \ - len = sqlite3_column_count(handle); \ + len = sqlite3_column_count(obj->st_handle); \ if (len < 1) { \ - UNLOCK_DB(db_handle); \ + sqlite3_reset(obj->st_handle); \ + UNLOCK_DB(obj->db_handle); \ return SetErrorMessage("This statement returns no result columns."); \ } \ - if (GetPluckColumn()) { \ + if (obj->pluck_column) { \ len = 1; \ } diff --git a/src/workers/database-workers/close.cc b/src/workers/database-workers/close.cc index 2653be89..a94ef689 100644 --- a/src/workers/database-workers/close.cc +++ b/src/workers/database-workers/close.cc @@ -11,7 +11,7 @@ CloseWorker::CloseWorker(Database* db, bool still_connecting) : Nan::AsyncWorker still_connecting(still_connecting) {} void CloseWorker::Execute() { if (!still_connecting) { - if (Database::CloseHandles(db) != SQLITE_OK) { + if (db->CloseHandles() != SQLITE_OK) { SetErrorMessage("Failed to successfully close the database connection."); } } diff --git a/src/workers/database-workers/open.cc b/src/workers/database-workers/open.cc index de78cf8d..876ffd96 100644 --- a/src/workers/database-workers/open.cc +++ b/src/workers/database-workers/open.cc @@ -21,14 +21,14 @@ void OpenWorker::Execute() { status = sqlite3_open_v2(filename, &db->write_handle, WRITE_MODE, NULL); if (status != SQLITE_OK) { SetErrorMessage(sqlite3_errmsg(db->write_handle)); - Database::CloseHandles(db); + db->CloseHandles(); return; } status = sqlite3_open_v2(filename, &db->read_handle, READ_MODE, NULL); if (status != SQLITE_OK) { SetErrorMessage(sqlite3_errmsg(db->read_handle)); - Database::CloseHandles(db); + db->CloseHandles(); return; } @@ -41,7 +41,7 @@ void OpenWorker::Execute() { status = sqlite3_exec(db->write_handle, "PRAGMA journal_mode = WAL; PRAGMA synchronous = 1;", NULL, 0, &err); if (status != SQLITE_OK) { SetErrorMessage(err); - Database::CloseHandles(db); + db->CloseHandles(); sqlite3_free(err); return; } @@ -50,7 +50,7 @@ void OpenWorker::Execute() { status = sqlite3_exec(db->read_handle, "PRAGMA journal_mode = WAL; PRAGMA synchronous = 1;", NULL, 0, &err); if (status != SQLITE_OK) { SetErrorMessage(err); - Database::CloseHandles(db); + db->CloseHandles(); sqlite3_free(err); return; } @@ -61,7 +61,7 @@ void OpenWorker::Execute() { db->t_handles = new TransactionHandles(db->write_handle, &status); if (status != SQLITE_OK) { SetErrorMessage(sqlite3_errmsg(db->write_handle)); - Database::CloseHandles(db); + db->CloseHandles(); return; } } @@ -72,7 +72,7 @@ void OpenWorker::HandleOKCallback() { if (--db->workers == 0) {db->Unref();} if (db->state == DB_DONE) { - Database::CloseHandles(db); + db->CloseHandles(); } else { db->state = DB_READY; v8::Local args[1] = {Nan::New("open").ToLocalChecked()}; diff --git a/src/workers/query-worker.h b/src/workers/query-worker.h new file mode 100644 index 00000000..a00cd564 --- /dev/null +++ b/src/workers/query-worker.h @@ -0,0 +1,50 @@ +#ifndef NODE_SQLITE3_PLUS_WORKER_QUERY_WORKER_H +#define NODE_SQLITE3_PLUS_WORKER_QUERY_WORKER_H + +#include +#include +#include "../objects/database/database.h" +#include "../util/macros.h" + +template +class QueryWorker : public ASYNC { + public: + QueryWorker(OBJECT* obj, Nan::Callback* cb) : ASYNC(cb) + , obj(obj) {} + + void HandleErrorCallback() { + Nan::HandleScope scope; + CONCAT2(message, "SQLite: ", ASYNC::ErrorMessage()); + Reject(Nan::Error(message)); + } + + protected: + void Resolve(v8::Local value) { + FinishRequest(); + v8::Local args[2] = {Nan::Null(), value}; + ASYNC::callback->Call(2, args); + } + void Reject(v8::Local value) { + FinishRequest(); + v8::Local args[1] = {value}; + ASYNC::callback->Call(1, args); + } + + OBJECT* const obj; + + private: + void FinishRequest() { + obj->busy = false; + obj->Unref(); + if (!obj->bound) { + obj->ClearBindings(); + } + if (obj->db->state == DB_DONE) { + obj->CloseHandles(); + obj->EraseFromSet(); + obj->db->MaybeClose(); + } + } +}; + +#endif \ No newline at end of file diff --git a/src/workers/statement-workers/all.cc b/src/workers/statement-workers/all.cc index fbe28303..dff5c16d 100644 --- a/src/workers/statement-workers/all.cc +++ b/src/workers/statement-workers/all.cc @@ -1,41 +1,42 @@ #include #include #include "all.h" -#include "statement-worker.h" +#include "../query-worker.h" #include "../../objects/statement/statement.h" #include "../../util/macros.h" #include "../../util/data.h" #include "../../util/list.h" -AllWorker::AllWorker(Statement* stmt, sqlite3_stmt* handle, int handle_index, Nan::Callback* cb) - : StatementWorker(stmt, handle, handle_index, cb), +AllWorker::AllWorker(Statement* stmt, Nan::Callback* cb) + : QueryWorker(stmt, cb), row_count(0) {} void AllWorker::Execute() { - LOCK_DB(db_handle); - int status = sqlite3_step(handle); + LOCK_DB(obj->db_handle); + int status = sqlite3_step(obj->st_handle); GET_COLUMN_COUNT(column_count); while (status == SQLITE_ROW) { ++row_count; - rows.Add(new Data::Row(handle, column_count)); - status = sqlite3_step(handle); + rows.Add(new Data::Row(obj->st_handle, column_count)); + status = sqlite3_step(obj->st_handle); } if (status != SQLITE_DONE) { - SetErrorMessage(sqlite3_errmsg(db_handle)); + SetErrorMessage(sqlite3_errmsg(obj->db_handle)); } - UNLOCK_DB(db_handle); + sqlite3_reset(obj->st_handle); + UNLOCK_DB(obj->db_handle); } void AllWorker::HandleOKCallback() { Nan::HandleScope scope; v8::Local arr = Nan::New(row_count); if (row_count > 0) { - int i = 0; + unsigned int i = 0; - if (GetPluckColumn()) { + if (obj->pluck_column) { // Fill array with plucked columns. rows.Flush([&arr, &i] (Data::Row* row) { Nan::Set(arr, i++, row->values[0]->ToJS()); @@ -45,16 +46,16 @@ void AllWorker::HandleOKCallback() { // Temporarily Cache column names. v8::Local columnNames = Nan::New(column_count); for (int j=0; jst_handle, j)).ToLocalChecked()); } // Fill array with row objects. rows.Flush([&arr, &i, &columnNames] (Data::Row* row) { - v8::Local obj = Nan::New(); + v8::Local object = Nan::New(); for (int j=0; jcolumn_count; ++j) { - Nan::ForceSet(obj, Nan::Get(columnNames, j).ToLocalChecked(), row->values[j]->ToJS()); + Nan::ForceSet(object, Nan::Get(columnNames, j).ToLocalChecked(), row->values[j]->ToJS()); } - Nan::Set(arr, i++, obj); + Nan::Set(arr, i++, object); }); } } diff --git a/src/workers/statement-workers/all.h b/src/workers/statement-workers/all.h index fb24891f..1f534da4 100644 --- a/src/workers/statement-workers/all.h +++ b/src/workers/statement-workers/all.h @@ -3,19 +3,19 @@ #include #include -#include "statement-worker.h" +#include "../query-worker.h" #include "../../util/data.h" #include "../../util/list.h" class Statement; -class AllWorker : public StatementWorker { +class AllWorker : public QueryWorker { public: - AllWorker(Statement*, sqlite3_stmt*, int, Nan::Callback*); + AllWorker(Statement*, Nan::Callback*); void Execute(); void HandleOKCallback(); private: int column_count; - int row_count; + unsigned int row_count; List rows; }; diff --git a/src/workers/statement-workers/each.cc b/src/workers/statement-workers/each.cc index de4cb94e..95a14b8c 100644 --- a/src/workers/statement-workers/each.cc +++ b/src/workers/statement-workers/each.cc @@ -2,24 +2,25 @@ #include #include #include "each.h" -#include "statement-worker.h" +#include "../query-worker.h" #include "../../objects/statement/statement.h" #include "../../util/macros.h" #include "../../util/data.h" #include "../../util/list.h" -EachWorker::EachWorker(Statement* stmt, sqlite3_stmt* handle, int handle_index, Nan::Callback* cb, Nan::Callback* progressCb) - : StatementWorker(stmt, handle, handle_index, cb), - data_mutex(NULL), handle_mutex(NULL), cached_names(false) { - progressCallback = progressCb; - } +EachWorker::EachWorker(Statement* stmt, Nan::Callback* cb, Nan::Callback* progressCb) + : QueryWorker(stmt, cb), + data_mutex(NULL), + handle_mutex(NULL), + cached_names(false), + progressCallback(progressCb) {} EachWorker::~EachWorker() { sqlite3_mutex_free(data_mutex); sqlite3_mutex_free(handle_mutex); delete progressCallback; } void EachWorker::Execute(const Nan::AsyncProgressWorker::ExecutionProgress &progress) { - // Allocated mutexes. + // Allocate mutexes. data_mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST); if (data_mutex == NULL) { return SetErrorMessage("Out of memory."); @@ -29,35 +30,36 @@ void EachWorker::Execute(const Nan::AsyncProgressWorker::ExecutionProgress &prog return SetErrorMessage("Out of memory."); } - LOCK_DB(db_handle); + LOCK_DB(obj->db_handle); // Retreive first row, and validated statement result columns. - int status = sqlite3_step(handle); + int status = sqlite3_step(obj->st_handle); GET_COLUMN_COUNT(column_count); // Retrieve and save rows. while (status == SQLITE_ROW) { sqlite3_mutex_enter(data_mutex); - rows.Add(new Data::Row(handle, column_count)); + rows.Add(new Data::Row(obj->st_handle, column_count)); sqlite3_mutex_leave(data_mutex); progress.Signal(); sqlite3_mutex_enter(handle_mutex); - status = sqlite3_step(handle); + status = sqlite3_step(obj->st_handle); sqlite3_mutex_leave(handle_mutex); } if (status != SQLITE_DONE) { - SetErrorMessage(sqlite3_errmsg(db_handle)); + SetErrorMessage(sqlite3_errmsg(obj->db_handle)); } - UNLOCK_DB(db_handle); + sqlite3_reset(obj->st_handle); + UNLOCK_DB(obj->db_handle); } void EachWorker::HandleProgressCallback(const char* not_used1, size_t not_used2) { Nan::HandleScope scope; - if (GetPluckColumn()) { + if (obj->pluck_column) { // Flush rows of plucked columns. sqlite3_mutex_enter(data_mutex); @@ -82,7 +84,7 @@ void EachWorker::HandleProgressCallback(const char* not_used1, size_t not_used2) sqlite3_mutex_enter(handle_mutex); for (int i=0; ist_handle, i)).ToLocalChecked()); } sqlite3_mutex_leave(handle_mutex); @@ -94,13 +96,13 @@ void EachWorker::HandleProgressCallback(const char* not_used1, size_t not_used2) sqlite3_mutex_enter(data_mutex); rows.Flush([this, &columnNames] (Data::Row* row) { - v8::Local obj = Nan::New(); + v8::Local object = Nan::New(); for (int i=0; icolumn_count; ++i) { - Nan::ForceSet(obj, Nan::Get(columnNames, i).ToLocalChecked(), row->values[i]->ToJS()); + Nan::ForceSet(object, Nan::Get(columnNames, i).ToLocalChecked(), row->values[i]->ToJS()); } sqlite3_mutex_leave(data_mutex); - v8::Local args[1] = {obj}; + v8::Local args[1] = {object}; progressCallback->Call(1, args); sqlite3_mutex_enter(data_mutex); diff --git a/src/workers/statement-workers/each.h b/src/workers/statement-workers/each.h index 95d5e905..78614be4 100644 --- a/src/workers/statement-workers/each.h +++ b/src/workers/statement-workers/each.h @@ -4,14 +4,14 @@ #include #include #include -#include "statement-worker.h" +#include "../query-worker.h" #include "../../util/data.h" #include "../../util/list.h" class Statement; -class EachWorker : public StatementWorker { +class EachWorker : public QueryWorker { public: - EachWorker(Statement*, sqlite3_stmt*, int, Nan::Callback*, Nan::Callback*); + EachWorker(Statement*, Nan::Callback*, Nan::Callback*); ~EachWorker(); void Execute(const Nan::AsyncProgressWorker::ExecutionProgress&); void HandleProgressCallback(const char*, size_t); diff --git a/src/workers/statement-workers/get.cc b/src/workers/statement-workers/get.cc index 1267bdf5..392697cf 100644 --- a/src/workers/statement-workers/get.cc +++ b/src/workers/statement-workers/get.cc @@ -1,27 +1,28 @@ #include #include #include "get.h" -#include "statement-worker.h" +#include "../query-worker.h" #include "../../objects/statement/statement.h" #include "../../util/macros.h" #include "../../util/data.h" -GetWorker::GetWorker(Statement* stmt, sqlite3_stmt* handle, int handle_index, Nan::Callback* cb) - : StatementWorker(stmt, handle, handle_index, cb) {} +GetWorker::GetWorker(Statement* stmt, Nan::Callback* cb) + : QueryWorker(stmt, cb) {} void GetWorker::Execute() { - LOCK_DB(db_handle); - int status = sqlite3_step(handle); + LOCK_DB(obj->db_handle); + int status = sqlite3_step(obj->st_handle); int column_count; GET_COLUMN_COUNT(column_count); if (status == SQLITE_ROW) { - row.Fill(handle, column_count); + row.Fill(obj->st_handle, column_count); } else if (status != SQLITE_DONE) { - SetErrorMessage(sqlite3_errmsg(db_handle)); + SetErrorMessage(sqlite3_errmsg(obj->db_handle)); } - UNLOCK_DB(db_handle); + sqlite3_reset(obj->st_handle); + UNLOCK_DB(obj->db_handle); } void GetWorker::HandleOKCallback() { Nan::HandleScope scope; @@ -32,17 +33,17 @@ void GetWorker::HandleOKCallback() { } // Resolve with the plucked column. - if (GetPluckColumn()) { + if (obj->pluck_column) { return Resolve(row.values[0]->ToJS()); } // Resolve with every column. - v8::Local obj = Nan::New(); + v8::Local object = Nan::New(); for (int i=0; ist_handle, i)).ToLocalChecked(), row.values[i]->ToJS()); } - Resolve(obj); + Resolve(object); } diff --git a/src/workers/statement-workers/get.h b/src/workers/statement-workers/get.h index 15231d99..a3296b72 100644 --- a/src/workers/statement-workers/get.h +++ b/src/workers/statement-workers/get.h @@ -3,13 +3,13 @@ #include #include -#include "statement-worker.h" +#include "../query-worker.h" #include "../../util/data.h" class Statement; -class GetWorker : public StatementWorker { +class GetWorker : public QueryWorker { public: - GetWorker(Statement*, sqlite3_stmt*, int, Nan::Callback*); + GetWorker(Statement*, Nan::Callback*); void Execute(); void HandleOKCallback(); private: diff --git a/src/workers/statement-workers/run.cc b/src/workers/statement-workers/run.cc index 5f499601..28af0a00 100644 --- a/src/workers/statement-workers/run.cc +++ b/src/workers/statement-workers/run.cc @@ -1,31 +1,32 @@ #include #include #include "run.h" -#include "statement-worker.h" +#include "../query-worker.h" #include "../../objects/statement/statement.h" #include "../../util/macros.h" -RunWorker::RunWorker(Statement* stmt, sqlite3_stmt* handle, int handle_index, Nan::Callback* cb) - : StatementWorker(stmt, handle, handle_index, cb) {} +RunWorker::RunWorker(Statement* stmt, Nan::Callback* cb) + : QueryWorker(stmt, cb) {} void RunWorker::Execute() { - LOCK_DB(db_handle); - int status = sqlite3_step(handle); + LOCK_DB(obj->db_handle); + int status = sqlite3_step(obj->st_handle); if (status == SQLITE_DONE) { - changes = sqlite3_changes(db_handle); - id = sqlite3_last_insert_rowid(db_handle); + changes = sqlite3_changes(obj->db_handle); + id = sqlite3_last_insert_rowid(obj->db_handle); } else if (status != SQLITE_ROW) { - SetErrorMessage(sqlite3_errmsg(db_handle)); + SetErrorMessage(sqlite3_errmsg(obj->db_handle)); } else { SetErrorMessage("Unexpected data returned by a write transaction."); } - UNLOCK_DB(db_handle); + sqlite3_reset(obj->st_handle); + UNLOCK_DB(obj->db_handle); } void RunWorker::HandleOKCallback() { Nan::HandleScope scope; - v8::Local obj = Nan::New(); - Nan::ForceSet(obj, Nan::New("changes").ToLocalChecked(), Nan::New((double)changes)); - Nan::ForceSet(obj, Nan::New("id").ToLocalChecked(), Nan::New((double)id)); + v8::Local object = Nan::New(); + Nan::ForceSet(object, Nan::New("changes").ToLocalChecked(), Nan::New((double)changes)); + Nan::ForceSet(object, Nan::New("id").ToLocalChecked(), Nan::New((double)id)); - Resolve(obj); + Resolve(object); } diff --git a/src/workers/statement-workers/run.h b/src/workers/statement-workers/run.h index d5407843..3473ba87 100644 --- a/src/workers/statement-workers/run.h +++ b/src/workers/statement-workers/run.h @@ -3,12 +3,12 @@ #include #include -#include "statement-worker.h" +#include "../query-worker.h" class Statement; -class RunWorker : public StatementWorker { +class RunWorker : public QueryWorker { public: - RunWorker(Statement*, sqlite3_stmt*, int, Nan::Callback*); + RunWorker(Statement*, Nan::Callback*); void Execute(); void HandleOKCallback(); private: diff --git a/src/workers/statement-workers/statement-worker.h b/src/workers/statement-workers/statement-worker.h deleted file mode 100644 index cf75340c..00000000 --- a/src/workers/statement-workers/statement-worker.h +++ /dev/null @@ -1,62 +0,0 @@ -#ifndef NODE_SQLITE3_PLUS_WORKER_STATEMENT_WORKER_H -#define NODE_SQLITE3_PLUS_WORKER_STATEMENT_WORKER_H - -#include -#include -#include "../../objects/database/database.h" -#include "../../objects/statement/statement.h" -#include "../../util/macros.h" -#include "../../util/handle-manager.h" - -template -class StatementWorker : public T { - public: - StatementWorker(Statement* stmt, sqlite3_stmt* handle, int handle_index, Nan::Callback* cb) - : T(cb), - handle(handle), - db_handle(stmt->db_handle), - stmt(stmt), - handle_index(handle_index) {} - - void HandleErrorCallback() { - Nan::HandleScope scope; - CONCAT2(message, "SQLite: ", T::ErrorMessage()); - Reject(Nan::Error(message)); - } - - protected: - void Resolve(v8::Local value) { - FinishRequest(); - v8::Local args[2] = {Nan::Null(), value}; - T::callback->Call(2, args); - } - void Reject(v8::Local value) { - FinishRequest(); - v8::Local args[1] = {value}; - T::callback->Call(1, args); - } - inline bool GetPluckColumn() { - return stmt->pluck_column; - } - - sqlite3_stmt* const handle; - sqlite3* const db_handle; - - private: - Statement* const stmt; - int const handle_index; - - void FinishRequest() { - stmt->requests -= 1; - stmt->db->requests -= 1; - stmt->handles->Release(handle_index, handle); - if (stmt->requests == 0) { - stmt->Unref(); - if (stmt->db->state == DB_DONE && stmt->db->requests == 0) { - stmt->db->ActuallyClose(); - } - } - } -}; - -#endif \ No newline at end of file diff --git a/src/workers/transaction-worker.cc b/src/workers/transaction-worker.cc index 76c67754..fc596dfb 100644 --- a/src/workers/transaction-worker.cc +++ b/src/workers/transaction-worker.cc @@ -1,15 +1,17 @@ +#include #include #include #include "transaction-worker.h" +#include "query-worker.h" #include "../objects/database/database.h" #include "../objects/transaction/transaction.h" #include "../util/macros.h" TransactionWorker::TransactionWorker(Transaction* trans, Nan::Callback* cb) - : Nan::AsyncWorker(cb), trans(trans) {} + : QueryWorker(trans, cb) {} void TransactionWorker::Execute() { - sqlite3* db_handle = trans->db->write_handle; - TransactionHandles* t_handles = trans->db->t_handles; + sqlite3* db_handle = obj->db->write_handle; + TransactionHandles* t_handles = obj->db->t_handles; int status; LOCK_DB(db_handle); @@ -26,21 +28,21 @@ void TransactionWorker::Execute() { sqlite3_reset(t_handles->begin); // Execute statements - for (unsigned int i=0; ihandle_count; ++i) { - status = sqlite3_step(trans->handles[i]); + for (unsigned int i=0; ihandle_count; ++i) { + status = sqlite3_step(obj->handles[i]); if (status != SQLITE_DONE) { if (status != SQLITE_ROW) { SetErrorMessage(sqlite3_errmsg(db_handle)); } else { SetErrorMessage("Unexpected data returned by a write transaction."); } - sqlite3_reset(trans->handles[i]); + sqlite3_reset(obj->handles[i]); sqlite3_step(t_handles->rollback); sqlite3_reset(t_handles->rollback); UNLOCK_DB(db_handle); return; } - sqlite3_reset(trans->handles[i]); + sqlite3_reset(obj->handles[i]); } // Commit Transaction @@ -63,34 +65,9 @@ void TransactionWorker::Execute() { void TransactionWorker::HandleOKCallback() { Nan::HandleScope scope; - v8::Local obj = Nan::New(); - Nan::ForceSet(obj, Nan::New("changes").ToLocalChecked(), Nan::New((double)changes)); - Nan::ForceSet(obj, Nan::New("id").ToLocalChecked(), Nan::New((double)id)); + v8::Local object = Nan::New(); + Nan::ForceSet(object, Nan::New("changes").ToLocalChecked(), Nan::New((double)changes)); + Nan::ForceSet(object, Nan::New("id").ToLocalChecked(), Nan::New((double)id)); - // Resolve - FinishRequest(); - v8::Local args[2] = {Nan::Null(), obj}; - Nan::AsyncWorker::callback->Call(2, args); -} -void TransactionWorker::HandleErrorCallback() { - Nan::HandleScope scope; - CONCAT2(message, "SQLite: ", Nan::AsyncWorker::ErrorMessage()); - - // Reject - FinishRequest(); - v8::Local args[1] = {Nan::Error(message)}; - Nan::AsyncWorker::callback->Call(1, args); -} -void TransactionWorker::FinishRequest() { - trans->busy = false; - trans->db->requests -= 1; - trans->Unref(); - if (!trans->bound) { - for (unsigned int i=0; ihandle_count; ++i) { - sqlite3_clear_bindings(trans->handles[i]); - } - } - if (trans->db->state == DB_DONE && trans->db->requests == 0) { - trans->db->ActuallyClose(); - } + Resolve(object); } diff --git a/src/workers/transaction-worker.h b/src/workers/transaction-worker.h index e4b5be9b..4294a135 100644 --- a/src/workers/transaction-worker.h +++ b/src/workers/transaction-worker.h @@ -1,22 +1,19 @@ -#ifndef NODE_SQLITE3_PLUS_WORKER_TRANSACTION_H -#define NODE_SQLITE3_PLUS_WORKER_TRANSACTION_H +#ifndef NODE_SQLITE3_PLUS_WORKER_TRANSACTION_WORKER_H +#define NODE_SQLITE3_PLUS_WORKER_TRANSACTION_WORKER_H +#include #include #include +#include "query-worker.h" class Transaction; -class TransactionWorker : public Nan::AsyncWorker { +class TransactionWorker : public QueryWorker { public: TransactionWorker(Transaction*, Nan::Callback*); void Execute(); - void HandleErrorCallback(); void HandleOKCallback(); private: - void FinishRequest(); - - Transaction* const trans; - int changes; sqlite3_int64 id; };