Commit db72749d by Konstantin Käfer

unify indentation and rework database opening, closing and garbage collection

parent 493050a9
...@@ -16,6 +16,9 @@ ...@@ -16,6 +16,9 @@
var sqlite3 = module.exports = exports = require('./sqlite3_bindings'); var sqlite3 = module.exports = exports = require('./sqlite3_bindings');
var sys = require("sys"); var sys = require("sys");
/*
sqlite3.Database.prototype.query = function(sql, bindings, rowCallback) { sqlite3.Database.prototype.query = function(sql, bindings, rowCallback) {
var self = this; var self = this;
...@@ -68,7 +71,7 @@ function _doStep(db, statement, rowCallback) { ...@@ -68,7 +71,7 @@ function _doStep(db, statement, rowCallback) {
// Execute a single SQL query with the given optional parameters. Calls // Execute a single SQL query with the given optional parameters. Calls
// `callback` with all rows or an error on query completion. // `callback` with all rows or an error on query completion.
sqlite3.Database.prototype.execute = function (sql /* , bindings, callback */) { sqlite3.Database.prototype.execute = function (sql /* , bindings, callback ) {
var self = this; var self = this;
var bindings, callback; var bindings, callback;
var n = arguments.length; var n = arguments.length;
...@@ -248,3 +251,4 @@ sqlite3.sanitizeError = function(err, data) { ...@@ -248,3 +251,4 @@ sqlite3.sanitizeError = function(err, data) {
'" with values ' + JSON.stringify(data, false, 4); '" with values ' + JSON.stringify(data, false, 4);
return err; return err;
}; };
*/
\ No newline at end of file
// Copyright (c) 2010, Orlando Vazquez <ovazquez@gmail.com> // Copyright (c) 2010, Orlando Vazquez <ovazquez@gmail.com>
// //
// Permission to use, copy, modify, and/or distribute this software for any // Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above // purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies. // copyright notice and this permission notice appear in all copies.
// //
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
...@@ -28,182 +28,202 @@ using namespace node; ...@@ -28,182 +28,202 @@ using namespace node;
Persistent<FunctionTemplate> Database::constructor_template; Persistent<FunctionTemplate> Database::constructor_template;
void Database::Init(v8::Handle<Object> target) { void Database::Init(v8::Handle<Object> target) {
HandleScope scope; HandleScope scope;
Local<FunctionTemplate> t = FunctionTemplate::New(New); Local<FunctionTemplate> t = FunctionTemplate::New(New);
constructor_template = Persistent<FunctionTemplate>::New(t); constructor_template = Persistent<FunctionTemplate>::New(t);
constructor_template->Inherit(EventEmitter::constructor_template); constructor_template->Inherit(EventEmitter::constructor_template);
constructor_template->InstanceTemplate()->SetInternalFieldCount(1); constructor_template->InstanceTemplate()->SetInternalFieldCount(1);
constructor_template->SetClassName(String::NewSymbol("Database")); constructor_template->SetClassName(String::NewSymbol("Database"));
NODE_SET_PROTOTYPE_METHOD(constructor_template, "open", Open); NODE_SET_PROTOTYPE_METHOD(constructor_template, "open", Open);
NODE_SET_PROTOTYPE_METHOD(constructor_template, "close", Close); NODE_SET_PROTOTYPE_METHOD(constructor_template, "openSync", OpenSync);
NODE_SET_PROTOTYPE_METHOD(constructor_template, "prepare", Prepare); NODE_SET_PROTOTYPE_METHOD(constructor_template, "close", Close);
NODE_SET_PROTOTYPE_METHOD(constructor_template, "prepareAndStep", PrepareAndStep); NODE_SET_PROTOTYPE_METHOD(constructor_template, "closeSync", CloseSync);
target->Set(v8::String::NewSymbol("Database"), NODE_SET_PROTOTYPE_METHOD(constructor_template, "prepare", Prepare);
constructor_template->GetFunction());
// insert/update execution result mask target->Set(v8::String::NewSymbol("Database"),
NODE_DEFINE_CONSTANT (target, EXEC_EMPTY); constructor_template->GetFunction());
NODE_DEFINE_CONSTANT (target, EXEC_LAST_INSERT_ID);
NODE_DEFINE_CONSTANT (target, EXEC_AFFECTED_ROWS);
}
Handle<Value> Database::New(const Arguments& args) { // insert/update execution result mask
HandleScope scope; NODE_DEFINE_CONSTANT(target, EXEC_EMPTY);
Database* db = new Database(); NODE_DEFINE_CONSTANT(target, EXEC_LAST_INSERT_ID);
db->Wrap(args.This()); NODE_DEFINE_CONSTANT(target, EXEC_AFFECTED_ROWS);
return args.This();
} }
int Database::EIO_AfterOpen(eio_req *req) { Handle<Value> Database::New(const Arguments& args) {
ev_unref(EV_DEFAULT_UC); HandleScope scope;
HandleScope scope; Database* db = new Database();
struct open_request *open_req = (struct open_request *)(req->data); db->Wrap(args.This());
return args.This();
Local<Value> argv[1];
bool err = false;
if (req->result) {
err = true;
argv[0] = Exception::Error(String::New("Error opening database"));
}
TryCatch try_catch;
open_req->db->Unref();
open_req->cb->Call(Context::GetCurrent()->Global(), err ? 1 : 0, argv);
if (try_catch.HasCaught()) {
FatalException(try_catch);
}
open_req->db->Emit(String::New("ready"), 0, NULL);
open_req->cb.Dispose();
free(open_req);
return 0;
} }
int Database::EIO_Open(eio_req *req) {
struct open_request *open_req = (struct open_request *)(req->data);
sqlite3 **dbptr = open_req->db->GetDBPtr();
int rc = sqlite3_open_v2( open_req->filename
, dbptr
, SQLITE_OPEN_READWRITE
| SQLITE_OPEN_CREATE
| SQLITE_OPEN_FULLMUTEX
, NULL);
req->result = rc;
// Set the a 10s timeout valuei for retries on BUSY errors. Handle<Value> Database::OpenSync(const Arguments& args) {
sqlite3_busy_timeout(*dbptr, 10000); HandleScope scope;
Database* db = ObjectWrap::Unwrap<Database>(args.This());
if (db->readyState == CLOSED) {
if (!Open(db)) {
EXCEPTION(db->error_message.c_str(), db->error_status, exception);
return ThrowException(exception);
}
else {
args.This()->Set(String::NewSymbol("opened"), True(), ReadOnly);
db->Emit(String::NewSymbol("opened"), 0, NULL);
// sqlite3 *db = *dbptr; if (db->pending == 0) {
// sqlite3_commit_hook(db, CommitHook, open_req->db); db->Emit(String::NewSymbol("idle"), 0, NULL);
// sqlite3_rollback_hook(db, RollbackHook, open_req->db); }
// sqlite3_update_hook(db, UpdateHook, open_req->db); }
}
return 0; return args.This();
} }
Handle<Value> Database::Open(const Arguments& args) { Handle<Value> Database::Open(const Arguments& args) {
HandleScope scope; HandleScope scope;
Database* db = ObjectWrap::Unwrap<Database>(args.This());
REQ_STR_ARG(0, filename);
REQ_FUN_ARG(1, cb); if (db->readyState == CLOSED) {
db->readyState = OPENING;
db->Ref();
eio_custom(EIO_Open, EIO_PRI_DEFAULT, EIO_AfterOpen, db);
ev_ref(EV_DEFAULT_UC);
}
Database* db = ObjectWrap::Unwrap<Database>(args.This()); return args.This();
}
struct open_request *open_req = (struct open_request *) bool Database::Open(Database* db) {
calloc(1, sizeof(struct open_request) + filename.length()); db->error_status = sqlite3_open_v2(
db->filename.c_str(),
&db->handle,
SQLITE_OPEN_FULLMUTEX | db->open_mode,
NULL
);
if (db->error_status != SQLITE_OK) {
db->error_message = std::string(sqlite3_errmsg(db->handle));
db->readyState = CLOSED;
return false;
}
else {
db->readyState = OPEN;
return true;
}
}
if (!open_req) { int Database::EIO_Open(eio_req *req) {
V8::LowMemoryNotification(); Database* db = static_cast<Database*>(req->data);
return ThrowException(Exception::Error( Open(db);
String::New("Could not allocate enough memory"))); return 0;
} }
strcpy(open_req->filename, *filename); int Database::EIO_AfterOpen(eio_req *req) {
open_req->cb = Persistent<Function>::New(cb); HandleScope scope;
open_req->db = db; Database* db = static_cast<Database*>(req->data);
ev_unref(EV_DEFAULT_UC);
db->Unref();
Local<Value> argv[1];
if (db->error_status != SQLITE_OK) {
EXCEPTION(db->error_message.c_str(), db->error_status, exception);
argv[0] = exception;
}
else {
argv[0] = Local<Value>::New(Null());
db->handle_->Set(String::NewSymbol("opened"), True(), ReadOnly);
}
eio_custom(EIO_Open, EIO_PRI_DEFAULT, EIO_AfterOpen, open_req); db->Emit(String::NewSymbol("opened"), 1, argv);
ev_ref(EV_DEFAULT_UC); if (db->pending == 0) {
db->Ref(); db->Emit(String::NewSymbol("idle"), 0, NULL);
}
return Undefined(); return 0;
} }
int Database::EIO_AfterClose(eio_req *req) {
ev_unref(EV_DEFAULT_UC);
HandleScope scope;
struct close_request *close_req = (struct close_request *)(req->data); Handle<Value> Database::CloseSync(const Arguments& args) {
HandleScope scope;
Database* db = ObjectWrap::Unwrap<Database>(args.This());
Local<Value> argv[1]; if (db->readyState == OPEN) {
bool err = false; if (!Close(db)) {
if (req->result) { EXCEPTION(db->error_message.c_str(), db->error_status, exception);
err = true; return ThrowException(exception);
argv[0] = Exception::Error(String::New("Error closing database")); }
} else {
args.This()->Set(String::NewSymbol("opened"), False(), ReadOnly);
db->Emit(String::NewSymbol("closed"), 0, NULL);
}
}
TryCatch try_catch; return True();
}
close_req->db->Unref(); Handle<Value> Database::Close(const Arguments& args) {
close_req->cb->Call(Context::GetCurrent()->Global(), err ? 1 : 0, argv); HandleScope scope;
Database* db = ObjectWrap::Unwrap<Database>(args.This());
if (db->readyState == OPEN) {
db->readyState = CLOSING;
db->Ref();
eio_custom(EIO_Close, EIO_PRI_DEFAULT, EIO_AfterClose, db);
ev_ref(EV_DEFAULT_UC);
}
if (try_catch.HasCaught()) { return args.This();
FatalException(try_catch); }
}
close_req->cb.Dispose(); bool Database::Close(Database* db) {
assert(db->handle);
free(close_req); db->error_status = sqlite3_close(db->handle);
return 0; if (db->error_status != SQLITE_OK) {
db->error_message = std::string(sqlite3_errmsg(db->handle));
db->readyState = OPEN;
return false;
}
else {
db->readyState = CLOSED;
db->handle = NULL;
return true;
}
} }
int Database::EIO_Close(eio_req *req) { int Database::EIO_Close(eio_req *req) {
struct close_request *close_req = (struct close_request *)(req->data); Database* db = static_cast<Database*>(req->data);
Database* db = close_req->db; Close(db);
req->result = sqlite3_close(db->db_); return 0;
db->db_ = NULL;
return 0;
} }
Handle<Value> Database::Close(const Arguments& args) { int Database::EIO_AfterClose(eio_req *req) {
HandleScope scope; HandleScope scope;
Database* db = static_cast<Database*>(req->data);
REQ_FUN_ARG(0, cb); ev_unref(EV_DEFAULT_UC);
db->Unref();
Database* db = ObjectWrap::Unwrap<Database>(args.This());
Local<Value> argv[1];
struct close_request *close_req = (struct close_request *) if (db->error_status != SQLITE_OK) {
calloc(1, sizeof(struct close_request)); EXCEPTION(db->error_message.c_str(), db->error_status, exception);
argv[0] = exception;
if (!close_req) { }
V8::LowMemoryNotification(); else {
return ThrowException(Exception::Error( argv[0] = Local<Value>::New(Null());
String::New("Could not allocate enough memory"))); db->handle_->Set(String::NewSymbol("opened"), False(), ReadOnly);
} }
close_req->cb = Persistent<Function>::New(cb);
close_req->db = db;
eio_custom(EIO_Close, EIO_PRI_DEFAULT, EIO_AfterClose, close_req);
ev_ref(EV_DEFAULT_UC); db->Emit(String::NewSymbol("closed"), 1, argv);
db->Ref();
return Undefined(); return 0;
} }
// // TODO: libeio'fy // // TODO: libeio'fy
...@@ -233,279 +253,309 @@ Handle<Value> Database::Close(const Arguments& args) { ...@@ -233,279 +253,309 @@ Handle<Value> Database::Close(const Arguments& args) {
// } // }
int Database::EIO_AfterPrepareAndStep(eio_req *req) { int Database::EIO_AfterPrepareAndStep(eio_req *req) {
ev_unref(EV_DEFAULT_UC); ev_unref(EV_DEFAULT_UC);
struct prepare_request *prep_req = (struct prepare_request *)(req->data); struct prepare_request *prep_req = (struct prepare_request *)(req->data);
HandleScope scope; HandleScope scope;
Local<Value> argv[2]; Local<Value> argv[2];
int argc = 0; int argc = 0;
// if the prepare failed // if the prepare failed
if (req->result != SQLITE_OK) { if (req->result != SQLITE_OK) {
argv[0] = Exception::Error( argv[0] = Exception::Error(
String::New(sqlite3_errmsg(prep_req->db->db_))); String::New(sqlite3_errmsg(prep_req->db->handle)));
argc = 1; argc = 1;
} }
else { else {
if (req->int1 == SQLITE_DONE) { if (req->int1 == SQLITE_DONE) {
if (prep_req->mode != EXEC_EMPTY) { if (prep_req->mode != EXEC_EMPTY) {
argv[0] = Local<Value>::New(Undefined()); // no error argv[0] = Local<Value>::New(Undefined()); // no error
Local<Object> info = Object::New(); Local<Object> info = Object::New();
if (prep_req->mode & EXEC_LAST_INSERT_ID) { if (prep_req->mode & EXEC_LAST_INSERT_ID) {
info->Set(String::NewSymbol("last_inserted_id"), info->Set(String::NewSymbol("last_inserted_id"),
Integer::NewFromUnsigned (prep_req->lastInsertId)); Integer::NewFromUnsigned (prep_req->lastInsertId));
} }
if (prep_req->mode & EXEC_AFFECTED_ROWS) { if (prep_req->mode & EXEC_AFFECTED_ROWS) {
info->Set(String::NewSymbol("affected_rows"), info->Set(String::NewSymbol("affected_rows"),
Integer::New (prep_req->affectedRows)); Integer::New (prep_req->affectedRows));
} }
argv[1] = info; argv[1] = info;
argc = 2; argc = 2;
} else { } else {
argc = 0; argc = 0;
} }
}
else {
argv[0] = External::New(prep_req->stmt);
argv[1] = Integer::New(req->int1);
Persistent<Object> statement(
Statement::constructor_template->GetFunction()->NewInstance(2, argv));
if (prep_req->tail) {
statement->Set(String::New("tail"), String::New(prep_req->tail));
}
argv[0] = Local<Value>::New(Undefined());
argv[1] = Local<Value>::New(statement);
argc = 2;
}
} }
else {
argv[0] = External::New(prep_req->stmt);
argv[1] = Integer::New(req->int1);
Persistent<Object> statement(
Statement::constructor_template->GetFunction()->NewInstance(2, argv));
if (prep_req->tail) {
statement->Set(String::New("tail"), String::New(prep_req->tail));
}
argv[0] = Local<Value>::New(Undefined());
argv[1] = Local<Value>::New(statement);
argc = 2;
}
}
TryCatch try_catch; TryCatch try_catch;
prep_req->db->Unref(); prep_req->db->Unref();
prep_req->cb->Call(Context::GetCurrent()->Global(), argc, argv); prep_req->cb->Call(Context::GetCurrent()->Global(), argc, argv);
if (try_catch.HasCaught()) { if (try_catch.HasCaught()) {
FatalException(try_catch); FatalException(try_catch);
} }
prep_req->cb.Dispose(); prep_req->cb.Dispose();
free(prep_req); free(prep_req);
return 0; return 0;
} }
int Database::EIO_PrepareAndStep(eio_req *req) { int Database::EIO_PrepareAndStep(eio_req *req) {
struct prepare_request *prep_req = (struct prepare_request *)(req->data); struct prepare_request *prep_req = (struct prepare_request *)(req->data);
prep_req->stmt = NULL; prep_req->stmt = NULL;
prep_req->tail = NULL; prep_req->tail = NULL;
sqlite3* db = prep_req->db->db_; sqlite3* db = prep_req->db->handle;
int rc = sqlite3_prepare_v2(db, prep_req->sql, -1, int rc = sqlite3_prepare_v2(db, prep_req->sql, -1,
&(prep_req->stmt), &(prep_req->tail)); &(prep_req->stmt), &(prep_req->tail));
req->result = rc; req->result = rc;
req->int1 = -1; req->int1 = -1;
// This might be a INSERT statement. Let's try to get the first row. // This might be a INSERT statement. Let's try to get the first row.
// This is to optimize out further calls to the thread pool. This is only // This is to optimize out further calls to the thread pool. This is only
// possible in the case where there are no variable placeholders/bindings // possible in the case where there are no variable placeholders/bindings
// in the SQL. // in the SQL.
if (rc == SQLITE_OK && !sqlite3_bind_parameter_count(prep_req->stmt)) { if (rc == SQLITE_OK && !sqlite3_bind_parameter_count(prep_req->stmt)) {
rc = sqlite3_step(prep_req->stmt); rc = sqlite3_step(prep_req->stmt);
req->int1 = rc; req->int1 = rc;
// no more rows to return, clean up statement // no more rows to return, clean up statement
if (rc == SQLITE_DONE) { if (rc == SQLITE_DONE) {
rc = sqlite3_finalize(prep_req->stmt); rc = sqlite3_finalize(prep_req->stmt);
prep_req->stmt = NULL; prep_req->stmt = NULL;
assert(rc == SQLITE_OK); assert(rc == SQLITE_OK);
}
} }
}
prep_req->lastInsertId = 0; prep_req->lastInsertId = 0;
prep_req->affectedRows = 0; prep_req->affectedRows = 0;
// load custom properties // load custom properties
if (prep_req->mode & EXEC_LAST_INSERT_ID) if (prep_req->mode & EXEC_LAST_INSERT_ID)
prep_req->lastInsertId = sqlite3_last_insert_rowid(db); prep_req->lastInsertId = sqlite3_last_insert_rowid(db);
if (prep_req->mode & EXEC_AFFECTED_ROWS) if (prep_req->mode & EXEC_AFFECTED_ROWS)
prep_req->affectedRows = sqlite3_changes(db); prep_req->affectedRows = sqlite3_changes(db);
return 0; return 0;
} }
Handle<Value> Database::PrepareAndStep(const Arguments& args) { Handle<Value> Database::PrepareAndStep(const Arguments& args) {
HandleScope scope; HandleScope scope;
REQ_STR_ARG(0, sql); REQUIRE_ARGUMENT_STRING(0, sql);
REQ_FUN_ARG(1, cb); REQUIRE_ARGUMENT_FUNCTION(1, cb);
OPT_INT_ARG(2, mode, EXEC_EMPTY); OPTIONAL_ARGUMENT_INTEGER(2, mode, EXEC_EMPTY);
Database* db = ObjectWrap::Unwrap<Database>(args.This()); Database* db = ObjectWrap::Unwrap<Database>(args.This());
struct prepare_request *prep_req = (struct prepare_request *) struct prepare_request *prep_req = (struct prepare_request *)
calloc(1, sizeof(struct prepare_request) + sql.length()); calloc(1, sizeof(struct prepare_request) + sql.length());
if (!prep_req) { if (!prep_req) {
V8::LowMemoryNotification(); V8::LowMemoryNotification();
return ThrowException(Exception::Error( return ThrowException(Exception::Error(
String::New("Could not allocate enough memory"))); String::New("Could not allocate enough memory")));
} }
strcpy(prep_req->sql, *sql); strcpy(prep_req->sql, *sql);
prep_req->cb = Persistent<Function>::New(cb); prep_req->cb = Persistent<Function>::New(cb);
prep_req->db = db; prep_req->db = db;
prep_req->mode = mode; prep_req->mode = mode;
eio_custom(EIO_PrepareAndStep, EIO_PRI_DEFAULT, EIO_AfterPrepareAndStep, prep_req); eio_custom(EIO_PrepareAndStep, EIO_PRI_DEFAULT, EIO_AfterPrepareAndStep, prep_req);
ev_ref(EV_DEFAULT_UC); ev_ref(EV_DEFAULT_UC);
db->Ref(); db->Ref();
return Undefined(); return Undefined();
} }
int Database::EIO_AfterPrepare(eio_req *req) { int Database::EIO_AfterPrepare(eio_req *req) {
ev_unref(EV_DEFAULT_UC); ev_unref(EV_DEFAULT_UC);
struct prepare_request *prep_req = (struct prepare_request *)(req->data); struct prepare_request *prep_req = (struct prepare_request *)(req->data);
HandleScope scope; HandleScope scope;
Local<Value> argv[3]; Local<Value> argv[3];
int argc = 0; int argc = 0;
// if the prepare failed // if the prepare failed
if (req->result != SQLITE_OK) { if (req->result != SQLITE_OK) {
argv[0] = Exception::Error( argv[0] = Exception::Error(
String::New(sqlite3_errmsg(prep_req->db->db_))); String::New(sqlite3_errmsg(prep_req->db->handle)));
argc = 1; argc = 1;
}
else {
argv[0] = External::New(prep_req->stmt);
argv[1] = Integer::New(-1);
argv[2] = Integer::New(prep_req->mode);
Persistent<Object> statement(
Statement::constructor_template->GetFunction()->NewInstance(3, argv));
if (prep_req->tail) {
statement->Set(String::New("tail"), String::New(prep_req->tail));
} }
else {
argv[0] = External::New(prep_req->stmt);
argv[1] = Integer::New(-1);
argv[2] = Integer::New(prep_req->mode);
Persistent<Object> statement(
Statement::constructor_template->GetFunction()->NewInstance(3, argv));
if (prep_req->tail) {
statement->Set(String::New("tail"), String::New(prep_req->tail));
}
argc = 2; argc = 2;
argv[0] = Local<Value>::New(Undefined()); argv[0] = Local<Value>::New(Undefined());
argv[1] = Local<Value>::New(statement); argv[1] = Local<Value>::New(statement);
} }
TryCatch try_catch; TryCatch try_catch;
prep_req->db->Unref(); prep_req->db->Unref();
prep_req->cb->Call(Context::GetCurrent()->Global(), argc, argv); prep_req->cb->Call(Context::GetCurrent()->Global(), argc, argv);
if (try_catch.HasCaught()) { if (try_catch.HasCaught()) {
FatalException(try_catch); FatalException(try_catch);
} }
prep_req->cb.Dispose(); prep_req->cb.Dispose();
free(prep_req); free(prep_req);
return 0; return 0;
} }
int Database::EIO_Prepare(eio_req *req) { int Database::EIO_Prepare(eio_req *req) {
struct prepare_request *prep_req = (struct prepare_request *)(req->data); struct prepare_request *prep_req = (struct prepare_request *)(req->data);
prep_req->stmt = NULL; prep_req->stmt = NULL;
prep_req->tail = NULL; prep_req->tail = NULL;
sqlite3* db = prep_req->db->db_; sqlite3* db = prep_req->db->handle;
int rc = sqlite3_prepare_v2(db, prep_req->sql, -1, int rc = sqlite3_prepare_v2(db, prep_req->sql, -1,
&(prep_req->stmt), &(prep_req->tail)); &(prep_req->stmt), &(prep_req->tail));
req->result = rc; req->result = rc;
prep_req->lastInsertId = 0; prep_req->lastInsertId = 0;
prep_req->affectedRows = 0; prep_req->affectedRows = 0;
// load custom properties // load custom properties
if (prep_req->mode & EXEC_LAST_INSERT_ID) if (prep_req->mode & EXEC_LAST_INSERT_ID)
prep_req->lastInsertId = sqlite3_last_insert_rowid(db); prep_req->lastInsertId = sqlite3_last_insert_rowid(db);
if (prep_req->mode & EXEC_AFFECTED_ROWS) if (prep_req->mode & EXEC_AFFECTED_ROWS)
prep_req->affectedRows = sqlite3_changes(db); prep_req->affectedRows = sqlite3_changes(db);
return 0; return 0;
} }
// Statement#prepare(sql, [ options ,] callback); // Statement#prepare(sql, [ options ,] callback);
Handle<Value> Database::Prepare(const Arguments& args) { Handle<Value> Database::Prepare(const Arguments& args) {
HandleScope scope; HandleScope scope;
Local<Object> options; Local<Object> options;
Local<Function> cb; Local<Function> cb;
int mode; int mode;
REQ_STR_ARG(0, sql); REQUIRE_ARGUMENT_STRING(0, sql);
// middle argument could be options or // middle argument could be options or
switch (args.Length()) { switch (args.Length()) {
case 2: case 2:
if (!args[1]->IsFunction()) { if (!args[1]->IsFunction()) {
return ThrowException(Exception::TypeError( return ThrowException(Exception::TypeError(
String::New("Argument 1 must be a function"))); String::New("Argument 1 must be a function")));
} }
cb = Local<Function>::Cast(args[1]); cb = Local<Function>::Cast(args[1]);
options = Object::New(); options = Object::New();
break; break;
case 3: case 3:
if (!args[1]->IsObject()) { if (!args[1]->IsObject()) {
return ThrowException(Exception::TypeError( return ThrowException(Exception::TypeError(
String::New("Argument 1 must be an object"))); String::New("Argument 1 must be an object")));
} }
options = Local<Function>::Cast(args[1]); options = Local<Function>::Cast(args[1]);
if (!args[2]->IsFunction()) { if (!args[2]->IsFunction()) {
return ThrowException(Exception::TypeError( return ThrowException(Exception::TypeError(
String::New("Argument 2 must be a function"))); String::New("Argument 2 must be a function")));
} }
cb = Local<Function>::Cast(args[2]); cb = Local<Function>::Cast(args[2]);
break; break;
} }
mode = EXEC_EMPTY; mode = EXEC_EMPTY;
if (options->Get(String::New("lastInsertRowID"))->IsTrue()) { if (options->Get(String::New("lastInsertRowID"))->IsTrue()) {
mode |= EXEC_LAST_INSERT_ID; mode |= EXEC_LAST_INSERT_ID;
} }
if (options->Get(String::New("affectedRows"))->IsTrue()) { if (options->Get(String::New("affectedRows"))->IsTrue()) {
mode |= EXEC_AFFECTED_ROWS; mode |= EXEC_AFFECTED_ROWS;
} }
Database* db = ObjectWrap::Unwrap<Database>(args.This()); Database* db = ObjectWrap::Unwrap<Database>(args.This());
struct prepare_request *prep_req = (struct prepare_request *) struct prepare_request *prep_req = (struct prepare_request *)
calloc(1, sizeof(struct prepare_request) + sql.length()); calloc(1, sizeof(struct prepare_request) + sql.length());
if (!prep_req) { if (!prep_req) {
V8::LowMemoryNotification(); V8::LowMemoryNotification();
return ThrowException(Exception::Error( return ThrowException(Exception::Error(
String::New("Could not allocate enough memory"))); String::New("Could not allocate enough memory")));
} }
strcpy(prep_req->sql, *sql); strcpy(prep_req->sql, *sql);
prep_req->cb = Persistent<Function>::New(cb); prep_req->cb = Persistent<Function>::New(cb);
prep_req->db = db; prep_req->db = db;
prep_req->mode = mode; prep_req->mode = mode;
eio_custom(EIO_Prepare, EIO_PRI_DEFAULT, EIO_AfterPrepare, prep_req); eio_custom(EIO_Prepare, EIO_PRI_DEFAULT, EIO_AfterPrepare, prep_req);
ev_ref(EV_DEFAULT_UC); ev_ref(EV_DEFAULT_UC);
db->Ref(); db->Ref();
return Undefined(); return Undefined();
}
/**
* Override this so that we can properly close the database when this object
* gets garbage collected.
*/
void Database::Wrap(Handle<Object> handle) {
assert(handle_.IsEmpty());
assert(handle->InternalFieldCount() > 0);
handle_ = Persistent<Object>::New(handle);
handle_->SetPointerInInternalField(0, this);
handle_.MakeWeak(this, Destruct);
}
void Database::Destruct(Persistent<Value> value, void *data) {
Database* db = static_cast<Database*>(data);
if (db->handle) {
eio_custom(EIO_Close, EIO_PRI_DEFAULT, EIO_AfterDestruct, db);
ev_ref(EV_DEFAULT_UC);
}
else {
delete db;
}
}
int Database::EIO_AfterDestruct(eio_req *req) {
Database* db = static_cast<Database*>(req->data);
ev_unref(EV_DEFAULT_UC);
delete db;
return 0;
} }
// Copyright (c) 2010, Orlando Vazquez <ovazquez@gmail.com> // Copyright (c) 2010, Orlando Vazquez <ovazquez@gmail.com>
// //
// Permission to use, copy, modify, and/or distribute this software for any // Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above // purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies. // copyright notice and this permission notice appear in all copies.
// //
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
...@@ -19,35 +19,51 @@ ...@@ -19,35 +19,51 @@
#include <node.h> #include <node.h>
#include <node_events.h> #include <node_events.h>
#include <string>
#include <sqlite3.h> #include <sqlite3.h>
using namespace v8; using namespace v8;
using namespace node; using namespace node;
enum ReadyState {
CLOSED,
OPENING,
OPEN,
CLOSING
};
class Database : public EventEmitter { class Database : public EventEmitter {
public: public:
static Persistent<FunctionTemplate> constructor_template; static Persistent<FunctionTemplate> constructor_template;
static void Init(v8::Handle<Object> target); static void Init(v8::Handle<Object> target);
protected: protected:
Database() : EventEmitter(), db_(NULL) { } Database() : EventEmitter(),
handle(NULL),
pending(0),
readyState(CLOSED) {
}
~Database() { ~Database() {
assert(db_ == NULL); fprintf(stderr, "Calling destructor\n");
printf("Destroying database\n");
} }
static Handle<Value> New(const Arguments& args); static Handle<Value> New(const Arguments& args);
static int EIO_AfterOpen(eio_req *req); static Handle<Value> OpenSync(const Arguments& args);
static int EIO_Open(eio_req *req);
static Handle<Value> Open(const Arguments& args); static Handle<Value> Open(const Arguments& args);
static bool Open(Database* db);
static int EIO_Open(eio_req *req);
static int EIO_AfterOpen(eio_req *req);
static int EIO_AfterClose(eio_req *req); static Handle<Value> CloseSync(const Arguments& args);
static int EIO_Close(eio_req *req);
static Handle<Value> Close(const Arguments& args); static Handle<Value> Close(const Arguments& args);
static bool Close(Database* db);
static int EIO_Close(eio_req *req);
static int EIO_AfterClose(eio_req *req);
// static Handle<Value> LastInsertRowid(const Arguments& args);
static int EIO_AfterPrepareAndStep(eio_req *req); static int EIO_AfterPrepareAndStep(eio_req *req);
static int EIO_PrepareAndStep(eio_req *req); static int EIO_PrepareAndStep(eio_req *req);
static Handle<Value> PrepareAndStep(const Arguments& args); static Handle<Value> PrepareAndStep(const Arguments& args);
...@@ -56,35 +72,29 @@ class Database : public EventEmitter { ...@@ -56,35 +72,29 @@ class Database : public EventEmitter {
static int EIO_Prepare(eio_req *req); static int EIO_Prepare(eio_req *req);
static Handle<Value> Prepare(const Arguments& args); static Handle<Value> Prepare(const Arguments& args);
// Return a pointer to the Sqlite handle pointer so that EIO_Open can void Wrap (Handle<Object> handle);
// pass it to sqlite3_open which wants a pointer to an sqlite3 pointer. This static void Destruct (Persistent<Value> value, void *data);
// is because it wants to initialize our original (sqlite3*) pointer to static int EIO_Destruct(eio_req *req);
// point to an valid object. static int EIO_AfterDestruct(eio_req *req);
sqlite3** GetDBPtr(void) { return &db_; }
protected: protected:
sqlite3* handle;
std::string filename;
int open_mode;
std::string error_message;
int error_status;
sqlite3* db_; int pending;
ReadyState readyState;
}; };
enum ExecMode enum ExecMode {
{
EXEC_EMPTY = 0, EXEC_EMPTY = 0,
EXEC_LAST_INSERT_ID = 1, EXEC_LAST_INSERT_ID = 1,
EXEC_AFFECTED_ROWS = 2 EXEC_AFFECTED_ROWS = 2
}; };
struct open_request {
Persistent<Function> cb;
Database *db;
char filename[1];
};
struct close_request {
Persistent<Function> cb;
Database *db;
};
struct prepare_request { struct prepare_request {
Persistent<Function> cb; Persistent<Function> cb;
Database *db; Database *db;
......
...@@ -15,6 +15,9 @@ ...@@ -15,6 +15,9 @@
#ifndef NODE_SQLITE3_SRC_MACROS_H #ifndef NODE_SQLITE3_SRC_MACROS_H
#define NODE_SQLITE3_SRC_MACROS_H #define NODE_SQLITE3_SRC_MACROS_H
const char* sqlite_code_string(int code);
#define CHECK(rc) { if ((rc) != SQLITE_OK) \ #define CHECK(rc) { if ((rc) != SQLITE_OK) \
return ThrowException(Exception::Error(String::New( \ return ThrowException(Exception::Error(String::New( \
sqlite3_errmsg(*db)))); } sqlite3_errmsg(*db)))); }
...@@ -23,37 +26,110 @@ ...@@ -23,37 +26,110 @@
return ThrowException(Exception::Error(String::New( \ return ThrowException(Exception::Error(String::New( \
sqlite3_errmsg(sqlite3_db_handle(sto->stmt_))))); } sqlite3_errmsg(sqlite3_db_handle(sto->stmt_))))); }
#define REQ_ARGS(N) \
if (args.Length() < (N)) \ #define REQUIRE_ARGUMENTS(n) \
return ThrowException(Exception::TypeError( \ if (args.Length() < (n)) { \
String::New("Expected " #N "arguments"))); return ThrowException( \
Exception::TypeError(String::New("Expected " #n "arguments")) \
#define REQ_STR_ARG(I, VAR) \ ); \
if (args.Length() <= (I) || !args[I]->IsString()) \ }
return ThrowException(Exception::TypeError( \
String::New("Argument " #I " must be a string"))); \
String::Utf8Value VAR(args[I]->ToString()); #define REQUIRE_ARGUMENT_EXTERNAL(i, var) \
if (args.Length() <= (i) || !args[i]->IsExternal()) { \
#define REQ_FUN_ARG(I, VAR) \ return ThrowException( \
if (args.Length() <= (I) || !args[I]->IsFunction()) \ Exception::TypeError(String::New("Argument " #i " invalid")) \
return ThrowException(Exception::TypeError( \ ); \
String::New("Argument " #I " must be a function"))); \ } \
Local<Function> VAR = Local<Function>::Cast(args[I]); Local<External> var = Local<External>::Cast(args[i]);
#define REQ_EXT_ARG(I, VAR) \
if (args.Length() <= (I) || !args[I]->IsExternal()) \ #define REQUIRE_ARGUMENT_FUNCTION(i, var) \
return ThrowException(Exception::TypeError( \ if (args.Length() <= (i) || !args[i]->IsFunction()) { \
String::New("Argument " #I " invalid"))); \ return ThrowException(Exception::TypeError( \
Local<External> VAR = Local<External>::Cast(args[I]); String::New("Argument " #i " must be a function")) \
); \
#define OPT_INT_ARG(I, VAR, DEFAULT) \ } \
int VAR; \ Local<Function> var = Local<Function>::Cast(args[i]);
if (args.Length() <= (I)) { \
VAR = (DEFAULT); \
} else if (args[I]->IsInt32()) { \ #define REQUIRE_ARGUMENT_STRING(i, var) \
VAR = args[I]->Int32Value(); \ if (args.Length() <= (i) || !args[i]->IsString()) { \
} else { \ return ThrowException(Exception::TypeError( \
return ThrowException(Exception::TypeError( \ String::New("Argument " #i " must be a string")) \
String::New("Argument " #I " must be an integer"))); \ ); \
} } \
String::Utf8Value var(args[i]->ToString());
#define OPTIONAL_ARGUMENT_FUNCTION(i, var) \
Local<Function> var; \
bool var ## _exists = false; \
if (args.Length() >= i) { \
if (!args[i]->IsFunction()) { \
return ThrowException(Exception::TypeError( \
String::New("Argument " #i " must be a function")) \
); \
} \
var = Local<Function>::Cast(args[i]); \
var ## _exists = true; \
}
#define OPTIONAL_ARGUMENT_INTEGER(i, var, default) \
int var; \
if (args.Length() <= (i)) { \
var = (default); \
} \
else if (args[i]->IsInt32()) { \
var = args[i]->Int32Value(); \
} \
else { \
return ThrowException(Exception::TypeError( \
String::New("Argument " #i " must be an integer")) \
); \
}
#define DEFINE_CONSTANT_INTEGER(target, constant, name) \
(target)->Set( \
String::NewSymbol(#name), \
Integer::New(constant), \
static_cast<PropertyAttribute>(ReadOnly | DontDelete) \
);
#define DEFINE_CONSTANT_STRING(target, constant, name) \
(target)->Set( \
String::NewSymbol(#name), \
String::NewSymbol(constant), \
static_cast<PropertyAttribute>(ReadOnly | DontDelete) \
);
#define NODE_SET_GETTER(target, name, function) \
(target)->InstanceTemplate() \
->SetAccessor(String::NewSymbol(name), (function));
#define GET_STRING(source, name, property) \
String::Utf8Value name((source)->Get(String::NewSymbol(property)));
#define GET_INTEGER(source, name, property) \
int name = (source)->Get(String::NewSymbol(property))->Int32Value();
#define EXCEPTION(msg, errno, name) \
Local<Value> name = Exception::Error( \
String::Concat( \
String::Concat( \
String::NewSymbol(sqlite_code_string(errno)), \
String::NewSymbol(": ") \
), \
String::New(msg) \
) \
); \
Local<Object> name ## _obj = name->ToObject(); \
name ## _obj->Set(NODE_PSYMBOL("errno"), Integer::New(errno)); \
name ## _obj->Set(NODE_PSYMBOL("code"), \
String::NewSymbol(sqlite_code_string(errno)));
#endif #endif
...@@ -16,11 +16,54 @@ ...@@ -16,11 +16,54 @@
#include <node.h> #include <node.h>
#include <node_events.h> #include <node_events.h>
#include <sqlite3.h>
#include "macros.h"
#include "database.h" #include "database.h"
#include "statement.h" #include "statement.h"
extern "C" void init (v8::Handle<Object> target) { extern "C" void init (v8::Handle<Object> target) {
Database::Init(target); Database::Init(target);
Statement::Init(target); Statement::Init(target);
DEFINE_CONSTANT_INTEGER(target, SQLITE_OPEN_READONLY, OPEN_READONLY);
DEFINE_CONSTANT_INTEGER(target, SQLITE_OPEN_READWRITE, OPEN_READWRITE);
DEFINE_CONSTANT_INTEGER(target, SQLITE_OPEN_CREATE, OPEN_CREATE);
DEFINE_CONSTANT_STRING(target, SQLITE_VERSION, VERSION);
DEFINE_CONSTANT_INTEGER(target, SQLITE_VERSION_NUMBER, VERSION_NUMBER);
} }
const char* sqlite_code_string(int code) {
switch (code) {
case SQLITE_OK: return "SQLITE_OK";
case SQLITE_ERROR: return "SQLITE_ERROR";
case SQLITE_INTERNAL: return "SQLITE_INTERNAL";
case SQLITE_PERM: return "SQLITE_PERM";
case SQLITE_ABORT: return "SQLITE_ABORT";
case SQLITE_BUSY: return "SQLITE_BUSY";
case SQLITE_LOCKED: return "SQLITE_LOCKED";
case SQLITE_NOMEM: return "SQLITE_NOMEM";
case SQLITE_READONLY: return "SQLITE_READONLY";
case SQLITE_INTERRUPT: return "SQLITE_INTERRUPT";
case SQLITE_IOERR: return "SQLITE_IOERR";
case SQLITE_CORRUPT: return "SQLITE_CORRUPT";
case SQLITE_NOTFOUND: return "SQLITE_NOTFOUND";
case SQLITE_FULL: return "SQLITE_FULL";
case SQLITE_CANTOPEN: return "SQLITE_CANTOPEN";
case SQLITE_PROTOCOL: return "SQLITE_PROTOCOL";
case SQLITE_EMPTY: return "SQLITE_EMPTY";
case SQLITE_SCHEMA: return "SQLITE_SCHEMA";
case SQLITE_TOOBIG: return "SQLITE_TOOBIG";
case SQLITE_CONSTRAINT: return "SQLITE_CONSTRAINT";
case SQLITE_MISMATCH: return "SQLITE_MISMATCH";
case SQLITE_MISUSE: return "SQLITE_MISUSE";
case SQLITE_NOLFS: return "SQLITE_NOLFS";
case SQLITE_AUTH: return "SQLITE_AUTH";
case SQLITE_FORMAT: return "SQLITE_FORMAT";
case SQLITE_RANGE: return "SQLITE_RANGE";
case SQLITE_NOTADB: return "SQLITE_NOTADB";
case SQLITE_ROW: return "SQLITE_ROW";
case SQLITE_DONE: return "SQLITE_DONE";
default: return "UNKNOWN";
}
}
...@@ -50,7 +50,7 @@ void Statement::Init(v8::Handle<Object> target) { ...@@ -50,7 +50,7 @@ void Statement::Init(v8::Handle<Object> target) {
Handle<Value> Statement::New(const Arguments& args) { Handle<Value> Statement::New(const Arguments& args) {
HandleScope scope; HandleScope scope;
REQ_EXT_ARG(0, stmt); REQUIRE_ARGUMENT_EXTERNAL(0, stmt);
int first_rc = args[1]->IntegerValue(); int first_rc = args[1]->IntegerValue();
int mode = args[2]->IntegerValue(); int mode = args[2]->IntegerValue();
...@@ -169,8 +169,8 @@ Handle<Value> Statement::BindObject(const Arguments& args) { ...@@ -169,8 +169,8 @@ Handle<Value> Statement::BindObject(const Arguments& args) {
HandleScope scope; HandleScope scope;
Statement* sto = ObjectWrap::Unwrap<Statement>(args.This()); Statement* sto = ObjectWrap::Unwrap<Statement>(args.This());
REQ_ARGS(2); REQUIRE_ARGUMENTS(2);
REQ_FUN_ARG(1, cb); REQUIRE_ARGUMENT_FUNCTION(1, cb);
if (! args[0]->IsObject()) if (! args[0]->IsObject())
return ThrowException(Exception::TypeError( return ThrowException(Exception::TypeError(
...@@ -253,8 +253,8 @@ Handle<Value> Statement::BindArray(const Arguments& args) { ...@@ -253,8 +253,8 @@ Handle<Value> Statement::BindArray(const Arguments& args) {
HandleScope scope; HandleScope scope;
Statement* sto = ObjectWrap::Unwrap<Statement>(args.This()); Statement* sto = ObjectWrap::Unwrap<Statement>(args.This());
REQ_ARGS(2); REQUIRE_ARGUMENTS(2);
REQ_FUN_ARG(1, cb); REQUIRE_ARGUMENT_FUNCTION(1, cb);
if (! args[0]->IsArray()) if (! args[0]->IsArray())
return ThrowException(Exception::TypeError( return ThrowException(Exception::TypeError(
String::New("First argument must be an Array."))); String::New("First argument must be an Array.")));
...@@ -349,8 +349,8 @@ Handle<Value> Statement::Bind(const Arguments& args) { ...@@ -349,8 +349,8 @@ Handle<Value> Statement::Bind(const Arguments& args) {
HandleScope scope; HandleScope scope;
Statement* sto = ObjectWrap::Unwrap<Statement>(args.This()); Statement* sto = ObjectWrap::Unwrap<Statement>(args.This());
REQ_ARGS(2); REQUIRE_ARGUMENTS(2);
REQ_FUN_ARG(2, cb); REQUIRE_ARGUMENT_FUNCTION(2, cb);
if (!( args[0]->IsString() if (!( args[0]->IsString()
|| args[0]->IsInt32() || args[0]->IsInt32()
...@@ -475,7 +475,7 @@ Handle<Value> Statement::Finalize(const Arguments& args) { ...@@ -475,7 +475,7 @@ Handle<Value> Statement::Finalize(const Arguments& args) {
return ThrowException(Exception::Error(String::New("Already stepping"))); return ThrowException(Exception::Error(String::New("Already stepping")));
} }
REQ_FUN_ARG(0, cb); REQUIRE_ARGUMENT_FUNCTION(0, cb);
sto->SetCallback(cb); sto->SetCallback(cb);
...@@ -740,7 +740,7 @@ Handle<Value> Statement::Step(const Arguments& args) { ...@@ -740,7 +740,7 @@ Handle<Value> Statement::Step(const Arguments& args) {
return ThrowException(Exception::Error(String::New("Already stepping"))); return ThrowException(Exception::Error(String::New("Already stepping")));
} }
REQ_FUN_ARG(0, cb); REQUIRE_ARGUMENT_FUNCTION(0, cb);
sto->SetCallback(cb); sto->SetCallback(cb);
...@@ -1011,7 +1011,7 @@ int Statement::EIO_FetchAll(eio_req *req) { ...@@ -1011,7 +1011,7 @@ int Statement::EIO_FetchAll(eio_req *req) {
Handle<Value> Statement::FetchAll(const Arguments& args) { Handle<Value> Statement::FetchAll(const Arguments& args) {
HandleScope scope; HandleScope scope;
REQ_FUN_ARG(0, cb); REQUIRE_ARGUMENT_FUNCTION(0, cb);
struct fetchall_request *fetchall_req = (struct fetchall_request *) struct fetchall_request *fetchall_req = (struct fetchall_request *)
calloc(1, sizeof(struct fetchall_request)); calloc(1, sizeof(struct fetchall_request));
......
...@@ -14,11 +14,10 @@ exports['Blob overflow test'] = function(beforeExit) { ...@@ -14,11 +14,10 @@ exports['Blob overflow test'] = function(beforeExit) {
var inserted = 0; var inserted = 0;
var retrieved = 0; var retrieved = 0;
db.openSync('');
Step( Step(
function() { function() {
db.open('', this);
},
function() {
var next = this; var next = this;
db.prepare('CREATE TABLE elmos (image BLOB);', function(err, statement) { db.prepare('CREATE TABLE elmos (image BLOB);', function(err, statement) {
assert.isUndefined(err); assert.isUndefined(err);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment