diff --git a/.travis.yml b/.travis.yml index f7896cb3..83350efe 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,4 +12,6 @@ addons: - gcc-4.9 - g++-4.9 before_install: -- export CC="gcc-4.9" CXX="g++-4.9" \ No newline at end of file +- export CC="gcc-4.9" CXX="g++-4.9" +before_script: +- node-gyp rebuild --debug \ No newline at end of file diff --git a/TODO b/TODO index 30c07879..c524dcc5 100644 --- a/TODO +++ b/TODO @@ -1,10 +1,14 @@ ---------- Version 1 ---------- -write tests +should a new HandleScope be inside the .each() loop? + +re-write benchmarks for sync + +re-write readme for sync +re-write tests for sync + +write tests compare tests to node-sqlite3 ---------- Version 2 ---------- use special objects to deal with 64bit integers - ----------- Backlog ---------- -Make sure that Promise.promisify(fn, {deoptimize: true}) does not reduce performance too much for situations with ~6 arguments diff --git a/binding.gyp b/binding.gyp index 20851e46..83d7ef78 100644 --- a/binding.gyp +++ b/binding.gyp @@ -20,14 +20,8 @@ "src/objects/database/database.cc", "src/objects/statement/statement.cc", "src/objects/transaction/transaction.cc", - "src/workers/database-workers/open.cc", - "src/workers/database-workers/close.cc", - "src/workers/database-workers/checkpoint.cc", - "src/workers/statement-workers/run.cc", - "src/workers/statement-workers/get.cc", - "src/workers/statement-workers/all.cc", - "src/workers/statement-workers/each.cc", - "src/workers/transaction-worker.cc", + "src/workers/open.cc", + "src/workers/close.cc", "src/binder/binder.cc", "src/multi-binder/multi-binder.cc", "src/better_sqlite3.cc" diff --git a/deps/sqlite3.gyp b/deps/sqlite3.gyp index 286986d9..90b22376 100755 --- a/deps/sqlite3.gyp +++ b/deps/sqlite3.gyp @@ -82,7 +82,7 @@ ], 'defines': [ '_REENTRANT=1', - 'SQLITE_THREADSAFE=1', + 'SQLITE_THREADSAFE=2', 'SQLITE_ENABLE_FTS5', 'SQLITE_ENABLE_JSON1', 'SQLITE_ENABLE_RTREE', diff --git a/lib/database.js b/lib/database.js index cb3a456a..e6130a94 100644 --- a/lib/database.js +++ b/lib/database.js @@ -32,7 +32,7 @@ function Database(filenameGiven, options) { .replace(/\?/g, '%3f') .replace(/\/\/+/g, '/') .replace(/#/g, '%23') - + '?mode=memory'; + + '?mode=memory&cache=shared'; } return new CPPDatabase(filename, filenameGiven, NullFactory); diff --git a/src/binder/bind-buffer.cc b/src/binder/bind-buffer.cc index b1cfa9fe..b74a71e5 100644 --- a/src/binder/bind-buffer.cc +++ b/src/binder/bind-buffer.cc @@ -5,14 +5,7 @@ void Binder::BindBuffer(v8::Local value, int index) { if (!index) {index = NextAnonIndex();} - int status; - size_t length = node::Buffer::Length(value); - if (length > 1024) { - persistent->Set((uint32_t)index, value); - status = sqlite3_bind_blob(handle, index, (void*)node::Buffer::Data(value), length, SQLITE_STATIC); - } else { - status = sqlite3_bind_blob(handle, index, (void*)node::Buffer::Data(value), length, SQLITE_TRANSIENT); - } + int status = sqlite3_bind_blob(handle, index, (void*)node::Buffer::Data(value), node::Buffer::Length(value), bind_type); SetBindingError(status); } diff --git a/src/binder/bind-string.cc b/src/binder/bind-string.cc index f80fc137..92104c1d 100644 --- a/src/binder/bind-string.cc +++ b/src/binder/bind-string.cc @@ -5,14 +5,8 @@ void Binder::BindString(v8::Local value, int index) { if (!index) {index = NextAnonIndex();} - int status; v8::String::Value utf16(value); - if (utf16.length() > 1024) { - persistent->Set((uint32_t)index, value); - status = sqlite3_bind_text16(handle, index, *utf16, utf16.length() * sizeof (uint16_t), SQLITE_STATIC); - } else { - status = sqlite3_bind_text16(handle, index, *utf16, utf16.length() * sizeof (uint16_t), SQLITE_TRANSIENT); - } + int status = sqlite3_bind_text16(handle, index, *utf16, utf16.length() * sizeof (uint16_t), bind_type); SetBindingError(status); } diff --git a/src/binder/binder.cc b/src/binder/binder.cc index 97a98b7c..22288fac 100644 --- a/src/binder/binder.cc +++ b/src/binder/binder.cc @@ -23,14 +23,14 @@ #include "bind-object.cc" #include "bind.cc" -Binder::Binder(sqlite3_stmt* handle, v8::Local persistent) +Binder::Binder(sqlite3_stmt* handle, sqlite3_destructor_type bind_type) : handle(handle) , param_count(sqlite3_bind_parameter_count(handle)) , anon_index(0) , error(NULL) , error_extra(NULL) , error_full(NULL) - , persistent(persistent) {} + , bind_type(bind_type) {} Binder::~Binder() { delete[] error_extra; diff --git a/src/binder/binder.h b/src/binder/binder.h index 984063ae..038bfb59 100644 --- a/src/binder/binder.h +++ b/src/binder/binder.h @@ -7,7 +7,7 @@ class Query; class Binder { public: - Binder(sqlite3_stmt*, v8::Local); + Binder(sqlite3_stmt*, sqlite3_destructor_type); ~Binder(); virtual void Bind(Nan::NAN_METHOD_ARGS_TYPE, int, Query*); const char* GetError(); @@ -36,7 +36,7 @@ class Binder { char* error_extra; const char* error_full; - v8::Local persistent; + sqlite3_destructor_type bind_type; }; #endif \ No newline at end of file diff --git a/src/multi-binder/multi-binder.cc b/src/multi-binder/multi-binder.cc index 1f82f941..ad130d0e 100644 --- a/src/multi-binder/multi-binder.cc +++ b/src/multi-binder/multi-binder.cc @@ -10,8 +10,8 @@ #include "bind-object.cc" #include "bind.cc" -MultiBinder::MultiBinder(sqlite3_stmt** handles, unsigned int handle_count, v8::Local persistent) - : Binder(handles[0], persistent) +MultiBinder::MultiBinder(sqlite3_stmt** handles, unsigned int handle_count, sqlite3_destructor_type bind_type) + : Binder(handles[0], bind_type) , handles(handles) , handle_count(handle_count) , handle_index(0) diff --git a/src/multi-binder/multi-binder.h b/src/multi-binder/multi-binder.h index b63b65f8..de5a7f7b 100644 --- a/src/multi-binder/multi-binder.h +++ b/src/multi-binder/multi-binder.h @@ -8,7 +8,7 @@ class Query; class MultiBinder : public Binder { public: - MultiBinder(sqlite3_stmt**, unsigned int, v8::Local); + MultiBinder(sqlite3_stmt**, unsigned int, sqlite3_destructor_type); void Bind(Nan::NAN_METHOD_ARGS_TYPE, int, Query*); protected: diff --git a/src/objects/database/checkpoint.cc b/src/objects/database/checkpoint.cc index 53f807b5..174de103 100644 --- a/src/objects/database/checkpoint.cc +++ b/src/objects/database/checkpoint.cc @@ -1,27 +1,37 @@ // .checkpoint([boolean force], Function callback) -> this NAN_METHOD(Database::Checkpoint) { - bool force; - v8::Local func; - if (info.Length() >= 2) { - TRUTHINESS_OF_ARGUMENT(0, a); - REQUIRE_ARGUMENT_FUNCTION(1, b); - force = a; - func = b; - } else { - force = false; - REQUIRE_ARGUMENT_FUNCTION(0, a); - func = a; - } - + TRUTHINESS_OF_ARGUMENT(0, force); Database* db = Nan::ObjectWrap::Unwrap(info.This()); + if (db->in_each) { + return Nan::ThrowTypeError("This database connection is busy executing a query."); + } if (db->state != DB_READY) { return Nan::ThrowError("The database connection is not open."); } - if (db->workers++ == 0) {db->Ref();} - db->checkpoints += 1; - Nan::AsyncQueueWorker(new CheckpointWorker(db, force, new Nan::Callback(func))); + int total_frames; + int checkpointed_frames; + int status = sqlite3_wal_checkpoint_v2( + db->db_handle, + "main", + force ? SQLITE_CHECKPOINT_RESTART : SQLITE_CHECKPOINT_PASSIVE, + &total_frames, + &checkpointed_frames + ); - info.GetReturnValue().Set(info.This()); + if (status != SQLITE_OK) { + CONCAT2(message, "SQLite: ", sqlite3_errmsg(db->db_handle)); + return Nan::ThrowError(message); + } + + if (checkpointed_frames < 0 || total_frames < 0) { + info.GetReturnValue().Set(Nan::New(0)); + } else if (total_frames == 0) { + info.GetReturnValue().Set(Nan::New(1)); + } else { + info.GetReturnValue().Set(Nan::New( + (double)checkpointed_frames / (double)total_frames + )); + } } diff --git a/src/objects/database/close.cc b/src/objects/database/close.cc index 611c4b74..b2ac3e54 100644 --- a/src/objects/database/close.cc +++ b/src/objects/database/close.cc @@ -4,41 +4,17 @@ NAN_METHOD(Database::Close) { Database* db = Nan::ObjectWrap::Unwrap(info.This()); if (db->state != DB_DONE) { - if (db->workers++ == 0) {db->Ref();} - - // 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; - } + if (db->in_each) { + return Nan::ThrowTypeError("You cannot close a database while it is executing a query."); } - 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 MaybeClose() attempt, so the CloseWorker + if (db->workers++ == 0) {db->Ref();} + Nan::AsyncQueueWorker(new CloseWorker(db, db->state == DB_CONNECTING)); + + // This must be after the CloseWorker is created, so the CloseWorker // can detect if the database is still connecting. db->state = DB_DONE; } info.GetReturnValue().Set(info.This()); } - -void Database::MaybeClose() { - if (stmts.empty() && transs.empty() && checkpoints == 0) { - Nan::AsyncQueueWorker(new CloseWorker(this, state == DB_CONNECTING)); - } -} diff --git a/src/objects/database/create-statement.cc b/src/objects/database/create-statement.cc index b8236640..a87e1791 100644 --- a/src/objects/database/create-statement.cc +++ b/src/objects/database/create-statement.cc @@ -4,6 +4,9 @@ NAN_METHOD(Database::CreateStatement) { REQUIRE_ARGUMENT_STRING(0, source); Database* db = Nan::ObjectWrap::Unwrap(info.This()); + if (db->in_each) { + return Nan::ThrowTypeError("This database connection is busy executing a query."); + } if (db->state != DB_READY) { return Nan::ThrowError("The database connection is not open."); } @@ -20,20 +23,16 @@ NAN_METHOD(Database::CreateStatement) { // Digest the source string. v8::String::Value utf16(source); - int source_bytes_plus1 = utf16.length() * sizeof (uint16_t) + 1; // Builds actual sqlite3_stmt handle. const void* tail; - LOCK_DB(db->db_handle); - int status = sqlite3_prepare16(db->db_handle, *utf16, source_bytes_plus1, &stmt->st_handle, &tail); + int status = sqlite3_prepare16(db->db_handle, *utf16, utf16.length() * sizeof (uint16_t) + 1, &stmt->st_handle, &tail); // Validates the newly created statement. if (status != SQLITE_OK) { CONCAT3(message, "Failed to construct SQL statement (", sqlite3_errmsg(db->db_handle), ")."); - UNLOCK_DB(db->db_handle); return Nan::ThrowError(message); } - UNLOCK_DB(db->db_handle); if (stmt->st_handle == NULL) { return Nan::ThrowTypeError("The supplied SQL string contains no statements."); } @@ -51,6 +50,7 @@ NAN_METHOD(Database::CreateStatement) { } } Nan::ForceSet(statement, NEW_INTERNAL_STRING_FAST("source"), source, FROZEN); + statement->SetHiddenValue(Nan::New("_").ToLocalChecked(), info.This()); // Pushes onto stmts set. stmt->id = NEXT_STATEMENT_ID++; diff --git a/src/objects/database/create-transaction.cc b/src/objects/database/create-transaction.cc index 92bb7070..c6d28dcc 100644 --- a/src/objects/database/create-transaction.cc +++ b/src/objects/database/create-transaction.cc @@ -4,6 +4,9 @@ NAN_METHOD(Database::CreateTransaction) { REQUIRE_ARGUMENT_ARRAY(0, sources); Database* db = Nan::ObjectWrap::Unwrap(info.This()); + if (db->in_each) { + return Nan::ThrowTypeError("This database connection is busy executing a query."); + } if (db->state != DB_READY) { return Nan::ThrowError("The database connection is not open."); } @@ -59,16 +62,13 @@ NAN_METHOD(Database::CreateTransaction) { v8::String::Value utf16(source); const void* tail; - LOCK_DB(db->db_handle); int status = sqlite3_prepare16(db->db_handle, *utf16, utf16.length() * sizeof (uint16_t) + 1, &(trans->handles[i]), &tail); // Validates the newly created statement. if (status != SQLITE_OK) { CONCAT3(message, "Failed to construct SQL statement (", sqlite3_errmsg(db->db_handle), ")."); - UNLOCK_DB(db->db_handle); return Nan::ThrowError(message); } - UNLOCK_DB(db->db_handle); if (trans->handles[i] == NULL) { return Nan::ThrowTypeError("One of the supplied SQL strings contains no statements."); } @@ -80,6 +80,7 @@ NAN_METHOD(Database::CreateTransaction) { } } Nan::ForceSet(transaction, NEW_INTERNAL_STRING_FAST("source"), joinedSource, FROZEN); + transaction->SetHiddenValue(Nan::New("_").ToLocalChecked(), info.This()); // Pushes onto transs set. trans->id = NEXT_TRANSACTION_ID++; diff --git a/src/objects/database/database.cc b/src/objects/database/database.cc index 85273fd6..7b2c33dd 100644 --- a/src/objects/database/database.cc +++ b/src/objects/database/database.cc @@ -4,9 +4,8 @@ #include "database.h" #include "../statement/statement.h" #include "../transaction/transaction.h" -#include "../../workers/database-workers/open.h" -#include "../../workers/database-workers/close.h" -#include "../../workers/database-workers/checkpoint.h" +#include "../../workers/open.h" +#include "../../workers/close.h" #include "../../util/macros.h" #include "../../util/data.h" #include "../../util/list.h" @@ -30,7 +29,7 @@ Database::Database() : Nan::ObjectWrap(), t_handles(NULL), state(DB_CONNECTING), workers(0), - checkpoints(0) {} + in_each(false) {} Database::~Database() { state = DB_DONE; @@ -39,10 +38,7 @@ Database::~Database() { // 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(); + CloseChildHandles(); CloseHandles(); } @@ -72,3 +68,10 @@ int Database::CloseHandles() { db_handle = NULL; return status; } + +void Database::CloseChildHandles() { + for (Statement* stmt : stmts) {stmt->CloseHandles();} + for (Transaction* trans : transs) {trans->CloseHandles();} + stmts.clear(); + transs.clear(); +} diff --git a/src/objects/database/database.h b/src/objects/database/database.h index 9d121b36..257c96ed 100644 --- a/src/objects/database/database.h +++ b/src/objects/database/database.h @@ -26,13 +26,6 @@ class Database : public Nan::ObjectWrap { friend class Transaction; friend class OpenWorker; friend class CloseWorker; - friend class CheckpointWorker; - template friend class QueryWorker; - friend class GetWorker; - friend class AllWorker; - friend class EachWorker; - friend class RunWorker; - friend class TransactionWorker; private: static NAN_METHOD(New); @@ -43,7 +36,7 @@ class Database : public Nan::ObjectWrap { static NAN_METHOD(Pragma); static NAN_METHOD(Checkpoint); int CloseHandles(); - void MaybeClose(); + void CloseChildHandles(); // Sqlite3 interfacing sqlite3* db_handle; @@ -52,7 +45,7 @@ class Database : public Nan::ObjectWrap { // State DB_STATE state; unsigned int workers; - unsigned int checkpoints; + bool in_each; // Associated Statements and Transactions std::set stmts; diff --git a/src/objects/database/pragma.cc b/src/objects/database/pragma.cc index 93b3f8b8..715498a5 100644 --- a/src/objects/database/pragma.cc +++ b/src/objects/database/pragma.cc @@ -21,6 +21,9 @@ NAN_METHOD(Database::Pragma) { REQUIRE_ARGUMENT_STRING(0, source); TRUTHINESS_OF_ARGUMENT(1, simple_result); Database* db = Nan::ObjectWrap::Unwrap(info.This()); + if (db->in_each) { + return Nan::ThrowTypeError("This database connection is busy executing a query."); + } if (db->state != DB_READY) { return Nan::ThrowError("The database connection is not open."); } @@ -36,8 +39,7 @@ NAN_METHOD(Database::Pragma) { if (err != NULL) { CONCAT2(message, "SQLite: ", err); sqlite3_free(err); - Nan::ThrowError(message); - return; + return Nan::ThrowError(message); } sqlite3_free(err); diff --git a/src/objects/statement/all.cc b/src/objects/statement/all.cc index 063dd303..202e34c3 100644 --- a/src/objects/statement/all.cc +++ b/src/objects/statement/all.cc @@ -1,13 +1,43 @@ -// .all(Function callback) -> this +// .all(...any boundValues) -> array of rows or plucked columns NAN_METHOD(Statement::All) { Statement* stmt = Nan::ObjectWrap::Unwrap(info.This()); if (stmt->column_count == 0) { return Nan::ThrowTypeError("This statement is not read-only. Use run() instead."); } - REQUIRE_LAST_ARGUMENT_FUNCTION(func_index, func); - WORKER_START(stmt, statement); - AllWorker* worker = new AllWorker(stmt, new Nan::Callback(func)); - WORKER_BIND(stmt, worker, info, func_index, STATEMENT_BIND, statement); - WORKER_END(stmt, worker); + QUERY_START(stmt, statement, STATEMENT_BIND, info, info.Length()); + + unsigned int row_count = 0; + List rows; + + while (sqlite3_step(stmt->st_handle) == SQLITE_ROW) { + ++row_count; + rows.Add(new Data::Row(stmt->st_handle, stmt->column_count)); + } + if (sqlite3_reset(stmt->st_handle) == SQLITE_OK) { + v8::Local returned_array = Nan::New(row_count); + + // Get array of result rows or plucked columns. + if (row_count > 0) { + unsigned int i = 0; + if (stmt->state & PLUCK_COLUMN) { + // Fill array with plucked columns. + rows.Flush([&returned_array, &i] (Data::Row* row) { + Nan::Set(returned_array, i++, row->values[0]->ToJS()); + }); + } else { + // Fill array with row objects. + rows.Flush([&stmt, &returned_array, &i] (Data::Row* row) { + v8::Local object = Nan::New(); + for (int j=0; jcolumn_count; ++j) { + Nan::Set(object, NEW_INTERNAL_STRING16(sqlite3_column_name16(stmt->st_handle, j)), row->values[j]->ToJS()); + } + Nan::Set(returned_array, i++, object); + }); + } + } + QUERY_RETURN(stmt, STATEMENT_CLEAR_BINDINGS, returned_array); + } + + QUERY_THROW(stmt, STATEMENT_CLEAR_BINDINGS, sqlite3_errmsg(stmt->db->db_handle)); } diff --git a/src/objects/statement/bind.cc b/src/objects/statement/bind.cc index c7e14fd5..9436241c 100644 --- a/src/objects/statement/bind.cc +++ b/src/objects/statement/bind.cc @@ -12,9 +12,7 @@ NAN_METHOD(Statement::Bind) { return Nan::ThrowError("The associated database connection is closed."); } - v8::Local object = Nan::New(); - info.This()->SetHiddenValue(NEW_INTERNAL_STRING_FAST("_"), object); - STATEMENT_BIND(stmt, info, info.Length(), object); + STATEMENT_BIND(stmt, info, info.Length(), SQLITE_TRANSIENT); stmt->state |= BOUND; info.GetReturnValue().Set(info.This()); diff --git a/src/objects/statement/each.cc b/src/objects/statement/each.cc index c2d815a3..8d1e7977 100644 --- a/src/objects/statement/each.cc +++ b/src/objects/statement/each.cc @@ -1,23 +1,48 @@ -// .each(Function dataHandler, Function callback) -> this +// .each(...any boundValues, Function callback) -> undefined NAN_METHOD(Statement::Each) { Statement* stmt = Nan::ObjectWrap::Unwrap(info.This()); if (stmt->column_count == 0) { return Nan::ThrowTypeError("This statement is not read-only. Use run() instead."); } - REQUIRE_LAST_TWO_ARGUMENTS_FUNCTIONS(func_index, func1, func2) - WORKER_START(stmt, statement); - EachWorker* worker = new EachWorker(stmt, new Nan::Callback(func2), new Nan::Callback(func1)); + REQUIRE_LAST_ARGUMENT_FUNCTION(func_index, callback); + QUERY_START(stmt, statement, STATEMENT_BIND, info, func_index); + stmt->db->in_each = true; - if (!(stmt->state & PLUCK_COLUMN)) { - // Cache column names. - v8::Local columnNames = Nan::New(stmt->column_count); - for (int i=0; icolumn_count; ++i) { - Nan::Set(columnNames, (uint32_t)i, NEW_INTERNAL_STRING16(sqlite3_column_name16(stmt->st_handle, i))); + + // Retrieve and feed rows. + while (sqlite3_step(stmt->st_handle) == SQLITE_ROW) { + // Nan::HandleScope scope; + Data::Row row(stmt->st_handle, stmt->column_count); + v8::MaybeLocal callback_return_value; + + // Flush row to callback. + if (stmt->state & PLUCK_COLUMN) { + v8::Local args[1] = {row.values[0]->ToJS()}; + callback_return_value = callback->Call(Nan::Null(), 1, args); + } else { + v8::Local object = Nan::New(); + for (int i=0; ist_handle, i)), row.values[i]->ToJS()); + } + v8::Local args[1] = {object}; + callback_return_value = callback->Call(Nan::Null(), 1, args); + } + + // If an exception was thrown in the callback, clean up and stop. + if (callback_return_value.IsEmpty()) { + sqlite3_reset(stmt->st_handle); + stmt->db->in_each = false; + QUERY_CLEANUP(stmt, STATEMENT_CLEAR_BINDINGS); + return; } - worker->SaveToPersistent((uint32_t)0, columnNames); + + } + if (sqlite3_reset(stmt->st_handle) == SQLITE_OK) { + stmt->db->in_each = false; + QUERY_RETURN(stmt, STATEMENT_CLEAR_BINDINGS, Nan::Undefined()); } - WORKER_BIND(stmt, worker, info, func_index, STATEMENT_BIND, statement); - WORKER_END(stmt, worker); + stmt->db->in_each = false; + QUERY_THROW(stmt, STATEMENT_CLEAR_BINDINGS, sqlite3_errmsg(stmt->db->db_handle)); } diff --git a/src/objects/statement/get.cc b/src/objects/statement/get.cc index 61a7322b..c851da2e 100644 --- a/src/objects/statement/get.cc +++ b/src/objects/statement/get.cc @@ -1,13 +1,35 @@ -// .get(Function callback) -> this +// .get(...any boundValues) -> row or plucked column NAN_METHOD(Statement::Get) { Statement* stmt = Nan::ObjectWrap::Unwrap(info.This()); if (stmt->column_count == 0) { return Nan::ThrowTypeError("This statement is not read-only. Use run() instead."); } - REQUIRE_LAST_ARGUMENT_FUNCTION(func_index, func); - WORKER_START(stmt, statement); - GetWorker* worker = new GetWorker(stmt, new Nan::Callback(func)); - WORKER_BIND(stmt, worker, info, func_index, STATEMENT_BIND, statement); - WORKER_END(stmt, worker); + QUERY_START(stmt, statement, STATEMENT_BIND, info, info.Length()); + + int status = sqlite3_step(stmt->st_handle); + if (status == SQLITE_ROW) { + Data::Row row(stmt->st_handle, stmt->column_count); + v8::Local returned_value; + + // Get result row, or plucked column. + if (stmt->state & PLUCK_COLUMN) { + returned_value = row.values[0]->ToJS(); + } else { + returned_value = Nan::New(); + v8::Local returned_object = v8::Local::Cast(returned_value); + for (int i=0; ist_handle, i)), row.values[i]->ToJS()); + } + } + + sqlite3_reset(stmt->st_handle); + QUERY_RETURN(stmt, STATEMENT_CLEAR_BINDINGS, returned_value); + } else if (status == SQLITE_DONE) { + sqlite3_reset(stmt->st_handle); + QUERY_RETURN(stmt, STATEMENT_CLEAR_BINDINGS, Nan::Undefined()); + } + + sqlite3_reset(stmt->st_handle); + QUERY_THROW(stmt, STATEMENT_CLEAR_BINDINGS, sqlite3_errmsg(stmt->db->db_handle)); } diff --git a/src/objects/statement/getters.cc b/src/objects/statement/getters.cc deleted file mode 100644 index 8a7fa392..00000000 --- a/src/objects/statement/getters.cc +++ /dev/null @@ -1,9 +0,0 @@ -// get .busy -> boolean -NAN_GETTER(Statement::Busy) { - info.GetReturnValue().Set(Nan::ObjectWrap::Unwrap(info.This())->state & BUSY ? true : false); -} - -// get .readonly -> boolean -NAN_GETTER(Statement::Readonly) { - info.GetReturnValue().Set(Nan::ObjectWrap::Unwrap(info.This())->column_count != 0); -} diff --git a/src/objects/statement/run.cc b/src/objects/statement/run.cc index de6f42a8..b44516e7 100644 --- a/src/objects/statement/run.cc +++ b/src/objects/statement/run.cc @@ -1,13 +1,24 @@ -// .run(Function callback) -> this +// .run(...any boundValues) -> info object NAN_METHOD(Statement::Run) { Statement* stmt = Nan::ObjectWrap::Unwrap(info.This()); if (stmt->column_count != 0) { return Nan::ThrowTypeError("This statement is read-only. Use get(), all(), or each() instead."); } - REQUIRE_LAST_ARGUMENT_FUNCTION(func_index, func); - WORKER_START(stmt, statement); - RunWorker* worker = new RunWorker(stmt, new Nan::Callback(func)); - WORKER_BIND(stmt, worker, info, func_index, STATEMENT_BIND, statement); - WORKER_END(stmt, worker); + QUERY_START(stmt, statement, STATEMENT_BIND, info, info.Length()); + + sqlite3* db_handle = stmt->db->db_handle; + int total_changes_before = sqlite3_total_changes(db_handle); + + sqlite3_step(stmt->st_handle); + if (sqlite3_reset(stmt->st_handle) == SQLITE_OK) { + int changes = sqlite3_total_changes(db_handle) == total_changes_before ? 0 : sqlite3_changes(db_handle); + sqlite3_int64 id = sqlite3_last_insert_rowid(db_handle); + v8::Local returned_object = Nan::New(); + Nan::Set(returned_object, NEW_INTERNAL_STRING_FAST("changes"), Nan::New((double)changes)); + Nan::Set(returned_object, NEW_INTERNAL_STRING_FAST("lastInsertROWID"), Nan::New((double)id)); + QUERY_RETURN(stmt, STATEMENT_CLEAR_BINDINGS, returned_object); + } + + QUERY_THROW(stmt, STATEMENT_CLEAR_BINDINGS, sqlite3_errmsg(stmt->db->db_handle)); } diff --git a/src/objects/statement/statement.cc b/src/objects/statement/statement.cc index 46191f05..19529c37 100644 --- a/src/objects/statement/statement.cc +++ b/src/objects/statement/statement.cc @@ -4,15 +4,12 @@ #include "statement.h" #include "../query.h" #include "../database/database.h" -#include "../../workers/statement-workers/run.h" -#include "../../workers/statement-workers/get.h" -#include "../../workers/statement-workers/all.h" -#include "../../workers/statement-workers/each.h" #include "../../util/macros.h" +#include "../../util/data.h" +#include "../../util/list.h" #include "../../binder/binder.h" #include "new.cc" -#include "getters.cc" #include "bind.cc" #include "pluck.cc" #include "run.cc" @@ -36,7 +33,6 @@ void Statement::Init() { t->InstanceTemplate()->SetInternalFieldCount(1); t->SetClassName(Nan::New("Statement").ToLocalChecked()); - Nan::SetAccessor(t->InstanceTemplate(), Nan::New("busy").ToLocalChecked(), Busy); Nan::SetAccessor(t->InstanceTemplate(), Nan::New("readonly").ToLocalChecked(), Readonly); Nan::SetPrototypeMethod(t, "bind", Bind); Nan::SetPrototypeMethod(t, "pluck", Pluck); diff --git a/src/objects/statement/statement.h b/src/objects/statement/statement.h index 8f2ee161..43c1020e 100644 --- a/src/objects/statement/statement.h +++ b/src/objects/statement/statement.h @@ -3,7 +3,6 @@ // Dependencies #include -#include #include #include #include "../query.h" @@ -25,16 +24,10 @@ class Statement : public Nan::ObjectWrap, public Query { // Friends friend class Compare; friend class Database; - 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_GETTER(Busy); static NAN_GETTER(Readonly); static NAN_METHOD(Bind); static NAN_METHOD(Pluck); @@ -43,13 +36,6 @@ class Statement : public Nan::ObjectWrap, public Query { static NAN_METHOD(All); static NAN_METHOD(Each); 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 and state Database* db; diff --git a/src/objects/statement/util.cc b/src/objects/statement/util.cc index bbf7e6db..658a5d9d 100644 --- a/src/objects/statement/util.cc +++ b/src/objects/statement/util.cc @@ -3,21 +3,6 @@ bool Statement::Compare::operator() (const Statement* a, const Statement* b) { return a->id < b->id; } -// Closes all associated sqlite3 handles if the statement is not busy. -// Returns false if the transaction is busy. -bool Statement::CloseIfPossible() { - if (!(state & BUSY)) { - CloseHandles(); - return true; - } - return false; -} - -// Removes itself from its database's associated std::set. -void Statement::EraseFromSet() { - db->stmts.erase(this); -} - // Builds a JavaScript object that maps the statement's parameter names with // the parameter index of each one. After the first invocation, a cached version // is returned, rather than rebuilding it. @@ -38,3 +23,8 @@ v8::Local Statement::GetBindMap() { state |= HAS_BIND_MAP; return namedParams; } + +// get .readonly -> boolean +NAN_GETTER(Statement::Readonly) { + info.GetReturnValue().Set(Nan::ObjectWrap::Unwrap(info.This())->column_count != 0); +} diff --git a/src/objects/transaction/bind.cc b/src/objects/transaction/bind.cc index f3a5dc80..9c92fd72 100644 --- a/src/objects/transaction/bind.cc +++ b/src/objects/transaction/bind.cc @@ -12,9 +12,7 @@ NAN_METHOD(Transaction::Bind) { return Nan::ThrowError("The associated database connection is closed."); } - v8::Local object = Nan::New(); - info.This()->SetHiddenValue(NEW_INTERNAL_STRING_FAST("_"), object); - TRANSACTION_BIND(trans, info, info.Length(), object); + TRANSACTION_BIND(trans, info, info.Length(), SQLITE_TRANSIENT); trans->state |= BOUND; info.GetReturnValue().Set(info.This()); diff --git a/src/objects/transaction/busy.cc b/src/objects/transaction/busy.cc deleted file mode 100644 index 07cc9302..00000000 --- a/src/objects/transaction/busy.cc +++ /dev/null @@ -1,5 +0,0 @@ -// get .busy -> boolean - -NAN_GETTER(Transaction::Busy) { - info.GetReturnValue().Set(Nan::ObjectWrap::Unwrap(info.This())->state & BUSY ? true : false); -} diff --git a/src/objects/transaction/run.cc b/src/objects/transaction/run.cc index ace0791d..f811f946 100644 --- a/src/objects/transaction/run.cc +++ b/src/objects/transaction/run.cc @@ -1,10 +1,50 @@ -// .run(Function callback) -> this +// .run(...any boundValues) -> info object NAN_METHOD(Transaction::Run) { Transaction* trans = Nan::ObjectWrap::Unwrap(info.This()); - REQUIRE_LAST_ARGUMENT_FUNCTION(func_index, func); - WORKER_START(trans, transaction); - TransactionWorker* worker = new TransactionWorker(trans, new Nan::Callback(func)); - WORKER_BIND(trans, worker, info, func_index, TRANSACTION_BIND, transaction); - WORKER_END(trans, worker); + QUERY_START(trans, transaction, TRANSACTION_BIND, info, info.Length()); + + sqlite3* db_handle = trans->db->db_handle; + TransactionHandles* t_handles = trans->db->t_handles; + + // Begin Transaction + sqlite3_step(t_handles->begin); + if (sqlite3_reset(t_handles->begin) != SQLITE_OK) { + QUERY_THROW(trans, TRANSACTION_CLEAR_BINDINGS, sqlite3_errmsg(db_handle)); + } + + int changes = 0; + + // Execute statements + for (unsigned int i=0; ihandle_count; ++i) { + int total_changes_before = sqlite3_total_changes(db_handle); + + sqlite3_step(trans->handles[i]); + if (sqlite3_reset(trans->handles[i]) != SQLITE_OK) { + QUERY_THROW_STAY(trans, TRANSACTION_CLEAR_BINDINGS, sqlite3_errmsg(db_handle)); + sqlite3_step(t_handles->rollback); + sqlite3_reset(t_handles->rollback); + return; + } + + if (sqlite3_total_changes(db_handle) != total_changes_before) { + changes += sqlite3_changes(db_handle); + } + } + + // Commit Transaction + sqlite3_step(t_handles->commit); + if (sqlite3_reset(t_handles->commit) != SQLITE_OK) { + QUERY_THROW_STAY(trans, TRANSACTION_CLEAR_BINDINGS, sqlite3_errmsg(db_handle)); + sqlite3_step(t_handles->rollback); + sqlite3_reset(t_handles->rollback); + return; + } + + // Return info object. + sqlite3_int64 id = sqlite3_last_insert_rowid(db_handle); + v8::Local returned_object = Nan::New(); + Nan::Set(returned_object, NEW_INTERNAL_STRING_FAST("changes"), Nan::New((double)changes)); + Nan::Set(returned_object, NEW_INTERNAL_STRING_FAST("lastInsertROWID"), Nan::New((double)id)); + QUERY_RETURN(trans, TRANSACTION_CLEAR_BINDINGS, returned_object); } diff --git a/src/objects/transaction/transaction.cc b/src/objects/transaction/transaction.cc index b4e89274..d51d2507 100644 --- a/src/objects/transaction/transaction.cc +++ b/src/objects/transaction/transaction.cc @@ -4,11 +4,9 @@ #include "transaction.h" #include "../query.h" #include "../database/database.h" -#include "../../workers/transaction-worker.h" #include "../../multi-binder/multi-binder.h" #include "new.cc" -#include "busy.cc" #include "bind.cc" #include "run.cc" #include "util.cc" @@ -28,7 +26,6 @@ void Transaction::Init() { t->InstanceTemplate()->SetInternalFieldCount(1); t->SetClassName(Nan::New("Transaction").ToLocalChecked()); - Nan::SetAccessor(t->InstanceTemplate(), Nan::New("busy").ToLocalChecked(), Busy); Nan::SetPrototypeMethod(t, "bind", Bind); Nan::SetPrototypeMethod(t, "run", Run); diff --git a/src/objects/transaction/transaction.h b/src/objects/transaction/transaction.h index 6d78f463..ecfb888d 100644 --- a/src/objects/transaction/transaction.h +++ b/src/objects/transaction/transaction.h @@ -3,7 +3,6 @@ // Dependencies #include -#include #include #include #include "../query.h" @@ -24,25 +23,13 @@ class Transaction : public Nan::ObjectWrap, public Query { // Friends friend class Compare; friend class Database; - template friend class QueryWorker; - friend class TransactionWorker; private: static CONSTRUCTOR(constructor); static NAN_METHOD(New); - 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; iid < b->id; } -// Closes all associated sqlite3 handles if the transaction is not busy. -// Returns false if the transaction is busy. -bool Transaction::CloseIfPossible() { - if (!(state & BUSY)) { - CloseHandles(); - return true; - } - return false; -} - -// Removes itself from its database's associated std::set. -void Transaction::EraseFromSet() { - db->transs.erase(this); -} - // Builds a JavaScript array that has an object for each sqlite3_stmt handle // that has bind parameters. Each object maps the handle's parameter names // to their respective parameter index. After the first invocation, a cached diff --git a/src/util/macros.h b/src/util/macros.h index b7225512..7d999e55 100644 --- a/src/util/macros.h +++ b/src/util/macros.h @@ -9,11 +9,10 @@ #include "strlcpy.h" // Bitwise flags -#define BUSY 0x01 -#define CONFIG_LOCKED 0x02 -#define BOUND 0x04 -#define HAS_BIND_MAP 0x8 -#define PLUCK_COLUMN 0x10 +#define CONFIG_LOCKED 0x01 +#define BOUND 0x02 +#define HAS_BIND_MAP 0x4 +#define PLUCK_COLUMN 0x8 // Given a v8::String, returns a pointer to a heap-allocated C-String clone. inline char* C_STRING(v8::Local string) { @@ -69,7 +68,7 @@ inline bool IS_POSITIVE_INTEGER(double num) { // returns. This should ONLY be invoked in a libuv async callback. #define EMIT_EVENT(obj, argc, argv) \ GET_METHOD(_method, obj, "emit"); \ - Nan::MakeCallback(obj, _method, argc, argv); \ + Nan::MakeCallback(obj, _method, argc, argv); // If the argument of the given index is not a boolean, an error is thrown and // the caller returns. Otherwise, it is cast to a c++ bool and made available @@ -122,21 +121,6 @@ inline bool IS_POSITIVE_INTEGER(double num) { } \ v8::Local var = v8::Local::Cast(info[indexOut]); -// If the last two arguments are not both functions, an error is thrown and the -// caller returns. Otherwise, they are cast to v8::Functions and made available -// at the given variable names. -#define REQUIRE_LAST_TWO_ARGUMENTS_FUNCTIONS(indexOut, var1, var2) \ - int indexOut = info.Length() - 2; \ - if (indexOut < 0 || !info[indexOut]->IsFunction() \ - || !info[indexOut + 1]->IsFunction()) { \ - return Nan::ThrowTypeError( \ - "Expected the final two arguments to both be functions."); \ - } \ - v8::Local var1 = \ - v8::Local::Cast(info[indexOut]); \ - v8::Local var2 = \ - v8::Local::Cast(info[indexOut + 1]); \ - // If the argument of the given index is not an array, an error is thrown and // the caller returns. Otherwise, it is cast to a v8::Array and made available // at the given variable name. @@ -172,69 +156,76 @@ inline bool IS_POSITIVE_INTEGER(double num) { #define CONSTRUCTOR(name) \ Nan::Persistent name; +#define STATEMENT_CLEAR_BINDINGS(stmt) \ + sqlite3_clear_bindings(stmt->st_handle); + +#define TRANSACTION_CLEAR_BINDINGS(trans) \ + for (unsigned int i=0; ihandle_count; ++i) { \ + sqlite3_clear_bindings(trans->handles[i]); \ + } + // Common bind logic for statements. -#define STATEMENT_BIND(stmt, info, info_length, persistent) \ +#define STATEMENT_BIND(stmt, info, info_length, bind_type) \ if (info_length > 0) { \ - Binder _binder(stmt->st_handle, persistent); \ + Binder _binder(stmt->st_handle, bind_type); \ _binder.Bind(info, info_length, stmt); \ const char* _err = _binder.GetError(); \ if (_err) { \ - sqlite3_clear_bindings(stmt->st_handle); \ + STATEMENT_CLEAR_BINDINGS(stmt); \ return Nan::ThrowError(_err); \ } \ } // Common bind logic for transactions. -#define TRANSACTION_BIND(trans, info, info_length, persistent) \ +#define TRANSACTION_BIND(trans, info, info_length, bind_type) \ if (info_length > 0) { \ - MultiBinder _binder(trans->handles, trans->handle_count, persistent); \ + MultiBinder _binder(trans->handles, trans->handle_count, bind_type); \ _binder.Bind(info, info_length, trans); \ const char* _err = _binder.GetError(); \ if (_err) { \ - for (unsigned int i=0; ihandle_count; ++i) { \ - sqlite3_clear_bindings(trans->handles[i]); \ - } \ + TRANSACTION_CLEAR_BINDINGS(trans); \ return Nan::ThrowError(_err); \ } \ } -// The first macro-instruction for setting up an asynchronous SQLite request. -#define WORKER_START(obj, object_name) \ - if (obj->state & BUSY) { \ +// The macro-instruction that runs before an SQLite request. +#define QUERY_START(obj, object_name, BIND_MACRO, info, info_length) \ + if (obj->db->in_each) { \ return Nan::ThrowTypeError( \ - "This " #object_name " is mid-execution. You must wait for it to finish.");\ + "This database connection is busy executing a query."); \ } \ if (obj->db->state != DB_READY) { \ return Nan::ThrowError( \ "The associated database connection is closed."); \ } \ - if (!(obj->state & CONFIG_LOCKED)) {obj->state |= CONFIG_LOCKED;} - -// The second macro-instruction for setting up an asynchronous SQLite request. -#define WORKER_BIND(obj, worker, info, info_length, BIND_MACRO, object_name) \ + if (!(obj->state & CONFIG_LOCKED)) {obj->state |= CONFIG_LOCKED;} \ if (!(obj->state & BOUND)) { \ - BIND_MACRO(obj, info, info_length, worker->GetPersistentObject()); \ + BIND_MACRO(obj, info, info_length, SQLITE_STATIC); \ } else if (info_length > 0) { \ return Nan::ThrowTypeError( \ "This " #object_name " already has bound parameters."); \ } -// The third macro-instruction for setting up an asynchronous SQLite request. -// Returns the statement object making the request. -#define WORKER_END(obj, worker) \ - obj->state |= BUSY; \ - obj->Ref(); \ - if (obj->db->workers++ == 0) {obj->db->Ref();} \ - Nan::AsyncQueueWorker(worker); \ - info.GetReturnValue().Set(info.This()); - -// Enters the mutex for the sqlite3 database handle. -#define LOCK_DB(db_handle) \ - sqlite3_mutex_enter(sqlite3_db_mutex(db_handle)) - -// Exits the mutex for the sqlite3 database handle. -#define UNLOCK_DB(db_handle) \ - sqlite3_mutex_leave(sqlite3_db_mutex(db_handle)) +// The macro-instruction that MUST be run before returning from a query. +#define QUERY_CLEANUP(obj, UNBIND_MACRO) \ + if (!(obj->state & BOUND)) {UNBIND_MACRO(obj);} + +// Like QUERY_THROW, but does not return from the caller function. +#define QUERY_THROW_STAY(obj, UNBIND_MACRO, error_out) \ + CONCAT2(_error_message, "SQLite: ", error_out); \ + QUERY_CLEANUP(obj, UNBIND_MACRO); \ + Nan::ThrowError(_error_message); + +// The macro-instruction that runs after a failed SQLite request. +#define QUERY_THROW(obj, UNBIND_MACRO, error_out) \ + QUERY_THROW_STAY(obj, UNBIND_MACRO, error_out); \ + return; + +// The macro-instruction that runs after a successful SQLite request. +#define QUERY_RETURN(obj, UNBIND_MACRO, return_value) \ + QUERY_CLEANUP(obj, UNBIND_MACRO); \ + info.GetReturnValue().Set(return_value); \ + return; // Creates a new internalized string from UTF-8 data. #define NEW_INTERNAL_STRING8(string) \ diff --git a/src/workers/database-workers/close.cc b/src/workers/close.cc similarity index 85% rename from src/workers/database-workers/close.cc rename to src/workers/close.cc index 0e205b2a..b85d9ed5 100644 --- a/src/workers/database-workers/close.cc +++ b/src/workers/close.cc @@ -1,16 +1,15 @@ #include #include #include "close.h" -#include "../../objects/database/database.h" -#include "../../objects/statement/statement.h" -#include "../../objects/transaction/transaction.h" -#include "../../util/macros.h" +#include "../objects/database/database.h" +#include "../util/macros.h" CloseWorker::CloseWorker(Database* db, bool still_connecting) : Nan::AsyncWorker(NULL), db(db), still_connecting(still_connecting) {} void CloseWorker::Execute() { if (!still_connecting) { + db->CloseChildHandles(); if (db->CloseHandles() != SQLITE_OK) { SetErrorMessage("Failed to successfully close the database connection."); } diff --git a/src/workers/database-workers/close.h b/src/workers/close.h similarity index 100% rename from src/workers/database-workers/close.h rename to src/workers/close.h diff --git a/src/workers/database-workers/checkpoint.cc b/src/workers/database-workers/checkpoint.cc deleted file mode 100644 index 3d398178..00000000 --- a/src/workers/database-workers/checkpoint.cc +++ /dev/null @@ -1,63 +0,0 @@ -#include -#include -#include "checkpoint.h" -#include "../../objects/database/database.h" -#include "../../util/macros.h" - -CheckpointWorker::CheckpointWorker(Database* db, bool force, Nan::Callback* cb) : Nan::AsyncWorker(cb), - db(db), - force(force) {} -void CheckpointWorker::Execute() { - sqlite3* db_handle = db->db_handle; - LOCK_DB(db_handle); - - int total_frames; - int checkpointed_frames; - int status = sqlite3_wal_checkpoint_v2( - db_handle, - "main", - force ? SQLITE_CHECKPOINT_RESTART : SQLITE_CHECKPOINT_PASSIVE, - &total_frames, - &checkpointed_frames - ); - - if (status == SQLITE_OK) { - if (checkpointed_frames < 0 || total_frames < 0) { - fraction_checkpointed = 0; - } else if (total_frames == 0) { - fraction_checkpointed = 1; - } else { - fraction_checkpointed = (double)checkpointed_frames / (double)total_frames; - } - } else { - SetErrorMessage(sqlite3_errmsg(db_handle)); - } - - UNLOCK_DB(db_handle); -} -void CheckpointWorker::HandleOKCallback() { - Nan::HandleScope scope; - FinishRequest(); - - v8::Local args[2] = { - Nan::Null(), - Nan::New(fraction_checkpointed) - }; - Nan::AsyncWorker::callback->Call(2, args); -} -void CheckpointWorker::HandleErrorCallback() { - Nan::HandleScope scope; - FinishRequest(); - - CONCAT2(message, "SQLite: ", Nan::AsyncWorker::ErrorMessage()); - v8::Local args[1] = {Nan::Error(message)}; - Nan::AsyncWorker::callback->Call(1, args); -} -void CheckpointWorker::FinishRequest() { - if (--db->workers == 0) { - db->Unref(); - } - if (--db->checkpoints == 0 && db->state == DB_DONE) { - db->MaybeClose(); - } -} diff --git a/src/workers/database-workers/checkpoint.h b/src/workers/database-workers/checkpoint.h deleted file mode 100644 index 8aedf209..00000000 --- a/src/workers/database-workers/checkpoint.h +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef BETTER_SQLITE3_WORKER_CHECKPOINT_H -#define BETTER_SQLITE3_WORKER_CHECKPOINT_H - -#include -class Database; - -class CheckpointWorker : public Nan::AsyncWorker { - public: - CheckpointWorker(Database*, bool, Nan::Callback*); - void Execute(); - void HandleOKCallback(); - void HandleErrorCallback(); - - private: - void FinishRequest(); - - Database* const db; - bool force; - double fraction_checkpointed; -}; - -#endif \ No newline at end of file diff --git a/src/workers/database-workers/open.cc b/src/workers/open.cc similarity index 82% rename from src/workers/database-workers/open.cc rename to src/workers/open.cc index 9e76178f..3057dce9 100644 --- a/src/workers/database-workers/open.cc +++ b/src/workers/open.cc @@ -1,11 +1,10 @@ +#include #include #include #include "open.h" -#include "../../objects/database/database.h" -#include "../../util/macros.h" -#include "../../util/transaction-handles.h" - -const int WRITE_MODE = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX; +#include "../objects/database/database.h" +#include "../util/macros.h" +#include "../util/transaction-handles.h" OpenWorker::OpenWorker(Database* db, char* filename) : Nan::AsyncWorker(NULL), db(db), @@ -16,13 +15,14 @@ OpenWorker::~OpenWorker() { void OpenWorker::Execute() { int status; - status = sqlite3_open_v2(filename, &db->db_handle, WRITE_MODE, NULL); + status = sqlite3_open(filename, &db->db_handle); if (status != SQLITE_OK) { SetErrorMessage(sqlite3_errmsg(db->db_handle)); db->CloseHandles(); return; } + assert(sqlite3_db_mutex(db->db_handle) == NULL); sqlite3_busy_timeout(db->db_handle, 5000); db->t_handles = new TransactionHandles(db->db_handle, &status); diff --git a/src/workers/database-workers/open.h b/src/workers/open.h similarity index 100% rename from src/workers/database-workers/open.h rename to src/workers/open.h diff --git a/src/workers/query-worker.h b/src/workers/query-worker.h deleted file mode 100644 index 921d988e..00000000 --- a/src/workers/query-worker.h +++ /dev/null @@ -1,55 +0,0 @@ -#ifndef BETTER_SQLITE3_WORKER_QUERY_H -#define BETTER_SQLITE3_WORKER_QUERY_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)); - } - - inline v8::Local GetPersistentObject() { - return Nan::New(ASYNC::persistentHandle); - } - - 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->state &= ~BUSY; - obj->Unref(); - if (--obj->db->workers == 0) {obj->db->Unref();} - if (!(obj->state & 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 deleted file mode 100644 index 00dbb407..00000000 --- a/src/workers/statement-workers/all.cc +++ /dev/null @@ -1,52 +0,0 @@ -#include -#include -#include "all.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, Nan::Callback* cb) - : QueryWorker(stmt, cb), - row_count(0) {} -void AllWorker::Execute() { - sqlite3* db_handle = obj->db->db_handle; - LOCK_DB(db_handle); - - while (sqlite3_step(obj->st_handle) == SQLITE_ROW) { - ++row_count; - rows.Add(new Data::Row(obj->st_handle, obj->column_count)); - } - if (sqlite3_reset(obj->st_handle) != SQLITE_OK) { - SetErrorMessage(sqlite3_errmsg(db_handle)); - } - - UNLOCK_DB(db_handle); -} -void AllWorker::HandleOKCallback() { - Nan::HandleScope scope; - v8::Local arr = Nan::New(row_count); - - if (row_count > 0) { - unsigned int i = 0; - - if (obj->state & PLUCK_COLUMN) { - // Fill array with plucked columns. - rows.Flush([&arr, &i] (Data::Row* row) { - Nan::Set(arr, i++, row->values[0]->ToJS()); - }); - } else { - // Fill array with row objects. - rows.Flush([this, &arr, &i] (Data::Row* row) { - v8::Local object = Nan::New(); - for (int j=0; jcolumn_count; ++j) { - Nan::Set(object, NEW_INTERNAL_STRING16(sqlite3_column_name16(obj->st_handle, j)), row->values[j]->ToJS()); - } - Nan::Set(arr, i++, object); - }); - } - } - - Resolve(arr); -} diff --git a/src/workers/statement-workers/all.h b/src/workers/statement-workers/all.h deleted file mode 100644 index 45fda561..00000000 --- a/src/workers/statement-workers/all.h +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef BETTER_SQLITE3_WORKER_ALL_H -#define BETTER_SQLITE3_WORKER_ALL_H - -#include -#include -#include "../query-worker.h" -#include "../../util/data.h" -#include "../../util/list.h" -class Statement; - -class AllWorker : public QueryWorker { - public: - AllWorker(Statement*, Nan::Callback*); - void Execute(); - void HandleOKCallback(); - private: - unsigned int row_count; - List rows; -}; - -#endif \ No newline at end of file diff --git a/src/workers/statement-workers/each.cc b/src/workers/statement-workers/each.cc deleted file mode 100644 index 7c72687d..00000000 --- a/src/workers/statement-workers/each.cc +++ /dev/null @@ -1,87 +0,0 @@ -#include -#include -#include -#include "each.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, Nan::Callback* cb, Nan::Callback* progressCb) - : QueryWorker(stmt, cb), - data_mutex(NULL), - progressCallback(progressCb) {} -EachWorker::~EachWorker() { - sqlite3_mutex_free(data_mutex); - delete progressCallback; -} -void EachWorker::Execute(const Nan::AsyncProgressWorker::ExecutionProgress &progress) { - sqlite3* db_handle = obj->db->db_handle; - LOCK_DB(db_handle); - - // Allocate mutex. - data_mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST); - if (data_mutex == NULL) { - UNLOCK_DB(db_handle); - SetErrorMessage("Out of memory."); - return; - } - - // Retrieve and feed rows. - while (sqlite3_step(obj->st_handle) == SQLITE_ROW) { - sqlite3_mutex_enter(data_mutex); - rows.Add(new Data::Row(obj->st_handle, obj->column_count)); - sqlite3_mutex_leave(data_mutex); - progress.Signal(); - } - if (sqlite3_reset(obj->st_handle) != SQLITE_OK) { - SetErrorMessage(sqlite3_errmsg(db_handle)); - } - - UNLOCK_DB(db_handle); -} -void EachWorker::HandleProgressCallback(const char* not_used1, size_t not_used2) { - Nan::HandleScope scope; - - if (obj->state & PLUCK_COLUMN) { - // Flush rows of plucked columns. - - sqlite3_mutex_enter(data_mutex); - rows.Flush([this] (Data::Row* row) { - v8::Local args[1] = {row->values[0]->ToJS()}; - - sqlite3_mutex_leave(data_mutex); - progressCallback->Call(1, args); - sqlite3_mutex_enter(data_mutex); - - }); - sqlite3_mutex_leave(data_mutex); - - } else { - // Get cached column names. - v8::Local columnNames = v8::Local::Cast(Nan::AsyncProgressWorker::GetFromPersistent((uint32_t)0)); - - // Flush rows. - sqlite3_mutex_enter(data_mutex); - rows.Flush([this, &columnNames] (Data::Row* row) { - - v8::Local object = Nan::New(); - for (int i=0; icolumn_count; ++i) { - Nan::Set(object, v8::Local::Cast(Nan::Get(columnNames, (uint32_t)i).ToLocalChecked()), row->values[i]->ToJS()); - } - - sqlite3_mutex_leave(data_mutex); - v8::Local args[1] = {object}; - progressCallback->Call(1, args); - sqlite3_mutex_enter(data_mutex); - - }); - sqlite3_mutex_leave(data_mutex); - } -} -void EachWorker::HandleOKCallback() { - Nan::HandleScope scope; - HandleProgressCallback(NULL, 0); - Resolve(Nan::Undefined()); -} diff --git a/src/workers/statement-workers/each.h b/src/workers/statement-workers/each.h deleted file mode 100644 index de59fa96..00000000 --- a/src/workers/statement-workers/each.h +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef BETTER_SQLITE3_WORKER_EACH_H -#define BETTER_SQLITE3_WORKER_EACH_H - -#include -#include -#include -#include "../query-worker.h" -#include "../../util/data.h" -#include "../../util/list.h" -class Statement; - -class EachWorker : public QueryWorker { - public: - EachWorker(Statement*, Nan::Callback*, Nan::Callback*); - ~EachWorker(); - void Execute(const Nan::AsyncProgressWorker::ExecutionProgress&); - void HandleProgressCallback(const char*, size_t); - void HandleOKCallback(); - private: - sqlite3_mutex* data_mutex; - List rows; - Nan::Callback* progressCallback; -}; - -#endif \ No newline at end of file diff --git a/src/workers/statement-workers/get.cc b/src/workers/statement-workers/get.cc deleted file mode 100644 index a5cde0e3..00000000 --- a/src/workers/statement-workers/get.cc +++ /dev/null @@ -1,44 +0,0 @@ -#include -#include -#include "get.h" -#include "../query-worker.h" -#include "../../objects/statement/statement.h" -#include "../../util/macros.h" -#include "../../util/data.h" - -GetWorker::GetWorker(Statement* stmt, Nan::Callback* cb) - : QueryWorker(stmt, cb) {} -void GetWorker::Execute() { - sqlite3* db_handle = obj->db->db_handle; - LOCK_DB(db_handle); - - if (sqlite3_step(obj->st_handle) == SQLITE_ROW) { - row.Fill(obj->st_handle, obj->column_count); - } - if (sqlite3_reset(obj->st_handle) != SQLITE_OK) { - SetErrorMessage(sqlite3_errmsg(db_handle)); - } - - UNLOCK_DB(db_handle); -} -void GetWorker::HandleOKCallback() { - Nan::HandleScope scope; - - // No error, but no result found. - if (!row.column_count) { - return Resolve(Nan::Undefined()); - } - - // Resolve with the plucked column. - if (obj->state & PLUCK_COLUMN) { - return Resolve(row.values[0]->ToJS()); - } - - // Resolve with every column. - v8::Local object = Nan::New(); - for (int i=0; ist_handle, i)), row.values[i]->ToJS()); - } - - Resolve(object); -} diff --git a/src/workers/statement-workers/get.h b/src/workers/statement-workers/get.h deleted file mode 100644 index f2913cc0..00000000 --- a/src/workers/statement-workers/get.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef BETTER_SQLITE3_WORKER_GET_H -#define BETTER_SQLITE3_WORKER_GET_H - -#include -#include -#include "../query-worker.h" -#include "../../util/data.h" -class Statement; - -class GetWorker : public QueryWorker { - public: - GetWorker(Statement*, Nan::Callback*); - void Execute(); - void HandleOKCallback(); - private: - Data::Row row; -}; - -#endif \ No newline at end of file diff --git a/src/workers/statement-workers/run.cc b/src/workers/statement-workers/run.cc deleted file mode 100644 index a3bc8bfa..00000000 --- a/src/workers/statement-workers/run.cc +++ /dev/null @@ -1,34 +0,0 @@ -#include -#include -#include "run.h" -#include "../query-worker.h" -#include "../../objects/statement/statement.h" -#include "../../util/macros.h" - -RunWorker::RunWorker(Statement* stmt, Nan::Callback* cb) - : QueryWorker(stmt, cb) {} -void RunWorker::Execute() { - sqlite3* db_handle = obj->db->db_handle; - LOCK_DB(db_handle); - - int total_changes_before = sqlite3_total_changes(db_handle); - - sqlite3_step(obj->st_handle); - if (sqlite3_reset(obj->st_handle) == SQLITE_OK) { - changes = sqlite3_total_changes(db_handle) == total_changes_before ? 0 : sqlite3_changes(db_handle); - id = sqlite3_last_insert_rowid(db_handle); - } else { - SetErrorMessage(sqlite3_errmsg(db_handle)); - } - - UNLOCK_DB(db_handle); -} -void RunWorker::HandleOKCallback() { - Nan::HandleScope scope; - - v8::Local object = Nan::New(); - Nan::Set(object, NEW_INTERNAL_STRING_FAST("changes"), Nan::New((double)changes)); - Nan::Set(object, NEW_INTERNAL_STRING_FAST("lastInsertROWID"), Nan::New((double)id)); - - Resolve(object); -} diff --git a/src/workers/statement-workers/run.h b/src/workers/statement-workers/run.h deleted file mode 100644 index e4dbf737..00000000 --- a/src/workers/statement-workers/run.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef BETTER_SQLITE3_WORKER_RUN_H -#define BETTER_SQLITE3_WORKER_RUN_H - -#include -#include -#include "../query-worker.h" -class Statement; - -class RunWorker : public QueryWorker { - public: - RunWorker(Statement*, Nan::Callback*); - void Execute(); - void HandleOKCallback(); - private: - int changes; - sqlite3_int64 id; -}; - -#endif \ No newline at end of file diff --git a/src/workers/transaction-worker.cc b/src/workers/transaction-worker.cc deleted file mode 100644 index 48e029ce..00000000 --- a/src/workers/transaction-worker.cc +++ /dev/null @@ -1,67 +0,0 @@ -#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) - : QueryWorker(trans, cb) {} -void TransactionWorker::Execute() { - sqlite3* db_handle = obj->db->db_handle; - TransactionHandles* t_handles = obj->db->t_handles; - - LOCK_DB(db_handle); - - // Begin Transaction - sqlite3_step(t_handles->begin); - if (sqlite3_reset(t_handles->begin) != SQLITE_OK) { - SetErrorMessage(sqlite3_errmsg(db_handle)); - UNLOCK_DB(db_handle); - return; - } - - changes = 0; - - // Execute statements - for (unsigned int i=0; ihandle_count; ++i) { - int total_changes_before = sqlite3_total_changes(db_handle); - - sqlite3_step(obj->handles[i]); - if (sqlite3_reset(obj->handles[i]) != SQLITE_OK) { - SetErrorMessage(sqlite3_errmsg(db_handle)); - sqlite3_step(t_handles->rollback); - sqlite3_reset(t_handles->rollback); - UNLOCK_DB(db_handle); - return; - } - - if (sqlite3_total_changes(db_handle) != total_changes_before) { - changes += sqlite3_changes(db_handle); - } - } - - // Commit Transaction - sqlite3_step(t_handles->commit); - if (sqlite3_reset(t_handles->commit) != SQLITE_OK) { - SetErrorMessage(sqlite3_errmsg(db_handle)); - sqlite3_step(t_handles->rollback); - sqlite3_reset(t_handles->rollback); - UNLOCK_DB(db_handle); - return; - } - - id = sqlite3_last_insert_rowid(db_handle); - UNLOCK_DB(db_handle); -} -void TransactionWorker::HandleOKCallback() { - Nan::HandleScope scope; - - v8::Local object = Nan::New(); - Nan::Set(object, NEW_INTERNAL_STRING_FAST("changes"), Nan::New((double)changes)); - Nan::Set(object, NEW_INTERNAL_STRING_FAST("lastInsertROWID"), Nan::New((double)id)); - - Resolve(object); -} diff --git a/src/workers/transaction-worker.h b/src/workers/transaction-worker.h deleted file mode 100644 index d7b7c9c9..00000000 --- a/src/workers/transaction-worker.h +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef BETTER_SQLITE3_WORKER_TRANSACTION_H -#define BETTER_SQLITE3_WORKER_TRANSACTION_H - -#include -#include -#include -#include "query-worker.h" -class Transaction; - -class TransactionWorker : public QueryWorker { - public: - TransactionWorker(Transaction*, Nan::Callback*); - void Execute(); - void HandleOKCallback(); - - private: - int changes; - sqlite3_int64 id; -}; - -#endif \ No newline at end of file