Skip to content

Commit

Permalink
overhauled scope of module
Browse files Browse the repository at this point in the history
  • Loading branch information
JoshuaWise committed Sep 5, 2016
1 parent c273620 commit e512ea0
Show file tree
Hide file tree
Showing 40 changed files with 437 additions and 607 deletions.
5 changes: 3 additions & 2 deletions TODO
Original file line number Diff line number Diff line change
@@ -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

--------------------------------------------------------------------------------

Expand Down Expand Up @@ -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())

Expand Down
10 changes: 10 additions & 0 deletions binding.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion deps/sqlite3.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
'includes': [ 'common-sqlite.gypi' ],
'target_defaults': {
'default_configuration': 'Release',
'cflags':[
'cflags': [
'-std=c99'
],
'configurations': {
Expand Down
34 changes: 26 additions & 8 deletions src/objects/database/close.cc
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,39 @@ 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<Statement*, Statement::Compare>::iterator stmts_it = db->stmts.begin();
std::set<Statement*, Statement::Compare>::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<Transaction*, Transaction::Compare>::iterator transs_it = db->transs.begin();
std::set<Transaction*, Transaction::Compare>::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;
}

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));
}
}
10 changes: 7 additions & 3 deletions src/objects/database/create-transaction.cc
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,12 @@ NAN_METHOD(Database::CreateTransaction) {

CONSTRUCTING_PRIVILEGES = true;
v8::Local<v8::Function> cons = Nan::New<v8::Function>(Transaction::constructor);
v8::Local<v8::Object> transaction = cons->NewInstance(0, NULL);
Nan::MaybeLocal<v8::Object> maybeTransaction = Nan::NewInstance(cons);
CONSTRUCTING_PRIVILEGES = false;

if (maybeTransaction.IsEmpty()) {return;}
v8::Local<v8::Object> transaction = maybeTransaction.ToLocalChecked();

// Initializes C++ object properties.
Transaction* trans = Nan::ObjectWrap::Unwrap<Transaction>(transaction);
trans->db = db;
Expand Down Expand Up @@ -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);
}
44 changes: 22 additions & 22 deletions src/objects/database/database.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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::PropertyAttribute>(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"
Expand All @@ -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;
Expand All @@ -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;
}
17 changes: 8 additions & 9 deletions src/objects/database/database.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@
#define NODE_SQLITE3_PLUS_DATABASE_H

// Dependencies
#include <set>
#include <sqlite3.h>
#include <nan.h>
#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;
Expand All @@ -26,7 +26,7 @@ class Database : public Nan::ObjectWrap {
friend class CloseWorker;
friend class Statement;
friend class Transaction;
template <class T> friend class StatementWorker;
template <class OBJECT, class ASYNC> friend class QueryWorker;
friend class TransactionWorker;

private:
Expand All @@ -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;
Expand All @@ -45,12 +45,11 @@ class Database : public Nan::ObjectWrap {

// State
DB_STATE state;
unsigned int requests;
unsigned int workers;

// Associated Statements and Transactions
List<Statement> stmts;
List<Transaction> transs;
std::set<Statement*, Statement::Compare> stmts;
std::set<Transaction*, Transaction::Compare> transs;
};

#endif
38 changes: 19 additions & 19 deletions src/objects/database/prepare.cc
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,25 @@ NAN_METHOD(Database::Prepare) {

CONSTRUCTING_PRIVILEGES = true;
v8::Local<v8::Function> cons = Nan::New<v8::Function>(Statement::constructor);
v8::Local<v8::Object> statement = cons->NewInstance(0, NULL);
Nan::MaybeLocal<v8::Object> maybeStatement = Nan::NewInstance(cons);
CONSTRUCTING_PRIVILEGES = false;

TRIM_STRING(source);
Nan::Utf8String utf8(source);
if (maybeStatement.IsEmpty()) {return;}
v8::Local<v8::Object> statement = maybeStatement.ToLocalChecked();

// Initializes object properties.
// Initializes c++ object properties.
Statement* stmt = Nan::ObjectWrap::Unwrap<Statement>(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) {
Expand All @@ -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), ").");
Expand All @@ -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);
}
7 changes: 3 additions & 4 deletions src/objects/statement/all.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
15 changes: 1 addition & 14 deletions src/objects/statement/bind.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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; i<len; ++i) {
sqlite3_stmt* handle = stmt->handles->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());
Expand Down
5 changes: 5 additions & 0 deletions src/objects/statement/busy.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// get .busy -> boolean

NAN_GETTER(Statement::Busy) {
info.GetReturnValue().Set(Nan::ObjectWrap::Unwrap<Statement>(info.This())->busy);
}
Loading

0 comments on commit e512ea0

Please sign in to comment.