Permalink
690 lines (550 sloc) 19.4 KB
#include <string.h>
#include "macros.h"
#include "database.h"
#include "statement.h"
using namespace node_sqlite3;
Nan::Persistent<FunctionTemplate> Database::constructor_template;
NAN_MODULE_INIT(Database::Init) {
Nan::HandleScope scope;
Local<FunctionTemplate> t = Nan::New<FunctionTemplate>(New);
t->InstanceTemplate()->SetInternalFieldCount(1);
t->SetClassName(Nan::New("Database").ToLocalChecked());
Nan::SetPrototypeMethod(t, "close", Close);
Nan::SetPrototypeMethod(t, "exec", Exec);
Nan::SetPrototypeMethod(t, "wait", Wait);
Nan::SetPrototypeMethod(t, "loadExtension", LoadExtension);
Nan::SetPrototypeMethod(t, "serialize", Serialize);
Nan::SetPrototypeMethod(t, "parallelize", Parallelize);
Nan::SetPrototypeMethod(t, "configure", Configure);
Nan::SetPrototypeMethod(t, "interrupt", Interrupt);
NODE_SET_GETTER(t, "open", OpenGetter);
constructor_template.Reset(t);
Nan::Set(target, Nan::New("Database").ToLocalChecked(),
Nan::GetFunction(t).ToLocalChecked());
}
void Database::Process() {
Nan::HandleScope scope;
if (!open && locked && !queue.empty()) {
EXCEPTION(Nan::New("Database handle is closed").ToLocalChecked(), SQLITE_MISUSE, exception);
Local<Value> argv[] = { exception };
bool called = false;
// Call all callbacks with the error object.
while (!queue.empty()) {
Call* call = queue.front();
Local<Function> cb = Nan::New(call->baton->callback);
if (!cb.IsEmpty() && cb->IsFunction()) {
TRY_CATCH_CALL(this->handle(), cb, 1, argv);
called = true;
}
queue.pop();
// We don't call the actual callback, so we have to make sure that
// the baton gets destroyed.
delete call->baton;
delete call;
}
// When we couldn't call a callback function, emit an error on the
// Database object.
if (!called) {
Local<Value> info[] = { Nan::New("error").ToLocalChecked(), exception };
EMIT_EVENT(handle(), 2, info);
}
return;
}
while (open && (!locked || pending == 0) && !queue.empty()) {
Call* call = queue.front();
if (call->exclusive && pending > 0) {
break;
}
queue.pop();
locked = call->exclusive;
call->callback(call->baton);
delete call;
if (locked) break;
}
}
void Database::Schedule(Work_Callback callback, Baton* baton, bool exclusive) {
Nan::HandleScope scope;
if (!open && locked) {
EXCEPTION(Nan::New("Database is closed").ToLocalChecked(), SQLITE_MISUSE, exception);
Local<Function> cb = Nan::New(baton->callback);
if (!cb.IsEmpty() && cb->IsFunction()) {
Local<Value> argv[] = { exception };
TRY_CATCH_CALL(handle(), cb, 1, argv);
}
else {
Local<Value> argv[] = { Nan::New("error").ToLocalChecked(), exception };
EMIT_EVENT(handle(), 2, argv);
}
return;
}
if (!open || ((locked || exclusive || serialize) && pending > 0)) {
queue.push(new Call(callback, baton, exclusive || serialize));
}
else {
locked = exclusive;
callback(baton);
}
}
NAN_METHOD(Database::New) {
if (!info.IsConstructCall()) {
return Nan::ThrowTypeError("Use the new operator to create new Database objects");
}
REQUIRE_ARGUMENT_STRING(0, filename);
int pos = 1;
int mode;
if (info.Length() >= pos && info[pos]->IsInt32()) {
mode = Nan::To<int>(info[pos++]).FromJust();
} else {
mode = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX;
}
Local<Function> callback;
if (info.Length() >= pos && info[pos]->IsFunction()) {
callback = Local<Function>::Cast(info[pos++]);
}
Database* db = new Database();
db->Wrap(info.This());
info.This()->ForceSet(Nan::New("filename").ToLocalChecked(), info[0].As<String>(), ReadOnly);
info.This()->ForceSet(Nan::New("mode").ToLocalChecked(), Nan::New(mode), ReadOnly);
// Start opening the database.
OpenBaton* baton = new OpenBaton(db, callback, *filename, mode);
Work_BeginOpen(baton);
info.GetReturnValue().Set(info.This());
}
void Database::Work_BeginOpen(Baton* baton) {
int status = uv_queue_work(uv_default_loop(),
&baton->request, Work_Open, (uv_after_work_cb)Work_AfterOpen);
assert(status == 0);
}
void Database::Work_Open(uv_work_t* req) {
OpenBaton* baton = static_cast<OpenBaton*>(req->data);
Database* db = baton->db;
baton->status = sqlite3_open_v2(
baton->filename.c_str(),
&db->_handle,
baton->mode,
NULL
);
if (baton->status != SQLITE_OK) {
baton->message = std::string(sqlite3_errmsg(db->_handle));
sqlite3_close(db->_handle);
db->_handle = NULL;
}
else {
// Set default database handle values.
sqlite3_busy_timeout(db->_handle, 1000);
}
}
void Database::Work_AfterOpen(uv_work_t* req) {
Nan::HandleScope scope;
OpenBaton* baton = static_cast<OpenBaton*>(req->data);
Database* db = baton->db;
Local<Value> argv[1];
if (baton->status != SQLITE_OK) {
EXCEPTION(Nan::New(baton->message.c_str()).ToLocalChecked(), baton->status, exception);
argv[0] = exception;
}
else {
db->open = true;
argv[0] = Nan::Null();
}
Local<Function> cb = Nan::New(baton->callback);
if (!cb.IsEmpty() && cb->IsFunction()) {
TRY_CATCH_CALL(db->handle(), cb, 1, argv);
}
else if (!db->open) {
Local<Value> info[] = { Nan::New("error").ToLocalChecked(), argv[0] };
EMIT_EVENT(db->handle(), 2, info);
}
if (db->open) {
Local<Value> info[] = { Nan::New("open").ToLocalChecked() };
EMIT_EVENT(db->handle(), 1, info);
db->Process();
}
delete baton;
}
NAN_GETTER(Database::OpenGetter) {
Database* db = Nan::ObjectWrap::Unwrap<Database>(info.This());
info.GetReturnValue().Set(db->open);
}
NAN_METHOD(Database::Close) {
Database* db = Nan::ObjectWrap::Unwrap<Database>(info.This());
OPTIONAL_ARGUMENT_FUNCTION(0, callback);
Baton* baton = new Baton(db, callback);
db->Schedule(Work_BeginClose, baton, true);
info.GetReturnValue().Set(info.This());
}
void Database::Work_BeginClose(Baton* baton) {
assert(baton->db->locked);
assert(baton->db->open);
assert(baton->db->_handle);
assert(baton->db->pending == 0);
baton->db->RemoveCallbacks();
baton->db->closing = true;
int status = uv_queue_work(uv_default_loop(),
&baton->request, Work_Close, (uv_after_work_cb)Work_AfterClose);
assert(status == 0);
}
void Database::Work_Close(uv_work_t* req) {
Baton* baton = static_cast<Baton*>(req->data);
Database* db = baton->db;
baton->status = sqlite3_close(db->_handle);
if (baton->status != SQLITE_OK) {
baton->message = std::string(sqlite3_errmsg(db->_handle));
}
else {
db->_handle = NULL;
}
}
void Database::Work_AfterClose(uv_work_t* req) {
Nan::HandleScope scope;
Baton* baton = static_cast<Baton*>(req->data);
Database* db = baton->db;
db->closing = false;
Local<Value> argv[1];
if (baton->status != SQLITE_OK) {
EXCEPTION(Nan::New(baton->message.c_str()).ToLocalChecked(), baton->status, exception);
argv[0] = exception;
}
else {
db->open = false;
// Leave db->locked to indicate that this db object has reached
// the end of its life.
argv[0] = Nan::Null();
}
Local<Function> cb = Nan::New(baton->callback);
// Fire callbacks.
if (!cb.IsEmpty() && cb->IsFunction()) {
TRY_CATCH_CALL(db->handle(), cb, 1, argv);
}
else if (db->open) {
Local<Value> info[] = { Nan::New("error").ToLocalChecked(), argv[0] };
EMIT_EVENT(db->handle(), 2, info);
}
if (!db->open) {
Local<Value> info[] = { Nan::New("close").ToLocalChecked(), argv[0] };
EMIT_EVENT(db->handle(), 1, info);
db->Process();
}
delete baton;
}
NAN_METHOD(Database::Serialize) {
Database* db = Nan::ObjectWrap::Unwrap<Database>(info.This());
OPTIONAL_ARGUMENT_FUNCTION(0, callback);
bool before = db->serialize;
db->serialize = true;
if (!callback.IsEmpty() && callback->IsFunction()) {
TRY_CATCH_CALL(info.This(), callback, 0, NULL);
db->serialize = before;
}
db->Process();
info.GetReturnValue().Set(info.This());
}
NAN_METHOD(Database::Parallelize) {
Database* db = Nan::ObjectWrap::Unwrap<Database>(info.This());
OPTIONAL_ARGUMENT_FUNCTION(0, callback);
bool before = db->serialize;
db->serialize = false;
if (!callback.IsEmpty() && callback->IsFunction()) {
TRY_CATCH_CALL(info.This(), callback, 0, NULL);
db->serialize = before;
}
db->Process();
info.GetReturnValue().Set(info.This());
}
NAN_METHOD(Database::Configure) {
Database* db = Nan::ObjectWrap::Unwrap<Database>(info.This());
REQUIRE_ARGUMENTS(2);
if (Nan::Equals(info[0], Nan::New("trace").ToLocalChecked()).FromJust()) {
Local<Function> handle;
Baton* baton = new Baton(db, handle);
db->Schedule(RegisterTraceCallback, baton);
}
else if (Nan::Equals(info[0], Nan::New("profile").ToLocalChecked()).FromJust()) {
Local<Function> handle;
Baton* baton = new Baton(db, handle);
db->Schedule(RegisterProfileCallback, baton);
}
else if (Nan::Equals(info[0], Nan::New("busyTimeout").ToLocalChecked()).FromJust()) {
if (!info[1]->IsInt32()) {
return Nan::ThrowTypeError("Value must be an integer");
}
Local<Function> handle;
Baton* baton = new Baton(db, handle);
baton->status = Nan::To<int>(info[1]).FromJust();
db->Schedule(SetBusyTimeout, baton);
}
else {
return Nan::ThrowError(Exception::Error(String::Concat(
Nan::To<String>(info[0]).ToLocalChecked(),
Nan::New(" is not a valid configuration option").ToLocalChecked()
)));
}
db->Process();
info.GetReturnValue().Set(info.This());
}
NAN_METHOD(Database::Interrupt) {
Database* db = Nan::ObjectWrap::Unwrap<Database>(info.This());
if (!db->open) {
return Nan::ThrowError("Database is not open");
}
if (db->closing) {
return Nan::ThrowError("Database is closing");
}
sqlite3_interrupt(db->_handle);
info.GetReturnValue().Set(info.This());
}
void Database::SetBusyTimeout(Baton* baton) {
assert(baton->db->open);
assert(baton->db->_handle);
// Abuse the status field for passing the timeout.
sqlite3_busy_timeout(baton->db->_handle, baton->status);
delete baton;
}
void Database::RegisterTraceCallback(Baton* baton) {
assert(baton->db->open);
assert(baton->db->_handle);
Database* db = baton->db;
if (db->debug_trace == NULL) {
// Add it.
db->debug_trace = new AsyncTrace(db, TraceCallback);
sqlite3_trace(db->_handle, TraceCallback, db);
}
else {
// Remove it.
sqlite3_trace(db->_handle, NULL, NULL);
db->debug_trace->finish();
db->debug_trace = NULL;
}
delete baton;
}
void Database::TraceCallback(void* db, const char* sql) {
// Note: This function is called in the thread pool.
// Note: Some queries, such as "EXPLAIN" queries, are not sent through this.
static_cast<Database*>(db)->debug_trace->send(new std::string(sql));
}
void Database::TraceCallback(Database* db, std::string* sql) {
// Note: This function is called in the main V8 thread.
Nan::HandleScope scope;
Local<Value> argv[] = {
Nan::New("trace").ToLocalChecked(),
Nan::New(sql->c_str()).ToLocalChecked()
};
EMIT_EVENT(db->handle(), 2, argv);
delete sql;
}
void Database::RegisterProfileCallback(Baton* baton) {
assert(baton->db->open);
assert(baton->db->_handle);
Database* db = baton->db;
if (db->debug_profile == NULL) {
// Add it.
db->debug_profile = new AsyncProfile(db, ProfileCallback);
sqlite3_profile(db->_handle, ProfileCallback, db);
}
else {
// Remove it.
sqlite3_profile(db->_handle, NULL, NULL);
db->debug_profile->finish();
db->debug_profile = NULL;
}
delete baton;
}
void Database::ProfileCallback(void* db, const char* sql, sqlite3_uint64 nsecs) {
// Note: This function is called in the thread pool.
// Note: Some queries, such as "EXPLAIN" queries, are not sent through this.
ProfileInfo* info = new ProfileInfo();
info->sql = std::string(sql);
info->nsecs = nsecs;
static_cast<Database*>(db)->debug_profile->send(info);
}
void Database::ProfileCallback(Database *db, ProfileInfo* info) {
Nan::HandleScope scope;
Local<Value> argv[] = {
Nan::New("profile").ToLocalChecked(),
Nan::New(info->sql.c_str()).ToLocalChecked(),
Nan::New<Number>((double)info->nsecs / 1000000.0)
};
EMIT_EVENT(db->handle(), 3, argv);
delete info;
}
void Database::RegisterUpdateCallback(Baton* baton) {
assert(baton->db->open);
assert(baton->db->_handle);
Database* db = baton->db;
if (db->update_event == NULL) {
// Add it.
db->update_event = new AsyncUpdate(db, UpdateCallback);
sqlite3_update_hook(db->_handle, UpdateCallback, db);
}
else {
// Remove it.
sqlite3_update_hook(db->_handle, NULL, NULL);
db->update_event->finish();
db->update_event = NULL;
}
delete baton;
}
void Database::UpdateCallback(void* db, int type, const char* database,
const char* table, sqlite3_int64 rowid) {
// Note: This function is called in the thread pool.
// Note: Some queries, such as "EXPLAIN" queries, are not sent through this.
UpdateInfo* info = new UpdateInfo();
info->type = type;
info->database = std::string(database);
info->table = std::string(table);
info->rowid = rowid;
static_cast<Database*>(db)->update_event->send(info);
}
void Database::UpdateCallback(Database *db, UpdateInfo* info) {
Nan::HandleScope scope;
Local<Value> argv[] = {
Nan::New(sqlite_authorizer_string(info->type)).ToLocalChecked(),
Nan::New(info->database.c_str()).ToLocalChecked(),
Nan::New(info->table.c_str()).ToLocalChecked(),
Nan::New<Number>(info->rowid),
};
EMIT_EVENT(db->handle(), 4, argv);
delete info;
}
NAN_METHOD(Database::Exec) {
Database* db = Nan::ObjectWrap::Unwrap<Database>(info.This());
REQUIRE_ARGUMENT_STRING(0, sql);
OPTIONAL_ARGUMENT_FUNCTION(1, callback);
Baton* baton = new ExecBaton(db, callback, *sql);
db->Schedule(Work_BeginExec, baton, true);
info.GetReturnValue().Set(info.This());
}
void Database::Work_BeginExec(Baton* baton) {
assert(baton->db->locked);
assert(baton->db->open);
assert(baton->db->_handle);
assert(baton->db->pending == 0);
int status = uv_queue_work(uv_default_loop(),
&baton->request, Work_Exec, (uv_after_work_cb)Work_AfterExec);
assert(status == 0);
}
void Database::Work_Exec(uv_work_t* req) {
ExecBaton* baton = static_cast<ExecBaton*>(req->data);
char* message = NULL;
baton->status = sqlite3_exec(
baton->db->_handle,
baton->sql.c_str(),
NULL,
NULL,
&message
);
if (baton->status != SQLITE_OK && message != NULL) {
baton->message = std::string(message);
sqlite3_free(message);
}
}
void Database::Work_AfterExec(uv_work_t* req) {
Nan::HandleScope scope;
ExecBaton* baton = static_cast<ExecBaton*>(req->data);
Database* db = baton->db;
Local<Function> cb = Nan::New(baton->callback);
if (baton->status != SQLITE_OK) {
EXCEPTION(Nan::New(baton->message.c_str()).ToLocalChecked(), baton->status, exception);
if (!cb.IsEmpty() && cb->IsFunction()) {
Local<Value> argv[] = { exception };
TRY_CATCH_CALL(db->handle(), cb, 1, argv);
}
else {
Local<Value> info[] = { Nan::New("error").ToLocalChecked(), exception };
EMIT_EVENT(db->handle(), 2, info);
}
}
else if (!cb.IsEmpty() && cb->IsFunction()) {
Local<Value> argv[] = { Nan::Null() };
TRY_CATCH_CALL(db->handle(), cb, 1, argv);
}
db->Process();
delete baton;
}
NAN_METHOD(Database::Wait) {
Database* db = Nan::ObjectWrap::Unwrap<Database>(info.This());
OPTIONAL_ARGUMENT_FUNCTION(0, callback);
Baton* baton = new Baton(db, callback);
db->Schedule(Work_Wait, baton, true);
info.GetReturnValue().Set(info.This());
}
void Database::Work_Wait(Baton* baton) {
Nan::HandleScope scope;
assert(baton->db->locked);
assert(baton->db->open);
assert(baton->db->_handle);
assert(baton->db->pending == 0);
Local<Function> cb = Nan::New(baton->callback);
if (!cb.IsEmpty() && cb->IsFunction()) {
Local<Value> argv[] = { Nan::Null() };
TRY_CATCH_CALL(baton->db->handle(), cb, 1, argv);
}
baton->db->Process();
delete baton;
}
NAN_METHOD(Database::LoadExtension) {
Database* db = Nan::ObjectWrap::Unwrap<Database>(info.This());
REQUIRE_ARGUMENT_STRING(0, filename);
OPTIONAL_ARGUMENT_FUNCTION(1, callback);
Baton* baton = new LoadExtensionBaton(db, callback, *filename);
db->Schedule(Work_BeginLoadExtension, baton, true);
info.GetReturnValue().Set(info.This());
}
void Database::Work_BeginLoadExtension(Baton* baton) {
assert(baton->db->locked);
assert(baton->db->open);
assert(baton->db->_handle);
assert(baton->db->pending == 0);
int status = uv_queue_work(uv_default_loop(),
&baton->request, Work_LoadExtension, reinterpret_cast<uv_after_work_cb>(Work_AfterLoadExtension));
assert(status == 0);
}
void Database::Work_LoadExtension(uv_work_t* req) {
LoadExtensionBaton* baton = static_cast<LoadExtensionBaton*>(req->data);
sqlite3_enable_load_extension(baton->db->_handle, 1);
char* message = NULL;
baton->status = sqlite3_load_extension(
baton->db->_handle,
baton->filename.c_str(),
0,
&message
);
sqlite3_enable_load_extension(baton->db->_handle, 0);
if (baton->status != SQLITE_OK && message != NULL) {
baton->message = std::string(message);
sqlite3_free(message);
}
}
void Database::Work_AfterLoadExtension(uv_work_t* req) {
Nan::HandleScope scope;
LoadExtensionBaton* baton = static_cast<LoadExtensionBaton*>(req->data);
Database* db = baton->db;
Local<Function> cb = Nan::New(baton->callback);
if (baton->status != SQLITE_OK) {
EXCEPTION(Nan::New(baton->message.c_str()).ToLocalChecked(), baton->status, exception);
if (!cb.IsEmpty() && cb->IsFunction()) {
Local<Value> argv[] = { exception };
TRY_CATCH_CALL(db->handle(), cb, 1, argv);
}
else {
Local<Value> info[] = { Nan::New("error").ToLocalChecked(), exception };
EMIT_EVENT(db->handle(), 2, info);
}
}
else if (!cb.IsEmpty() && cb->IsFunction()) {
Local<Value> argv[] = { Nan::Null() };
TRY_CATCH_CALL(db->handle(), cb, 1, argv);
}
db->Process();
delete baton;
}
void Database::RemoveCallbacks() {
if (debug_trace) {
debug_trace->finish();
debug_trace = NULL;
}
if (debug_profile) {
debug_profile->finish();
debug_profile = NULL;
}
}