Commit eaf72f23 by Konstantin Käfer

saving work

parent c09549d0
...@@ -16,273 +16,22 @@ ...@@ -16,273 +16,22 @@
var sqlite3 = module.exports = exports = require('./sqlite3_bindings'); var sqlite3 = module.exports = exports = require('./sqlite3_bindings');
var sys = require("sys"); var sys = require("sys");
function noop(err) { // function noop(err) {
if (err) throw err; // if (err) throw err;
};
var Database = sqlite3.Database;
// var realClose = Database.prototype.close;
// sqlite3.Database.prototype.close = function(callback) {
// if (this.status === sqlite3.STATUS_IDLE) {
// return realClose.call(this, callback);
// } else {
// this.once('idle', function() {
// realClose.call(this, callback);
// });
// }
// }; // };
// var realClose = sqlite3.Database.prototype.close; var Database = sqlite3.Database;
// sqlite3.Database.prototype.close = function() { var Statement = sqlite3.Statement;
// var db = this;
// process.nextTick(function() {
// if (!db.idle) {
// db.on('idle', function fn() {
// realClose.call(db);
// db.removeListener('idle', fn);
// });
// } else {
// realClose.call(db);
// }
// });
// return this;
// };
/*
sqlite3.Database.prototype.query = function(sql, bindings, rowCallback) {
var self = this;
if (typeof(bindings) == "function") {
rowCallback = bindings;
bindings = undefined;
}
this.prepare(sql, function(error, statement) {
function next() {
_doStep(self, statement, rowCallback);
}
if (error) {
return rowCallback (error);
}
if (statement) {
if (Array.isArray(bindings)) {
statement.bindArray(bindings, next);
}
else if (typeof(bindings) === 'object') {
statement.bindObject(bindings, next);
}
else {
next();
}
}
else {
rowCallback();
}
});
}
function _doStep(db, statement, rowCallback) {
statement.step(function (error, row) {
if (error) {
return rowCallback(error);
}
if (!row) {
rowCallback();
statement.finalize(function(){});
return;
}
rowCallback(undefined, row);
_doStep(db, statement, rowCallback);
});
}
// Execute a single SQL query with the given optional parameters. Calls
// `callback` with all rows or an error on query completion.
sqlite3.Database.prototype.execute = function (sql /* , bindings, callback ) {
var self = this;
var bindings, callback;
var n = arguments.length;
switch (n) {
case 3:
callback = arguments[2];
bindings = arguments[1];
break;
case 2:
callback = arguments[1];
break;
default: throw new Error("Invalid number of arguments ("+n+")");
}
self.prepare(sql, function (error, statement) { // Database#prepare(sql, [bind1, bind2, ...], [callback])
function next (error) { Database.prototype.prepare = function(sql) {
if (error) return callback(new Error("Error binding: " + error.toString())); var callback, params = Array.prototype.slice.call(arguments, 1);
fetchAll(statement);
}
if (error) { if (params.length && typeof params[params.length - 1] === 'function') {
return callback(error); callback = params.pop();
} }
if (bindings) {
if (Array.isArray(bindings)) {
statement.bindArray(bindings, next);
}
else if (typeof(bindings) === 'object') {
statement.bindObject(bindings, next);
}
}
else {
next();
}
function fetchAll(statement) {
statement.fetchAll(function (error, rows) {
if (error) {
return callback(error);
}
statement.finalize(function () {
callback(undefined, rows);
});
});
}
});
}
// Execute SQL statements separated by semi-colons.
// SQL must contain no placeholders. Results are discarded.
sqlite3.Database.prototype.executeScript = function (script, callback) {
var self = this;
(function stepOverSQL(sql) {
self.prepare(sql, function(error, statement) {
if (error) {
return callback(error);
}
statement.step(function (error, row) {
var tail;
if (error) {
callback(error);
return;
}
if (!row) {
statement.finalize(function(){});
tail = statement.tail;
if (typeof tail == "string") {
tail = tail.trim();
}
if (tail) {
stepOverSQL(tail);
}
else {
callback();
}
}
});
});
})(script);
}
sqlite3.Database.prototype.insertMany = function (table, columns, rows, callback) { return new Statement(this, sql, params, callback);
var columnsFragment = columns.join(",");
var placeholdersFragment = [];
var i = columns.length;
if (!rows.length) {
callback();
return;
}
while (i--) {
placeholdersFragment.push('?');
}
placeholdersFragment = placeholdersFragment.join(", ");
var sql = [ 'INSERT INTO'
, table
, '('
, columnsFragment
, ')'
, 'VALUES'
, '('
, placeholdersFragment
, ')'
]
.join(" ");
var i = rows.length;
var statement;
function doStep(i) {
statement.bindArray(rows[i], function () {
statement.step(function (error, row) {
if (error) return callback(error);
statement.reset();
if (i) {
doStep(--i);
}
else {
statement.finalize(function () {
callback();
});
}
});
});
}
this.prepare(sql, function (error, stmt) {
if (error) return callback(error);
statement = stmt;
doStep(--i);
});
}
sqlite3.fromErrorCode = function(code) {
switch (code) {
case 0: return "SQLITE_OK";
case 1: return "SQLITE_ERROR";
case 2: return "SQLITE_INTERNAL";
case 3: return "SQLITE_PERM";
case 4: return "SQLITE_ABORT";
case 5: return "SQLITE_BUSY";
case 6: return "SQLITE_LOCKED";
case 7: return "SQLITE_NOMEM";
case 8: return "SQLITE_READONLY";
case 9: return "SQLITE_INTERRUPT";
case 10: return "SQLITE_IOERR";
case 11: return "SQLITE_CORRUPT";
case 12: return "SQLITE_NOTFOUND";
case 13: return "SQLITE_FULL";
case 14: return "SQLITE_CANTOPEN";
case 15: return "SQLITE_PROTOCOL";
case 16: return "SQLITE_EMPTY";
case 17: return "SQLITE_SCHEMA";
case 18: return "SQLITE_TOOBIG";
case 19: return "SQLITE_CONSTRAINT";
case 20: return "SQLITE_MISMATCH";
case 21: return "SQLITE_MISUSE";
case 22: return "SQLITE_NOLFS";
case 23: return "SQLITE_AUTH";
case 24: return "SQLITE_FORMAT";
case 25: return "SQLITE_RANGE";
case 26: return "SQLITE_NOTADB";
}
}; };
sqlite3.sanitizeError = function(err, data) {
err.message = exports.fromErrorCode(err.errno) + ', ' + err.message +
' in query "' + err.query +
'" with values ' + JSON.stringify(data, false, 4);
return err;
};
*/
\ No newline at end of file
...@@ -38,259 +38,196 @@ void Database::Init(v8::Handle<Object> target) { ...@@ -38,259 +38,196 @@ void Database::Init(v8::Handle<Object> target) {
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, "openSync", OpenSync);
NODE_SET_PROTOTYPE_METHOD(constructor_template, "close", Close); NODE_SET_PROTOTYPE_METHOD(constructor_template, "close", Close);
NODE_SET_PROTOTYPE_METHOD(constructor_template, "closeSync", CloseSync);
NODE_SET_PROTOTYPE_METHOD(constructor_template, "prepare", Prepare);
target->Set(v8::String::NewSymbol("Database"), target->Set(v8::String::NewSymbol("Database"),
constructor_template->GetFunction()); constructor_template->GetFunction());
// insert/update execution result mask
NODE_DEFINE_CONSTANT(target, EXEC_EMPTY);
NODE_DEFINE_CONSTANT(target, EXEC_LAST_INSERT_ID);
NODE_DEFINE_CONSTANT(target, EXEC_AFFECTED_ROWS);
} }
Handle<Value> Database::New(const Arguments& args) { void Database::Process(Database* db) {
HandleScope scope; if (!db->open && db->locked && !db->queue.empty()) {
EXCEPTION(String::New("Database is closed"), SQLITE_MISUSE, exception);
REQUIRE_ARGUMENT_STRING(0, filename); Local<Value> argv[] = { String::NewSymbol("error"), exception };
OPTIONAL_ARGUMENT_INTEGER(1, mode, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE); EMIT_EVENT(db->handle_, 2, argv);
return;
Database* db = new Database(); }
db->filename = std::string(*filename);
db->open_mode = mode;
args.This()->Set(String::NewSymbol("filename"), args[0]->ToString(), ReadOnly);
args.This()->Set(String::NewSymbol("mode"), Integer::New(mode), ReadOnly);
db->Wrap(args.This());
return args.This();
}
void Database::ProcessQueue(Database* db) { while (db->open && !db->locked && !db->queue.empty()) {
while (!db->queue.empty()) {
Call* call = db->queue.front(); Call* call = db->queue.front();
if (!(call->Data() & db->status)) { if (call->exclusive && db->pending > 0) {
// The next task in the queue requires a different status than the
// one we're currently in. Wait before invoking it.
if (db->pending == 0) {
EXCEPTION("Invalid function call sequence", SQLITE_MISUSE, exception);
Local<Value> argv[] = { String::NewSymbol("error"), exception };
Local<Function> fn = Local<Function>::Cast(db->handle_->Get(String::NewSymbol("emit")));
TryCatch try_catch;
fn->Call(db->handle_, 2, argv);
if (try_catch.HasCaught()) {
FatalException(try_catch);
}
ev_unref(EV_DEFAULT_UC);
db->Unref();
db->queue.pop();
delete call;
}
break;
}
if (call->Mode() == Deferred::Exclusive && db->pending > 0) {
// We have to wait for the pending tasks to complete before we
// execute the exclusive task.
// break;
break; break;
} }
ev_unref(EV_DEFAULT_UC); call->callback(call->baton);
db->Unref();
TryCatch try_catch;
call->Invoke();
if (try_catch.HasCaught()) {
FatalException(try_catch);
}
db->queue.pop(); db->queue.pop();
delete call; delete call;
} }
} }
Handle<Value> Database::Open(const Arguments& args) { inline void Database::Schedule(Database* db, EIO_Callback callback, Baton* baton,
HandleScope scope; bool exclusive = false) {
Database* db = ObjectWrap::Unwrap<Database>(args.This()); if (!db->open && db->locked) {
EXCEPTION(String::New("Database is closed"), SQLITE_MISUSE, exception);
REQUIRE_ARGUMENT_FUNCTION(0, callback); if (!(baton)->callback.IsEmpty()) {
Local<Value> argv[] = { exception };
// Make sure that node doesn't exit before all elements in the queue have TRY_CATCH_CALL(db->handle_, (baton)->callback, 1, argv);
// been dealt with. }
db->Ref(); else {
ev_ref(EV_DEFAULT_UC); Local<Value> argv[] = { String::NewSymbol("error"), exception };
EMIT_EVENT(db->handle_, 2, argv);
if (db->status != IsClosed) { }
db->queue.push(new Call(args, IsClosed, Deferred::Exclusive)); return;
}
else {
db->status = IsOpening;
db->pending++;
Baton* baton = new Baton(db, Persistent<Function>::New(callback));
eio_custom(EIO_Open, EIO_PRI_DEFAULT, EIO_AfterOpen, baton);
} }
return args.This(); if (!db->open || db->locked || (exclusive && db->pending > 0)) {
} db->queue.push(new Call(callback, baton, exclusive));
bool Database::Open(Database* db) {
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));
return false;
} }
else { else {
return true; callback(baton);
} }
} }
Handle<Value> Database::OpenSync(const Arguments& args) { Handle<Value> Database::New(const Arguments& args) {
HandleScope scope; HandleScope scope;
Database* db = ObjectWrap::Unwrap<Database>(args.This());
REQUIRE_ARGUMENT_FUNCTION(0, callback); if (!args.IsConstructCall()) {
return ThrowException(Exception::TypeError(
String::New("Use the new keyword to create new Database objects"))
);
}
if (db->status == IsClosed) { REQUIRE_ARGUMENT_STRING(0, filename);
Local<Value> argv[1]; int pos = 1;
if (Open(db)) {
db->status = IsOpen;
argv[0] = Local<Value>::New(Null());
}
else {
EXCEPTION(db->error_message.c_str(), db->error_status, exception);
argv[0] = exception;
}
TRY_CATCH_CALL(args.This(), callback, 1, argv); int mode = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
ProcessQueue(db); if (args.Length() >= pos && args[pos]->IsInt32()) {
mode = args[pos++]->Int32Value();
} }
else {
return ThrowException(Exception::Error( Local<Function> callback;
String::New("Database is already open"))); if (args.Length() >= pos && args[pos]->IsFunction()) {
callback = Local<Function>::Cast(args[pos++]);
} }
Database* db = new Database();
db->Wrap(args.This());
args.This()->Set(String::NewSymbol("filename"), args[0]->ToString(), ReadOnly);
args.This()->Set(String::NewSymbol("mode"), Integer::New(mode), ReadOnly);
// Start opening the database.
OpenBaton* baton = new OpenBaton();
baton->db = db;
baton->callback = Persistent<Function>::New(callback);
baton->filename = *filename;
baton->mode = SQLITE_OPEN_FULLMUTEX | mode;
EIO_BeginOpen(baton);
return args.This(); return args.This();
} }
void Database::EIO_BeginOpen(Baton* baton) {
baton->db->Ref();
ev_ref(EV_DEFAULT_UC);
fprintf(stderr, "Open started\n");
eio_custom(EIO_Open, EIO_PRI_DEFAULT, EIO_AfterOpen, baton);
}
int Database::EIO_Open(eio_req *req) { int Database::EIO_Open(eio_req *req) {
Baton* baton = static_cast<Baton*>(req->data); OpenBaton* baton = static_cast<OpenBaton*>(req->data);
Open(baton->db); Database* db = baton->db;
fprintf(stderr, "Open performed\n");
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));
}
return 0; return 0;
} }
int Database::EIO_AfterOpen(eio_req *req) { int Database::EIO_AfterOpen(eio_req *req) {
HandleScope scope; HandleScope scope;
Baton* baton = static_cast<Baton*>(req->data); OpenBaton* baton = static_cast<OpenBaton*>(req->data);
Database* db = baton->db; Database* db = baton->db;
ev_unref(EV_DEFAULT_UC);
db->Unref(); db->Unref();
db->pending--; ev_unref(EV_DEFAULT_UC);
Local<Value> argv[1]; Local<Value> argv[1];
if (db->error_status == SQLITE_OK) { if (baton->status != SQLITE_OK) {
db->status = IsOpen; EXCEPTION(String::New(baton->message.c_str()), baton->status, exception);
argv[0] = Local<Value>::New(Null()); argv[0] = exception;
} }
else { else {
db->status = IsClosed; db->open = true;
EXCEPTION(db->error_message.c_str(), db->error_status, exception); argv[0] = Local<Value>::New(Null());
argv[0] = exception;
} }
TRY_CATCH_CALL(db->handle_, baton->callback, 1, argv); if (!baton->callback.IsEmpty()) {
ProcessQueue(db); TRY_CATCH_CALL(db->handle_, baton->callback, 1, argv);
}
else if (!db->open) {
Local<Value> args[] = { String::NewSymbol("error"), argv[0] };
EMIT_EVENT(db->handle_, 2, args);
}
baton->callback.Dispose(); if (db->open) {
Local<Value> args[] = { String::NewSymbol("open") };
EMIT_EVENT(db->handle_, 1, args);
fprintf(stderr, "Open completed\n");
Process(db);
}
delete baton; delete baton;
return 0; return 0;
} }
Handle<Value> Database::Close(const Arguments& args) { Handle<Value> Database::Close(const Arguments& args) {
HandleScope scope; HandleScope scope;
Database* db = ObjectWrap::Unwrap<Database>(args.This()); Database* db = ObjectWrap::Unwrap<Database>(args.This());
OPTIONAL_ARGUMENT_FUNCTION(0, callback);
REQUIRE_ARGUMENT_FUNCTION(0, callback);
// Make sure that node doesn't exit before all elements in the queue have
// been dealt with.
db->Ref(); db->Ref();
ev_ref(EV_DEFAULT_UC); ev_ref(EV_DEFAULT_UC);
if (db->status != IsOpen || db->pending > 0) { Baton* baton = new Baton();
db->queue.push(new Call(args, IsOpen, Deferred::Exclusive)); baton->db = db;
} baton->callback = Persistent<Function>::New(callback);
else { Schedule(db, EIO_BeginClose, baton, true);
db->status = IsClosing;
db->pending++;
Baton* baton = new Baton(db, Persistent<Function>::New(callback));
eio_custom(EIO_Close, EIO_PRI_DEFAULT, EIO_AfterClose, baton);
}
return args.This(); return args.This();
} }
bool Database::Close(Database* db) { void Database::EIO_BeginClose(Baton* baton) {
assert(db->handle); assert(baton->db->open);
db->error_status = sqlite3_close(db->handle); assert(!baton->db->locked);
assert(baton->db->pending == 0);
if (db->error_status != SQLITE_OK) { fprintf(stderr, "Close started\n");
db->error_message = std::string(sqlite3_errmsg(db->handle)); baton->db->locked = true;
return false; eio_custom(EIO_Close, EIO_PRI_DEFAULT, EIO_AfterClose, baton);
}
else {
db->handle = NULL;
return true;
}
} }
int Database::EIO_Close(eio_req *req) {
Baton* baton = static_cast<Baton*>(req->data);
Database* db = baton->db;
Handle<Value> Database::CloseSync(const Arguments& args) { fprintf(stderr, "Close performed\n");
HandleScope scope; baton->status = sqlite3_close(db->handle);
Database* db = ObjectWrap::Unwrap<Database>(args.This());
REQUIRE_ARGUMENT_FUNCTION(0, callback);
if (db->status == IsOpen && db->pending == 0) { if (baton->status != SQLITE_OK) {
Local<Value> argv[1]; baton->message = std::string(sqlite3_errmsg(db->handle));
if (Close(db)) {
db->status = IsClosed;
argv[0] = Local<Value>::New(Null());
}
else {
EXCEPTION(db->error_message.c_str(), db->error_status, exception);
argv[0] = exception;
}
TRY_CATCH_CALL(args.This(), callback, 1, argv);
ProcessQueue(db);
} }
else { else {
db->queue.push(new Call(args, IsOpen, Deferred::Exclusive)); db->handle = NULL;
} }
return args.This();
}
int Database::EIO_Close(eio_req *req) {
Baton* baton = static_cast<Baton*>(req->data);
Close(baton->db);
return 0; return 0;
} }
...@@ -301,331 +238,41 @@ int Database::EIO_AfterClose(eio_req *req) { ...@@ -301,331 +238,41 @@ int Database::EIO_AfterClose(eio_req *req) {
ev_unref(EV_DEFAULT_UC); ev_unref(EV_DEFAULT_UC);
db->Unref(); db->Unref();
db->pending--;
Local<Value> argv[1]; Local<Value> argv[1];
if (db->error_status != SQLITE_OK) { if (baton->status != SQLITE_OK) {
db->status = IsOpen; EXCEPTION(String::New(baton->message.c_str()), baton->status, exception);
EXCEPTION(db->error_message.c_str(), db->error_status, exception);
argv[0] = exception; argv[0] = exception;
} }
else { else {
db->status = IsClosed; db->open = false;
// Leave db->locked to indicate that this db object has reached
// the end of its life.
argv[0] = Local<Value>::New(Null()); argv[0] = Local<Value>::New(Null());
} }
TRY_CATCH_CALL(db->handle_, baton->callback, 1, argv); // Fire callbacks.
ProcessQueue(db); if (!baton->callback.IsEmpty()) {
baton->callback.Dispose(); TRY_CATCH_CALL(db->handle_, baton->callback, 1, argv);
delete baton;
return 0;
}
// // TODO: libeio'fy
// Hooks
// static int CommitHook(void* v_this) {
// HandleScope scope;
// Database* db = static_cast<Database*>(v_this);
// db->Emit(String::New("commit"), 0, NULL);
// // TODO: allow change in return value to convert to rollback...somehow
// return 0;
// }
//
// static void RollbackHook(void* v_this) {
// HandleScope scope;
// Database* db = static_cast<Database*>(v_this);
// db->Emit(String::New("rollback"), 0, NULL);
// }
//
// static void UpdateHook(void* v_this, int operation, const char* database,
// const char* table, sqlite_int64 rowid) {
// HandleScope scope;
// Database* db = static_cast<Database*>(v_this);
// Local<Value> args[] = { Int32::New(operation), String::New(database),
// String::New(table), Number::New(rowid) };
// db->Emit(String::New("update"), 4, args);
// }
int Database::EIO_AfterPrepareAndStep(eio_req *req) {
ev_unref(EV_DEFAULT_UC);
struct prepare_request *prep_req = (struct prepare_request *)(req->data);
HandleScope scope;
Local<Value> argv[2];
int argc = 0;
// if the prepare failed
if (req->result != SQLITE_OK) {
argv[0] = Exception::Error(
String::New(sqlite3_errmsg(prep_req->db->handle)));
argc = 1;
}
else {
if (req->int1 == SQLITE_DONE) {
if (prep_req->mode != EXEC_EMPTY) {
argv[0] = Local<Value>::New(Undefined()); // no error
Local<Object> info = Object::New();
if (prep_req->mode & EXEC_LAST_INSERT_ID) {
info->Set(String::NewSymbol("last_inserted_id"),
Integer::NewFromUnsigned (prep_req->lastInsertId));
}
if (prep_req->mode & EXEC_AFFECTED_ROWS) {
info->Set(String::NewSymbol("affected_rows"),
Integer::New (prep_req->affectedRows));
}
argv[1] = info;
argc = 2;
} else {
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;
}
}
TryCatch try_catch;
prep_req->db->Unref();
prep_req->cb->Call(Context::GetCurrent()->Global(), argc, argv);
if (try_catch.HasCaught()) {
FatalException(try_catch);
} }
else if (db->open) {
prep_req->cb.Dispose(); Local<Value> args[] = { String::NewSymbol("error"), argv[0] };
free(prep_req); EMIT_EVENT(db->handle_, 2, args);
return 0;
}
int Database::EIO_PrepareAndStep(eio_req *req) {
struct prepare_request *prep_req = (struct prepare_request *)(req->data);
prep_req->stmt = NULL;
prep_req->tail = NULL;
sqlite3* db = prep_req->db->handle;
int rc = sqlite3_prepare_v2(db, prep_req->sql, -1,
&(prep_req->stmt), &(prep_req->tail));
req->result = rc;
req->int1 = -1;
// 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
// possible in the case where there are no variable placeholders/bindings
// in the SQL.
if (rc == SQLITE_OK && !sqlite3_bind_parameter_count(prep_req->stmt)) {
rc = sqlite3_step(prep_req->stmt);
req->int1 = rc;
// no more rows to return, clean up statement
if (rc == SQLITE_DONE) {
rc = sqlite3_finalize(prep_req->stmt);
prep_req->stmt = NULL;
assert(rc == SQLITE_OK);
}
} }
prep_req->lastInsertId = 0; if (!db->open) {
prep_req->affectedRows = 0; Local<Value> args[] = { String::NewSymbol("close"), argv[0] };
EMIT_EVENT(db->handle_, 1, args);
// load custom properties Process(db);
if (prep_req->mode & EXEC_LAST_INSERT_ID)
prep_req->lastInsertId = sqlite3_last_insert_rowid(db);
if (prep_req->mode & EXEC_AFFECTED_ROWS)
prep_req->affectedRows = sqlite3_changes(db);
return 0;
}
Handle<Value> Database::PrepareAndStep(const Arguments& args) {
HandleScope scope;
REQUIRE_ARGUMENT_STRING(0, sql);
REQUIRE_ARGUMENT_FUNCTION(1, cb);
OPTIONAL_ARGUMENT_INTEGER(2, mode, EXEC_EMPTY);
Database* db = ObjectWrap::Unwrap<Database>(args.This());
struct prepare_request *prep_req = (struct prepare_request *)
calloc(1, sizeof(struct prepare_request) + sql.length());
if (!prep_req) {
V8::LowMemoryNotification();
return ThrowException(Exception::Error(
String::New("Could not allocate enough memory")));
} }
strcpy(prep_req->sql, *sql); delete baton;
prep_req->cb = Persistent<Function>::New(cb);
prep_req->db = db; fprintf(stderr, "Close completed\n");
prep_req->mode = mode;
eio_custom(EIO_PrepareAndStep, EIO_PRI_DEFAULT, EIO_AfterPrepareAndStep, prep_req);
ev_ref(EV_DEFAULT_UC);
db->Ref();
return Undefined();
}
int Database::EIO_AfterPrepare(eio_req *req) {
ev_unref(EV_DEFAULT_UC);
struct prepare_request *prep_req = (struct prepare_request *)(req->data);
HandleScope scope;
Local<Value> argv[3];
int argc = 0;
// if the prepare failed
if (req->result != SQLITE_OK) {
argv[0] = Exception::Error(
String::New(sqlite3_errmsg(prep_req->db->handle)));
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));
}
argc = 2;
argv[0] = Local<Value>::New(Undefined());
argv[1] = Local<Value>::New(statement);
}
TryCatch try_catch;
prep_req->db->Unref();
prep_req->cb->Call(Context::GetCurrent()->Global(), argc, argv);
if (try_catch.HasCaught()) {
FatalException(try_catch);
}
prep_req->cb.Dispose();
free(prep_req);
return 0;
}
int Database::EIO_Prepare(eio_req *req) {
struct prepare_request *prep_req = (struct prepare_request *)(req->data);
prep_req->stmt = NULL;
prep_req->tail = NULL;
sqlite3* db = prep_req->db->handle;
int rc = sqlite3_prepare_v2(db, prep_req->sql, -1,
&(prep_req->stmt), &(prep_req->tail));
req->result = rc;
prep_req->lastInsertId = 0;
prep_req->affectedRows = 0;
// load custom properties
if (prep_req->mode & EXEC_LAST_INSERT_ID)
prep_req->lastInsertId = sqlite3_last_insert_rowid(db);
if (prep_req->mode & EXEC_AFFECTED_ROWS)
prep_req->affectedRows = sqlite3_changes(db);
return 0; return 0;
} }
// Statement#prepare(sql, [ options ,] callback);
Handle<Value> Database::Prepare(const Arguments& args) {
HandleScope scope;
Local<Object> options;
Local<Function> cb;
int mode;
REQUIRE_ARGUMENT_STRING(0, sql);
// middle argument could be options or
switch (args.Length()) {
case 2:
if (!args[1]->IsFunction()) {
return ThrowException(Exception::TypeError(
String::New("Argument 1 must be a function")));
}
cb = Local<Function>::Cast(args[1]);
options = Object::New();
break;
case 3:
if (!args[1]->IsObject()) {
return ThrowException(Exception::TypeError(
String::New("Argument 1 must be an object")));
}
options = Local<Function>::Cast(args[1]);
if (!args[2]->IsFunction()) {
return ThrowException(Exception::TypeError(
String::New("Argument 2 must be a function")));
}
cb = Local<Function>::Cast(args[2]);
break;
}
mode = EXEC_EMPTY;
if (options->Get(String::New("lastInsertRowID"))->IsTrue()) {
mode |= EXEC_LAST_INSERT_ID;
}
if (options->Get(String::New("affectedRows"))->IsTrue()) {
mode |= EXEC_AFFECTED_ROWS;
}
Database* db = ObjectWrap::Unwrap<Database>(args.This());
struct prepare_request *prep_req = (struct prepare_request *)
calloc(1, sizeof(struct prepare_request) + sql.length());
if (!prep_req) {
V8::LowMemoryNotification();
return ThrowException(Exception::Error(
String::New("Could not allocate enough memory")));
}
strcpy(prep_req->sql, *sql);
prep_req->cb = Persistent<Function>::New(cb);
prep_req->db = db;
prep_req->mode = mode;
eio_custom(EIO_Prepare, EIO_PRI_DEFAULT, EIO_AfterPrepare, prep_req);
ev_ref(EV_DEFAULT_UC);
db->Ref();
return Undefined();
}
/** /**
* Override this so that we can properly close the database when this object * Override this so that we can properly close the database when this object
* gets garbage collected. * gets garbage collected.
......
...@@ -19,8 +19,6 @@ ...@@ -19,8 +19,6 @@
#include <node.h> #include <node.h>
#include <node_events.h> #include <node_events.h>
#include "deferred_call.h"
#include <string> #include <string>
#include <queue> #include <queue>
...@@ -29,34 +27,52 @@ ...@@ -29,34 +27,52 @@
using namespace v8; using namespace v8;
using namespace node; using namespace node;
class Database;
static struct Baton {
Database* db;
Persistent<Function> callback;
int status;
std::string message;
~Baton() {
callback.Dispose();
}
};
static struct OpenBaton : Baton {
std::string filename;
int mode;
};
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);
static enum Status { static inline bool HasInstance(Handle<Value> val) {
IsClosed = 1 << 0, if (!val->IsObject()) return false;
IsOpening = 1 << 1, Local<Object> obj = val->ToObject();
IsOpen = 1 << 2, return constructor_template->HasInstance(obj);
IsClosing = 1 << 3, }
DoesntMatter = IsClosed | IsOpening | IsOpen | IsClosing
};
typedef Deferred::Call<Status> Call; typedef void (*EIO_Callback)(Baton* baton);
struct Baton { struct Call {
Baton(Database* db_, Persistent<Function> callback_) : Call(EIO_Callback callback_, Baton* baton_, bool exclusive_ = false) :
db(db_), callback(callback_) {}; callback(callback_), exclusive(exclusive_), baton(baton_) {};
Database* db; EIO_Callback callback;
Persistent<Function> callback; bool exclusive;
Baton* baton;
}; };
friend class Statement;
protected: protected:
Database() : EventEmitter(), Database() : EventEmitter(),
handle(NULL), handle(NULL),
pending(0), open(false),
status(IsClosed) { locked(false),
pending(0) {
} }
...@@ -65,64 +81,32 @@ class Database : public EventEmitter { ...@@ -65,64 +81,32 @@ class Database : public EventEmitter {
} }
static Handle<Value> New(const Arguments& args); static Handle<Value> New(const Arguments& args);
static void EIO_BeginOpen(Baton* baton);
static void ProcessQueue(Database* db);
static Handle<Value> OpenSync(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_Open(eio_req *req);
static int EIO_AfterOpen(eio_req *req); static int EIO_AfterOpen(eio_req *req);
static Handle<Value> CloseSync(const Arguments& args); static void Schedule(Database* db, EIO_Callback callback, Baton* baton,
bool exclusive);
static void Process(Database* db);
static Handle<Value> Close(const Arguments& args); static Handle<Value> Close(const Arguments& args);
static bool Close(Database* db); static void EIO_BeginClose(Baton* baton);
static int EIO_Close(eio_req *req); static int EIO_Close(eio_req *req);
static int EIO_AfterClose(eio_req *req); static int EIO_AfterClose(eio_req *req);
static int EIO_AfterPrepareAndStep(eio_req *req);
static int EIO_PrepareAndStep(eio_req *req);
static Handle<Value> PrepareAndStep(const Arguments& args);
static int EIO_AfterPrepare(eio_req *req);
static int EIO_Prepare(eio_req *req);
static Handle<Value> Prepare(const Arguments& args);
void Wrap (Handle<Object> handle); void Wrap (Handle<Object> handle);
static void Destruct (Persistent<Value> value, void *data); static void Destruct (Persistent<Value> value, void *data);
static int EIO_Destruct(eio_req *req); static int EIO_Destruct(eio_req *req);
static int EIO_AfterDestruct(eio_req *req); static int EIO_AfterDestruct(eio_req *req);
protected: protected:
sqlite3* handle; sqlite3* handle;
std::string filename;
int open_mode;
std::string error_message; bool open;
int error_status; bool locked;
unsigned int pending;
int pending;
Status status;
std::queue<Call*> queue; std::queue<Call*> queue;
private:
};
enum ExecMode {
EXEC_EMPTY = 0,
EXEC_LAST_INSERT_ID = 1,
EXEC_AFFECTED_ROWS = 2
};
struct prepare_request {
Persistent<Function> cb;
Database *db;
sqlite3_stmt* stmt;
int mode;
sqlite3_int64 lastInsertId;
int affectedRows;
const char* tail;
char sql[1];
}; };
#endif #endif
...@@ -121,7 +121,7 @@ const char* sqlite_code_string(int code); ...@@ -121,7 +121,7 @@ const char* sqlite_code_string(int code);
String::NewSymbol(sqlite_code_string(errno)), \ String::NewSymbol(sqlite_code_string(errno)), \
String::NewSymbol(": ") \ String::NewSymbol(": ") \
), \ ), \
String::New(msg) \ (msg) \
) \ ) \
); \ ); \
Local<Object> name ##_obj = name->ToObject(); \ Local<Object> name ##_obj = name->ToObject(); \
...@@ -129,30 +129,19 @@ const char* sqlite_code_string(int code); ...@@ -129,30 +129,19 @@ const char* sqlite_code_string(int code);
name ##_obj->Set(NODE_PSYMBOL("code"), \ name ##_obj->Set(NODE_PSYMBOL("code"), \
String::NewSymbol(sqlite_code_string(errno))); String::NewSymbol(sqlite_code_string(errno)));
#define EVENT_ONCE(event, callback) \
Local<Value> argv[2] = { \ #define EMIT_EVENT(obj, argc, argv) \
String::NewSymbol(event), \ TRY_CATCH_CALL((obj), \
args.This()->Get(String::NewSymbol(callback)) \ Local<Function>::Cast((obj)->Get(String::NewSymbol("emit"))), \
}; \ argc, argv \
v8::Local<v8::Value> fn_val = args.This()->Get(String::NewSymbol("once")); \ );
Local<Function> fn = Local<Function>::Cast(fn_val); \
fn->Call(args.This(), 2, argv);
#define SET_STRING(sym, str) \
Set(String::NewSymbol(#sym), String::New(str), ReadOnly)
#define SET_INTEGER(sym, i) \
Set(String::NewSymbol(#sym), Integer::New(i), ReadOnly)
#define TRY_CATCH_CALL(context, callback, argc, argv) \ #define TRY_CATCH_CALL(context, callback, argc, argv) \
{ \ { TryCatch try_catch; \
TryCatch try_catch; \
(callback)->Call((context), (argc), (argv)); \ (callback)->Call((context), (argc), (argv)); \
if (try_catch.HasCaught()) { \ if (try_catch.HasCaught()) { \
FatalException(try_catch); \ FatalException(try_catch); \
} \ } }
}
#endif #endif
...@@ -13,1038 +13,166 @@ ...@@ -13,1038 +13,166 @@
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#include <string.h> #include <string.h>
#include <v8.h>
#include <node.h>
#include <node_events.h>
extern "C" { #include "macros.h"
#include <mpool.h>
};
#include "database.h" #include "database.h"
#include "statement.h" #include "statement.h"
#include "macros.h" #include "deferred_call.h"
static Persistent<String> callback_sym;
Persistent<FunctionTemplate> Statement::constructor_template; Persistent<FunctionTemplate> Statement::constructor_template;
void Statement::Init(v8::Handle<Object> target) { void Statement::Init(v8::Handle<Object> target) {
HandleScope scope; HandleScope scope;
Local<FunctionTemplate> t = FunctionTemplate::New(New);
constructor_template = Persistent<FunctionTemplate>::New(t);
constructor_template->Inherit(EventEmitter::constructor_template);
constructor_template->InstanceTemplate()->SetInternalFieldCount(1);
constructor_template->SetClassName(String::NewSymbol("Statement"));
NODE_SET_PROTOTYPE_METHOD(t, "bind", Bind); Local<FunctionTemplate> t = FunctionTemplate::New(New);
NODE_SET_PROTOTYPE_METHOD(t, "bindObject", BindObject);
NODE_SET_PROTOTYPE_METHOD(t, "bindArray", BindArray);
NODE_SET_PROTOTYPE_METHOD(t, "finalize", Finalize); constructor_template = Persistent<FunctionTemplate>::New(t);
NODE_SET_PROTOTYPE_METHOD(t, "reset", Reset); constructor_template->Inherit(EventEmitter::constructor_template);
NODE_SET_PROTOTYPE_METHOD(t, "clearBindings", ClearBindings); constructor_template->InstanceTemplate()->SetInternalFieldCount(1);
NODE_SET_PROTOTYPE_METHOD(t, "step", Step); constructor_template->SetClassName(String::NewSymbol("Statement"));
NODE_SET_PROTOTYPE_METHOD(t, "fetchAll", FetchAll);
callback_sym = Persistent<String>::New(String::New("callback")); target->Set(v8::String::NewSymbol("Statement"),
constructor_template->GetFunction());
} }
// { Database db, String sql, Array params, Function callback }
Handle<Value> Statement::New(const Arguments& args) { Handle<Value> Statement::New(const Arguments& args) {
HandleScope scope; HandleScope scope;
REQUIRE_ARGUMENT_EXTERNAL(0, stmt);
int first_rc = args[1]->IntegerValue();
int mode = args[2]->IntegerValue();
Statement *sto = new Statement((sqlite3_stmt*)stmt->Value(), first_rc, mode);
sto->Wrap(args.This());
sto->Ref();
return args.This();
}
int Statement::EIO_AfterBindArray(eio_req *req) {
ev_unref(EV_DEFAULT_UC);
HandleScope scope;
struct bind_request *bind_req = (struct bind_request *)(req->data);
Local<Value> argv[1];
bool err = false;
if (req->result) {
err = true;
argv[0] = Exception::Error(String::New("Error binding parameter"));
}
TryCatch try_catch;
bind_req->cb->Call(Context::GetCurrent()->Global(), err ? 1 : 0, argv);
if (try_catch.HasCaught()) {
FatalException(try_catch);
}
bind_req->cb.Dispose();
struct bind_pair *pair = bind_req->pairs;
for (size_t i = 0; i < bind_req->len; i++, pair++) {
free((char*)(pair->key));
switch(pair->key_type) {
case KEY_INT:
free((int*)(pair->value));
break;
case KEY_STRING:
free((char*)(pair->value));
break;
}
}
free(bind_req->pairs);
free(bind_req);
return 0;
}
int Statement::EIO_BindArray(eio_req *req) {
struct bind_request *bind_req = (struct bind_request *)(req->data);
Statement *sto = bind_req->sto;
int rc(0);
struct bind_pair *pair = bind_req->pairs;
int index = 0;
for (size_t i = 0; i < bind_req->len; i++, pair++) {
switch(pair->key_type) {
case KEY_INT:
index = *(int*)(pair->key);
break;
case KEY_STRING:
index = sqlite3_bind_parameter_index(sto->stmt_,
(char*)(pair->key));
break;
default: {
// this SHOULD be unreachable
}
}
if (!index) {
req->result = SQLITE_MISMATCH;
return 0;
}
int rc = 0;
switch(pair->value_type) {
case VALUE_INT:
rc = sqlite3_bind_int(sto->stmt_, index, *(int*)(pair->value));
break;
case VALUE_DOUBLE:
rc = sqlite3_bind_double(sto->stmt_, index, *(double*)(pair->value));
break;
case VALUE_STRING:
rc = sqlite3_bind_text(sto->stmt_, index, (char*)(pair->value),
pair->value_size, SQLITE_TRANSIENT);
break;
case VALUE_BLOB:
rc = sqlite3_bind_blob(sto->stmt_, index, (char*)(pair->value),
pair->value_size, SQLITE_TRANSIENT);
break;
case VALUE_NULL:
rc = sqlite3_bind_null(sto->stmt_, index);
break;
// should be unreachable
}
}
if (rc) { // if (args.Length() < 1 || !args[0]->IsString()) {
req->result = rc; // return ThrowException(Exception::TypeError(
// String::New("First argument must be a SQL query"))
// );
// }
//
// Local<Function> callback;
// int last = args.Length() - 1;
// if (args.Length() >= 2 && args[last]->IsFunction()) {
// callback = Local<Function>::Cast(args[last--]);
// }
//
// // Process any optional arguments
// for (int i = 1; i <= last; i++) {
//
// }
int length = args.Length();
if (length <= 0 || !Database::HasInstance(args[0])) {
return ThrowException(Exception::TypeError(
String::New("First argument must be a Database object")));
}
else if (length <= 1 || !args[1]->IsString()) {
return ThrowException(Exception::TypeError(
String::New("Second argument must be a SQL query")));
}
else if (length <= 2 || !args[2]->IsObject()) {
return ThrowException(Exception::TypeError(
String::New("Third argument must be an array or object of parameters")));
}
else if (length > 3 && !args[3]->IsUndefined() && !args[3]->IsFunction()) {
return ThrowException(Exception::TypeError(
String::New("Fourth argument must be a function")));
}
Database* db = ObjectWrap::Unwrap<Database>(args[0]->ToObject());
Local<String> sql = Local<String>::Cast(args[1]);
args.This()->Set(String::NewSymbol("sql"), Persistent<String>::New(sql), ReadOnly);
Statement* stmt = new Statement(db);
stmt->Wrap(args.This());
PrepareBaton* baton = new PrepareBaton();
baton->db = db;
baton->stmt = stmt;
baton->callback = Persistent<Function>::New(Local<Function>::Cast(args[3]));
baton->sql = std::string(*String::Utf8Value(sql));
Database::Schedule(db, EIO_BeginPrepare, baton, false);
return args.This();
}
void Statement::EIO_BeginPrepare(Baton* baton) {
assert(baton->db->open);
assert(!baton->db->locked);
baton->db->pending++;
static_cast<PrepareBaton*>(baton)->stmt->Ref();
ev_ref(EV_DEFAULT_UC);
fprintf(stderr, "Prepare started\n");
eio_custom(EIO_Prepare, EIO_PRI_DEFAULT, EIO_AfterPrepare, baton);
}
int Statement::EIO_Prepare(eio_req *req) {
PrepareBaton* baton = static_cast<PrepareBaton*>(req->data);
Database* db = baton->db;
fprintf(stderr, "Prepare performed\n");
// baton->status = sqlite3_prepare16_v2(
// db->handle,
// const void *zSql, /* SQL statement, UTF-16 encoded */
// int nByte, /* Maximum length of zSql in bytes. */
// sqlite3_stmt **ppStmt, /* OUT: Statement handle */
// const void **pzTail /* OUT: Pointer to unused portion of zSql */
// );
//
// baton->status = sqlite3_close(db->handle);
//
// if (baton->status != SQLITE_OK) {
// baton->message = std::string(sqlite3_errmsg(db->handle));
// }
// else {
// db->handle = NULL;
// }
return 0; return 0;
}
return 0;
}
Handle<Value> Statement::BindObject(const Arguments& args) {
HandleScope scope;
Statement* sto = ObjectWrap::Unwrap<Statement>(args.This());
REQUIRE_ARGUMENTS(2);
REQUIRE_ARGUMENT_FUNCTION(1, cb);
if (! args[0]->IsObject())
return ThrowException(Exception::TypeError(
String::New("First argument must be an Array.")));
Local<Object> obj = args[0]->ToObject();
Local<Array> properties = obj->GetPropertyNames();
struct bind_request *bind_req = (struct bind_request *)
calloc(1, sizeof(struct bind_request));
int len = bind_req->len = properties->Length();
bind_req->pairs = (struct bind_pair *)
calloc(len, sizeof(struct bind_pair));
struct bind_pair *pairs = bind_req->pairs;
for (uint32_t i = 0; i < properties->Length(); i++, pairs++) {
Local<Value> name = properties->Get(Integer::New(i));
Local<Value> val = obj->Get(name->ToString());
String::Utf8Value keyValue(name);
// setting key type
pairs->key_type = KEY_STRING;
char *key = (char *) calloc(1, keyValue.length()+1);
memcpy(key, *keyValue, keyValue.length()+1);
pairs->key = key;
// setup value
if (val->IsInt32()) {
pairs->value_type = VALUE_INT;
int *value = (int *) malloc(sizeof(int));
*value = val->Int32Value();
pairs->value = value;
}
else if (val->IsNumber()) {
pairs->value_type = VALUE_DOUBLE;
double *value = (double *) malloc(sizeof(double));
*value = val->NumberValue();
pairs->value = value;
}
else if (val->IsString()) {
pairs->value_type = VALUE_STRING;
String::Utf8Value text(val);
char *value = (char *) calloc(text.length(), sizeof(char));
memcpy(value, *text, text.length());
pairs->value = value;
pairs->value_size = text.length();
}
else if (Buffer::HasInstance(val)) {
pairs->value_type = VALUE_BLOB;
Buffer* buffer = Buffer::Unwrap<Buffer>(val->ToObject());
char *value = (char *) malloc(buffer->length());
memcpy(value, buffer->data(), buffer->length());
pairs->value = value;
pairs->value_size = buffer->length();
}
else if (val->IsNull() || val->IsUndefined()) {
pairs->value_type = VALUE_NULL;
pairs->value = NULL;
}
else {
free(pairs->key);
return ThrowException(Exception::TypeError(
String::New("Unable to bind value of this type")));
}
}
bind_req->cb = Persistent<Function>::New(cb);
bind_req->sto = sto;
eio_custom(EIO_BindArray, EIO_PRI_DEFAULT, EIO_AfterBindArray, bind_req);
ev_ref(EV_DEFAULT_UC);
return Undefined();
};
Handle<Value> Statement::BindArray(const Arguments& args) {
HandleScope scope;
Statement* sto = ObjectWrap::Unwrap<Statement>(args.This());
REQUIRE_ARGUMENTS(2);
REQUIRE_ARGUMENT_FUNCTION(1, cb);
if (! args[0]->IsArray())
return ThrowException(Exception::TypeError(
String::New("First argument must be an Array.")));
struct bind_request *bind_req = (struct bind_request *)
calloc(1, sizeof(struct bind_request));
Local<Array> array = Local<Array>::Cast(args[0]);
int len = bind_req->len = array->Length();
bind_req->pairs = (struct bind_pair *)
calloc(len, sizeof(struct bind_pair));
struct bind_pair *pairs = bind_req->pairs;
// pack the binds into the struct
for (int i = 0; i < len; i++, pairs++) {
Local<Value> val = array->Get(i);
// setting key type
pairs->key_type = KEY_INT;
int *index = (int *) malloc(sizeof(int));
*index = i+1;
// don't forget to `free` this
pairs->key = index;
// setup value
if (val->IsInt32()) {
pairs->value_type = VALUE_INT;
int *value = (int *) malloc(sizeof(int));
*value = val->Int32Value();
pairs->value = value;
}
else if (val->IsNumber()) {
pairs->value_type = VALUE_DOUBLE;
double *value = (double *) malloc(sizeof(double));
*value = val->NumberValue();
pairs->value = value;
}
else if (val->IsString()) {
pairs->value_type = VALUE_STRING;
String::Utf8Value text(val);
char *value = (char *) calloc(text.length(), sizeof(char));
memcpy(value, *text, text.length());
pairs->value = value;
pairs->value_size = text.length();
}
else if (Buffer::HasInstance(val)) {
pairs->value_type = VALUE_BLOB;
Buffer* buffer = Buffer::Unwrap<Buffer>(val->ToObject());
char *value = (char *) malloc(buffer->length());
memcpy(value, buffer->data(), buffer->length());
pairs->value = value;
pairs->value_size = buffer->length();
}
else if (val->IsNull() || val->IsUndefined()) {
pairs->value_type = VALUE_NULL;
pairs->value = NULL;
}
else {
free(pairs->key);
return ThrowException(Exception::TypeError(
String::New("Unable to bind value of this type")));
}
}
bind_req->cb = Persistent<Function>::New(cb);
bind_req->sto = sto;
eio_custom(EIO_BindArray, EIO_PRI_DEFAULT, EIO_AfterBindArray, bind_req);
ev_ref(EV_DEFAULT_UC);
return Undefined();
}
// db.prepare "SELECT $x, ?" -> statement
//
// Bind multiple placeholders by array
// statement.bind([ value0, value1 ], callback);
//
// Bind multiple placeholdders by name
// statement.bind({ $x: value }, callback);
//
// Bind single placeholder by name:
// statement.bind('$x', value, callback);
//
// Bind placeholder by position:
// statement.bind(1, value, callback);
Handle<Value> Statement::Bind(const Arguments& args) {
HandleScope scope;
Statement* sto = ObjectWrap::Unwrap<Statement>(args.This());
REQUIRE_ARGUMENTS(2);
REQUIRE_ARGUMENT_FUNCTION(2, cb);
if (!( args[0]->IsString()
|| args[0]->IsInt32()
|| args[0]->IsArray()
|| args[0]->IsObject()))
return ThrowException(Exception::TypeError(
String::New("First argument must be a string, number, array or object.")));
struct bind_request *bind_req = (struct bind_request *)
calloc(1, sizeof(struct bind_request));
bind_req->len = 1;
struct bind_pair *pair = bind_req->pairs = (struct bind_pair *)
calloc(1, sizeof(struct bind_pair));
// setup key
if (args[0]->IsString()) {
String::Utf8Value keyValue(args[0]);
pair->key_type = KEY_STRING;
char *key = (char *) calloc(1, keyValue.length()+1);
memcpy(key, *keyValue, keyValue.length()+1);
pair->key = key;
}
else if (args[0]->IsInt32()) {
pair->key_type = KEY_INT;
int *index = (int *) malloc(sizeof(int));
*index = args[0]->Int32Value();
if (*index < 1) return ThrowException(Exception::TypeError(
String::New("Parameter position index start with 1.")));
// don't forget to `free` this
pair->key = index;
}
// setup value
if (args[1]->IsInt32()) {
pair->value_type = VALUE_INT;
int *value = (int *) malloc(sizeof(int));
*value = args[1]->Int32Value();
pair->value = value;
}
else if (args[1]->IsNumber()) {
pair->value_type = VALUE_DOUBLE;
double *value = (double *) malloc(sizeof(double));
*value = args[1]->NumberValue();
pair->value = value;
}
else if (args[1]->IsString()) {
pair->value_type = VALUE_STRING;
String::Utf8Value text(args[1]);
char *value = (char *) calloc(text.length(), sizeof(char));
memcpy(value, *text, text.length());
pair->value = value;
pair->value_size = text.length();
}
else if (Buffer::HasInstance(args[1])) {
pair->value_type = VALUE_BLOB;
Buffer* buffer = Buffer::Unwrap<Buffer>(args[1]->ToObject());
char *value = (char *) malloc(buffer->length());
memcpy(value, buffer->data(), buffer->length());
pair->value = value;
pair->value_size = buffer->length();
}
else if (args[1]->IsNull() || args[1]->IsUndefined()) {
pair->value_type = VALUE_NULL;
pair->value = NULL;
}
else {
free(pair->key);
return ThrowException(Exception::TypeError(
String::New("Unable to bind value of this type")));
}
bind_req->cb = Persistent<Function>::New(cb);
bind_req->sto = sto;
eio_custom(EIO_BindArray, EIO_PRI_DEFAULT, EIO_AfterBindArray, bind_req);
ev_ref(EV_DEFAULT_UC);
return Undefined();
}
int Statement::EIO_AfterFinalize(eio_req *req) {
ev_unref(EV_DEFAULT_UC);
Statement *sto = (class Statement *)(req->data);
HandleScope scope;
Local<Function> cb = sto->GetCallback();
TryCatch try_catch;
cb->Call(sto->handle_, 0, NULL);
if (try_catch.HasCaught()) {
FatalException(try_catch);
}
sto->Unref();
return 0;
}
int Statement::EIO_Finalize(eio_req *req) {
Statement *sto = (class Statement *)(req->data);
assert(sto->stmt_);
req->result = sqlite3_finalize(sto->stmt_);
sto->stmt_ = NULL;
return 0;
}
Handle<Value> Statement::Finalize(const Arguments& args) {
HandleScope scope;
Statement* sto = ObjectWrap::Unwrap<Statement>(args.This());
if (sto->HasCallback()) {
return ThrowException(Exception::Error(String::New("Already stepping")));
}
REQUIRE_ARGUMENT_FUNCTION(0, cb);
sto->SetCallback(cb);
eio_custom(EIO_Finalize, EIO_PRI_DEFAULT, EIO_AfterFinalize, sto);
ev_ref(EV_DEFAULT_UC);
return Undefined();
}
Handle<Value> Statement::ClearBindings(const Arguments& args) {
HandleScope scope;
Statement* sto = ObjectWrap::Unwrap<Statement>(args.This());
SCHECK(sqlite3_clear_bindings(sto->stmt_));
return Undefined();
}
Handle<Value> Statement::Reset(const Arguments& args) {
HandleScope scope;
Statement* sto = ObjectWrap::Unwrap<Statement>(args.This());
sto->FreeColumnData();
SCHECK(sqlite3_reset(sto->stmt_));
return Undefined();
}
int Statement::EIO_AfterStep(eio_req *req) {
ev_unref(EV_DEFAULT_UC);
HandleScope scope;
Statement *sto = (class Statement *) req->data;
sqlite3* db = sqlite3_db_handle(sto->stmt_);
Local<Value> argv[2];
if (sto->error_) {
Local<Value> e = Exception::Error(sto->error_msg_);
Local<Object> obj = e->ToObject();
obj->Set(NODE_PSYMBOL("errno"), Integer::New(sto->error_));
obj->Set(NODE_PSYMBOL("query"), String::NewSymbol(sqlite3_sql(sto->stmt_)));
argv[0] = e;
}
else {
argv[0] = Local<Value>::New(Undefined());
}
if (req->result == SQLITE_DONE) {
argv[1] = Local<Value>::New(Null());
}
else {
Local<Object> row = Object::New();
struct cell_node *cell = sto->cells
, *next = NULL;
for (int i = 0; cell; i++) {
switch (cell->type) {
case SQLITE_INTEGER:
row->Set(String::NewSymbol((char*) sto->column_names_[i]),
Int32::New(*(int*) cell->value));
free(cell->value);
break;
case SQLITE_FLOAT:
row->Set(String::NewSymbol(sto->column_names_[i]),
Number::New(*(double*) cell->value));
free(cell->value);
break;
case SQLITE_TEXT: {
struct string_t *str = (struct string_t *) cell->value;
// str->bytes-1 to compensate for the NULL terminator
row->Set(String::NewSymbol(sto->column_names_[i]),
String::New(str->data, str->bytes-1));
free(str);
}
break;
case SQLITE_BLOB: {
node::Buffer *buf = Buffer::New(cell->size);
memcpy(buf->data(), cell->value, cell->size);
row->Set(String::New(sto->column_names_[i]), buf->handle_);
free(cell->value);
}
break;
case SQLITE_NULL:
row->Set(String::New(sto->column_names_[i]),
Local<Value>::New(Null()));
break;
}
next = cell->next;
free(cell);
cell=next;
}
argv[1] = row;
}
if (sto->mode_ & EXEC_LAST_INSERT_ID) {
sto->handle_->Set(String::New("lastInsertRowID"),
Integer::New(sqlite3_last_insert_rowid(db)));
}
if (sto->mode_ & EXEC_AFFECTED_ROWS) {
sto->handle_->Set(String::New("affectedRows"),
Integer::New(sqlite3_changes(db)));
}
TryCatch try_catch;
Local<Function> cb = sto->GetCallback();
cb->Call(sto->handle_, 2, argv);
if (try_catch.HasCaught()) {
FatalException(try_catch);
}
if (req->result == SQLITE_DONE && sto->column_count_) {
sto->FreeColumnData();
}
return 0;
} }
void Statement::FreeColumnData(void) { int Statement::EIO_AfterPrepare(eio_req *req) {
if (!column_count_) return;
free(column_names_);
column_count_ = 0;
column_names_ = NULL;
}
void Statement::InitializeColumns(void) {
this->column_count_ = sqlite3_column_count(this->stmt_);
this->column_names_ = (char **) calloc(this->column_count_, sizeof(char *));
for (int i = 0; i < this->column_count_; i++) {
// Don't free this!
this->column_names_[i] = (char *) sqlite3_column_name(this->stmt_, i);
}
}
int Statement::EIO_Step(eio_req *req) {
Statement *sto = (class Statement *)(req->data);
sqlite3_stmt *stmt = sto->stmt_;
assert(stmt);
int rc;
const char *msg;
// check if we have already taken a step immediately after prepare
if (sto->first_rc_ != -1) {
// This is the first one! Let's just use the rc from when we called
// it in EIO_Prepare
rc = req->result = sto->first_rc_;
// Now we set first_rc_ to -1 so that on the next step, it won't
// think this is the first.
sto->first_rc_ = -1;
}
else {
sqlite3* db = sqlite3_db_handle(stmt);
sqlite3_mutex* mtx = sqlite3_db_mutex(db);
sqlite3_mutex_enter(mtx);
rc = sqlite3_step(stmt);
if (rc != SQLITE_ROW && rc != SQLITE_DONE) msg = sqlite3_errmsg(db);
sqlite3_mutex_leave(mtx);
req->result = rc;
}
sto->error_ = SQLITE_OK;
if (rc == SQLITE_ROW) {
// If this pointer is NULL, look up and store the columns names.
if (!sto->column_names_) {
sto->InitializeColumns();
}
struct cell_node *cell_head = NULL
, *cell_prev = NULL
, *cell = NULL;
for (int i = 0; i < sto->column_count_; i++) {
cell = (struct cell_node *) malloc(sizeof(struct cell_node));
// If this is the first cell, set `cell_head` to it, otherwise attach
// the new cell to the end of the list `cell_prev->next`.
(!cell_head ? cell_head : cell_prev->next) = cell;
cell->type = sqlite3_column_type(sto->stmt_, i);
cell->next = NULL;
switch (cell->type) {
case SQLITE_INTEGER:
cell->value = (int *) malloc(sizeof(int));
* (int *) cell->value = sqlite3_column_int(stmt, i);
break;
case SQLITE_FLOAT:
cell->value = (double *) malloc(sizeof(double));
* (double *) cell->value = sqlite3_column_double(stmt, i);
break;
case SQLITE_TEXT: {
char *text = (char *) sqlite3_column_text(stmt, i);
int size = 1+sqlite3_column_bytes(stmt, i);
struct string_t *str = (struct string_t *)
malloc(sizeof(size_t) + size);
str->bytes = size;
memcpy(str->data, text, size);
cell->value = str;
}
break;
case SQLITE_BLOB: {
const void* blob = sqlite3_column_blob(stmt, i);
int size = sqlite3_column_bytes(stmt, i);
cell->size = size;
unsigned char *pzBlob = (unsigned char *)malloc(size);
memcpy(pzBlob, blob, size);
cell->value = pzBlob;
}
break;
case SQLITE_NULL:
cell->value = NULL;
break;
default: {
assert(0 && "unsupported type");
}
if (cell->type != SQLITE_NULL) {
assert(cell->value);
}
}
cell_prev = cell;
}
sto->cells = cell_head;
assert(sto->column_names_);
}
else if (rc == SQLITE_DONE) {
// nothing to do in this case
}
else {
HandleScope scope; HandleScope scope;
sto->error_ = rc; PrepareBaton* baton = static_cast<PrepareBaton*>(req->data);
sto->error_msg_ = scope.Close(String::NewSymbol(msg)); Database* db = baton->db;
sto->cells = NULL; Statement* stmt = baton->stmt;
}
stmt->Unref();
return 0; ev_unref(EV_DEFAULT_UC);
} db->pending--;
Handle<Value> Statement::Step(const Arguments& args) { // Local<Value> argv[1];
HandleScope scope; // if (baton->status != SQLITE_OK) {
// EXCEPTION(String::New(baton->message), baton->status, exception);
Statement* sto = ObjectWrap::Unwrap<Statement>(args.This()); // argv[0] = exception;
// }
if (sto->HasCallback()) { // else {
return ThrowException(Exception::Error(String::New("Already stepping"))); // db->open = false;
} // // Leave db->locked to indicate that this db object has reached
// // the end of its life.
REQUIRE_ARGUMENT_FUNCTION(0, cb); // argv[0] = Local<Value>::New(Null());
// }
sto->SetCallback(cb); //
// // Fire callbacks.
eio_custom(EIO_Step, EIO_PRI_DEFAULT, EIO_AfterStep, sto); // if (!baton->callback.IsEmpty()) {
// TRY_CATCH_CALL(db->handle_, baton->callback, 1, argv);
ev_ref(EV_DEFAULT_UC); // }
// else if (db->open) {
return Undefined(); // Local<Value> args[] = { String::NewSymbol("error"), argv[0] };
} // EMIT_EVENT(db->handle_, 2, args);
// }
int Statement::EIO_AfterFetchAll(eio_req *req) { //
HandleScope scope; // if (!db->open) {
ev_unref(EV_DEFAULT_UC); // Local<Value> args[] = { String::NewSymbol("close"), argv[0] };
// EMIT_EVENT(db->handle_, 1, args);
struct fetchall_request *fetchall_req // Process(db);
= (struct fetchall_request *)(req->data); // }
Statement *sto = fetchall_req->sto; fprintf(stderr, "Prepare completed\n");
struct row_node *cur = fetchall_req->rows; Database::Process(db);
struct cell_node *cell = NULL;
delete baton;
Local<Value> argv[2];
if (fetchall_req->error != NULL) {
argv[0] = Exception::Error(String::New(fetchall_req->error));
argv[1] = Local<Value>::New(Undefined());
}
else {
argv[0] = Local<Value>::New(Undefined());
Persistent<Array> results = Persistent<Array>::New(Array::New());
for (int row_count = 0; cur; row_count++, cur=cur->next) {
Local<Object> row = Object::New();
cell = cur->cells;
// walk down the list
for (int i = 0; cell; i++, cell=cell->next) {
assert(cell);
if (cell->type != SQLITE_NULL) {
assert((void*)cell->value);
}
assert(sto->column_names_[i]);
switch (cell->type) {
case SQLITE_INTEGER:
row->Set(String::NewSymbol((char*) sto->column_names_[i]),
Int32::New(*(int*) (cell->value)));
break;
case SQLITE_FLOAT:
row->Set(String::NewSymbol(sto->column_names_[i]),
Number::New(*(double*) (cell->value)));
break;
case SQLITE_TEXT: {
struct string_t *str = (struct string_t *) (cell->value);
// str->bytes-1 to compensate for the NULL terminator
row->Set(String::NewSymbol(sto->column_names_[i]),
String::New(str->data, str->bytes-1));
}
break;
case SQLITE_NULL:
row->Set(String::New(sto->column_names_[i]),
Local<Value>::New(Null()));
break;
}
}
results->Set(Integer::New(row_count), row);
}
argv[1] = Local<Value>::New(results);
}
// unsigned int page_size_p;
// unsigned long num_alloced_p;
// unsigned long user_alloced_p;
// unsigned long max_alloced_p;
// unsigned long tot_alloced_p;
// mpool_stats(fetchall_req->pool,
// &page_size_p,
// &num_alloced_p,
// &user_alloced_p,
// &max_alloced_p,
// &tot_alloced_p);
// printf(
// "%u page size\n"
// "%lu num allocated\n"
// "%lu user allocated\n"
// "%lu max allocated\n"
// "%lu total allocated\n",
// page_size_p,
// num_alloced_p,
// user_alloced_p,
// max_alloced_p,
// tot_alloced_p
// );
if (fetchall_req->pool) {
int ret = mpool_close(fetchall_req->pool);
if (ret != MPOOL_ERROR_NONE) {
req->result = -1;
argv[0] = Exception::Error(String::New(mpool_strerror(ret)));
argv[1] = Local<Value>::New(Undefined());
}
}
TryCatch try_catch;
fetchall_req->cb->Call(Context::GetCurrent()->Global(), 2, argv);
if (try_catch.HasCaught()) {
FatalException(try_catch);
}
fetchall_req->cb.Dispose();
sto->FreeColumnData();
free(fetchall_req);
return 0;
}
int Statement::EIO_FetchAll(eio_req *req) {
struct fetchall_request *fetchall_req
= (struct fetchall_request *)(req->data);
Statement *sto = fetchall_req->sto;
sqlite3_stmt *stmt = sto->stmt_;
assert(stmt);
int ret;
int rc = sqlite3_step(stmt);
if (rc != SQLITE_DONE) {
/* open the pool */
fetchall_req->pool = mpool_open(MPOOL_FLAG_USE_MAP_ANON
, 0
, NULL
, &ret);
if (fetchall_req->pool == NULL) {
req->result = -1;
fetchall_req->rows = NULL;
fetchall_req->error = (char *) mpool_strerror(ret);
return 0;
}
}
else {
fetchall_req->pool = NULL;
fetchall_req->rows = NULL;
}
// We're going to be traversing a linked list in two dimensions.
struct row_node *cur = NULL
, *prev = NULL
, *head = NULL;
for (;; rc = sqlite3_step(stmt)) {
if (rc != SQLITE_ROW) break;
if (!sto->column_names_) {
sto->InitializeColumns();
}
cur = (struct row_node *) mpool_alloc
( fetchall_req->pool
, sizeof(struct row_node)
, &ret
);
cur->next = NULL;
// If this is the first row, assign `cur` to `head` and `hold` head there
// since it was the first result. Otherwise set the `next` field on
// the`prev` pointer to attach the newly allocated element.
(!head ? head : prev->next) = cur;
struct cell_node *cell_head = NULL
, *cell_prev = NULL
, *cell = NULL;
for (int i = 0; i < sto->column_count_; i++) {
cell = (struct cell_node *)
mpool_alloc(fetchall_req->pool, sizeof(struct cell_node), &ret);
// Same as above with the row linked list.
(!cell_head ? cell_head : cell_prev->next) = cell;
cell->type = sqlite3_column_type(sto->stmt_, i);
cell->next = NULL;
// TODO: Cache column data in the fetchall req struct.
switch (cell->type) {
case SQLITE_INTEGER:
cell->value = (int *)
mpool_alloc(fetchall_req->pool , sizeof(int) , &ret);
* (int *) cell->value = sqlite3_column_int(stmt, i);
break;
case SQLITE_FLOAT:
cell->value = (double *)
mpool_alloc(fetchall_req->pool , sizeof(double) , &ret);
* (double *) cell->value = sqlite3_column_double(stmt, i);
break;
case SQLITE_TEXT: {
char *text = (char *) sqlite3_column_text(stmt, i);
int size = 1+sqlite3_column_bytes(stmt, i);
struct string_t *str = (struct string_t *)
mpool_alloc(fetchall_req->pool, sizeof(size_t) + size, &ret);
str->bytes = size;
memcpy(str->data, text, size);
cell->value = str;
}
break;
case SQLITE_NULL:
cell->value = NULL;
break;
default: {
assert(0 && "unsupported type");
}
}
assert(sto->column_names_[i]);
cell_prev = cell;
}
cur->cells = cell_head;
prev = cur;
}
// Test for errors
if (rc != SQLITE_DONE) {
const char *error = sqlite3_errmsg(sqlite3_db_handle(sto->stmt_));
if (error == NULL) {
error = "Unknown Error";
}
size_t errorSize = sizeof(char) * (strlen(error) + 1);
fetchall_req->error = (char *)mpool_alloc(fetchall_req->pool, errorSize, &ret);
if (fetchall_req->error != NULL) {
memcpy(fetchall_req->error, error, errorSize);
} else {
fetchall_req->error = (char *) mpool_strerror(ret);
}
req->result = -1;
return 0; return 0;
}
req->result = 0;
fetchall_req->rows = head;
return 0;
}
Handle<Value> Statement::FetchAll(const Arguments& args) {
HandleScope scope;
REQUIRE_ARGUMENT_FUNCTION(0, cb);
struct fetchall_request *fetchall_req = (struct fetchall_request *)
calloc(1, sizeof(struct fetchall_request));
if (!fetchall_req) {
V8::LowMemoryNotification();
return ThrowException(Exception::Error(
String::New("Could not allocate enough memory")));
}
fetchall_req->sto = ObjectWrap::Unwrap<Statement>(args.This());
fetchall_req->cb = Persistent<Function>::New(cb);
eio_custom(EIO_FetchAll, EIO_PRI_DEFAULT, EIO_AfterFetchAll, fetchall_req);
ev_ref(EV_DEFAULT_UC);
return Undefined();
}
// The following three methods must be called inside a HandleScope
bool Statement::HasCallback() {
return ! handle_->GetHiddenValue(callback_sym).IsEmpty();
}
void Statement::SetCallback(Local<Function> cb) {
handle_->SetHiddenValue(callback_sym, cb);
}
Local<Function> Statement::GetCallback() {
Local<Value> cb_v = handle_->GetHiddenValue(callback_sym);
assert(cb_v->IsFunction());
Local<Function> cb = Local<Function>::Cast(cb_v);
handle_->DeleteHiddenValue(callback_sym);
return cb;
} }
// 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
...@@ -18,139 +18,50 @@ ...@@ -18,139 +18,50 @@
#include <v8.h> #include <v8.h>
#include <node.h> #include <node.h>
#include <node_events.h> #include <node_events.h>
#include <sqlite3.h>
#include <stdlib.h>
#include <node_buffer.h>
extern "C" { #include "database.h"
#include <mpool.h>
}; #include <string>
#include <queue>
#include <sqlite3.h>
using namespace v8; using namespace v8;
using namespace node; using namespace node;
struct cell_node { class Statement;
void *value;
int type;
struct cell_node *next;
int size;
};
struct row_node { static struct PrepareBaton : Baton {
struct cell_node *cells; Statement* stmt;
struct row_node *next; std::string sql;
}; };
// represent strings with this struct
struct string_t {
size_t bytes;
char data[];
};
class Statement : public EventEmitter { class Statement : public EventEmitter {
public: public:
static Persistent<FunctionTemplate> constructor_template; static Persistent<FunctionTemplate> constructor_template;
static void Init(v8::Handle<Object> target); static void Init(Handle<Object> target);
static Handle<Value> New(const Arguments& args); static Handle<Value> New(const Arguments& args);
protected: Statement(Database* db_) : EventEmitter() {
db = db_;
Statement(sqlite3_stmt* stmt, int first_rc = -1, int mode = 0) db->Ref();
: EventEmitter(), first_rc_(first_rc), mode_(mode), stmt_(stmt) {
column_count_ = -1;
column_names_ = NULL;
} }
~Statement() { ~Statement() {
if (stmt_) sqlite3_finalize(stmt_); db->Unref();
if (column_names_) FreeColumnData();
} }
static Handle<Value> Bind(const Arguments &args); protected:
static Handle<Value> BindObject(const Arguments &args); static void EIO_BeginPrepare(Baton* baton);
static Handle<Value> BindArray(const Arguments &args); static int EIO_Prepare(eio_req *req);
static int EIO_BindArray(eio_req *req); static int EIO_AfterPrepare(eio_req *req);
static int EIO_AfterBindArray(eio_req *req);
static int EIO_AfterFinalize(eio_req *req);
static int EIO_Finalize(eio_req *req);
static Handle<Value> Finalize(const Arguments &args);
static Handle<Value> Reset(const Arguments &args);
static Handle<Value> ClearBindings(const Arguments &args);
static int EIO_AfterStep(eio_req *req);
static int EIO_Step(eio_req *req);
static Handle<Value> Step(const Arguments &args);
static int EIO_AfterFetchAll(eio_req *req);
static int EIO_FetchAll(eio_req *req);
static Handle<Value> FetchAll(const Arguments &args);
void InitializeColumns(void);
void FreeColumnData(void);
bool HasCallback();
void SetCallback(Local<Function> cb);
Local<Function> GetCallback(); private:
Database* db;
private:
int column_count_;
char **column_names_;
int error_;
Local<String> error_msg_;
int first_rc_;
int mode_;
sqlite3_stmt* stmt_;
// for statment.step
cell_node *cells;
};
// indicates the key type (integer index or name string)
enum BindKeyType {
KEY_INT,
KEY_STRING
};
// indicate the parameter type
enum BindValueType {
VALUE_INT,
VALUE_DOUBLE,
VALUE_BLOB,
VALUE_STRING,
VALUE_NULL
};
struct bind_request {
Persistent<Function> cb;
Statement *sto;
struct bind_pair *pairs;
size_t len;
};
struct bind_pair {
enum BindKeyType key_type;
enum BindValueType value_type;
void *key; // char * | int *
void *value; // char * | int * | double * | 0
size_t value_size;
};
struct fetchall_request { sqlite3_stmt* handle;
Persistent<Function> cb;
Statement *sto;
mpool_t *pool;
char *error;
struct row_node *rows;
}; };
#endif #endif
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