Commit a4c1a756 by Orlando Vazquez

Cache lastInsertRowID and affectedRows.

Add some options to preparing a statement. If the lastInsertedID and/or
affectedRows options are specified, those values will be looked up after every
step.
parent 858b70c3
......@@ -359,6 +359,7 @@ int Database::EIO_PrepareAndStep(eio_req *req) {
Handle<Value> Database::PrepareAndStep(const Arguments& args) {
HandleScope scope;
REQ_STR_ARG(0, sql);
REQ_FUN_ARG(1, cb);
OPT_INT_ARG(2, mode, EXEC_EMPTY);
......@@ -392,7 +393,7 @@ int Database::EIO_AfterPrepare(eio_req *req) {
struct prepare_request *prep_req = (struct prepare_request *)(req->data);
HandleScope scope;
Local<Value> argv[2];
Local<Value> argv[3];
int argc = 0;
// if the prepare failed
......@@ -404,16 +405,17 @@ int Database::EIO_AfterPrepare(eio_req *req) {
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(2, argv));
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);
argc = 2;
}
TryCatch try_catch;
......@@ -454,11 +456,49 @@ int Database::EIO_Prepare(eio_req *req) {
return 0;
}
// Statement#prepare(sql, [ options ,] callback);
Handle<Value> Database::Prepare(const Arguments& args) {
HandleScope scope;
Local<Object> options;
Local<Function> cb;
int mode;
REQ_STR_ARG(0, sql);
REQ_FUN_ARG(1, cb);
OPT_INT_ARG(2, mode, EXEC_EMPTY);
// 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* dbo = ObjectWrap::Unwrap<Database>(args.This());
......
......@@ -16,6 +16,7 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#include <string.h>
#include "database.h"
#include "statement.h"
#include "sqlite3_bindings.h"
......@@ -48,8 +49,9 @@ Handle<Value> Statement::New(const Arguments& args) {
HandleScope scope;
REQ_EXT_ARG(0, stmt);
int first_rc = args[1]->IntegerValue();
int mode = args[2]->IntegerValue();
Statement *sto = new Statement((sqlite3_stmt*)stmt->Value(), first_rc);
Statement *sto = new Statement((sqlite3_stmt*)stmt->Value(), first_rc, mode);
sto->Wrap(args.This());
sto->Ref();
......@@ -136,11 +138,9 @@ int Statement::EIO_BindArray(eio_req *req) {
rc = sqlite3_bind_null(sto->stmt_, index);
break;
default: {
// should be unreachable
}
}
}
if (rc) {
req->result = rc;
......@@ -177,7 +177,6 @@ Handle<Value> Statement::BindObject(const Arguments& args) {
Local<Value> val = obj->Get(name->ToString());
String::Utf8Value keyValue(name);
printf("prop %d is %s\n", i, *keyValue);
// setting key type
pairs->key_type = KEY_STRING;
......@@ -261,7 +260,6 @@ Handle<Value> Statement::BindArray(const Arguments& args) {
// setup value
if (val->IsInt32()) {
printf("Binding int\n");
pairs->value_type = VALUE_INT;
int *value = (int *) malloc(sizeof(int));
*value = val->Int32Value();
......@@ -470,11 +468,12 @@ int Statement::EIO_AfterStep(eio_req *req) {
Statement *sto = (class Statement *)(req->data);
sqlite3* db = sqlite3_db_handle(sto->stmt_);
Local<Value> argv[2];
if (sto->error_) {
argv[0] = Exception::Error(
String::New(sqlite3_errmsg(sqlite3_db_handle(sto->stmt_))));
String::New(sqlite3_errmsg(db)));
}
else {
argv[0] = Local<Value>::New(Undefined());
......@@ -523,6 +522,16 @@ int Statement::EIO_AfterStep(eio_req *req) {
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();
......
......@@ -36,8 +36,8 @@ class Statement : public EventEmitter {
protected:
Statement(sqlite3_stmt* stmt, int first_rc = -1)
: EventEmitter(), first_rc_(first_rc), stmt_(stmt) {
Statement(sqlite3_stmt* stmt, int first_rc = -1, int mode = 0)
: EventEmitter(), first_rc_(first_rc), mode_(mode), stmt_(stmt) {
column_count_ = -1;
column_types_ = NULL;
column_names_ = NULL;
......@@ -83,6 +83,7 @@ class Statement : public EventEmitter {
bool error_;
int first_rc_;
int mode_;
sqlite3_stmt* stmt_;
};
......
require.paths.push(__dirname + '/..');
sys = require('sys');
fs = require('fs');
path = require('path');
TestSuite = require('async-testing/async_testing').TestSuite;
sqlite = require('sqlite3_bindings');
puts = sys.puts;
inspect = sys.inspect;
// createa table
// prepare a statement (with options) that inserts
// do a step
// check that the result has a lastInsertedId property
var suite = exports.suite = new TestSuite("node-sqlite last inserted id test");
function createTestTable(db, callback) {
db.prepare('CREATE TABLE table1 (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)',
function (error, createStatement) {
if (error) throw error;
createStatement.step(function (error, row) {
if (error) throw error;
callback();
});
});
}
var tests = [
{ 'insert a row with lastinsertedid':
function (assert, finished) {
var self = this;
self.db.open(':memory:', function (error) {
function updateStatementCreated(error, statement) {
if (error) throw error;
statement.step(function (error, row) {
if (error) throw error;
puts("This is " + inspect(this));
assert.equal(this.affectedRows, 10, "Last inserted id should be 10");
finished();
});
}
createTestTable(self.db,
function () {
function insertRows(db, count, callback) {
var i = count;
db.prepare('INSERT INTO table1 (name) VALUES ("oh boy")',
function (error, statement) {
statement.step(function (error, row) {
if (error) throw error;
assert.ok(!row, "Row should be unset");
statement.reset();
if (--i)
statement.step(arguments.callee);
else
callback();
});
});
}
var updateSQL
= 'UPDATE table1 SET name="o hai"';
insertRows(self.db, 10, function () {
self.db.prepare(updateSQL
, { affectedRows: true }
, updateStatementCreated);
});
});
});
}
}
];
// order matters in our tests
for (var i=0,il=tests.length; i < il; i++) {
suite.addTests(tests[i]);
}
var currentTest = 0;
var testCount = tests.length;
suite.setup(function(finished, test) {
this.db = new sqlite.Database();
finished();
});
suite.teardown(function(finished) {
if (this.db) this.db.close(function (error) {
finished();
});
++currentTest == testCount;
});
if (module == require.main) {
suite.runTests();
}
require.paths.push(__dirname + '/..');
sys = require('sys');
fs = require('fs');
path = require('path');
TestSuite = require('async-testing/async_testing').TestSuite;
sqlite = require('sqlite3_bindings');
puts = sys.puts;
inspect = sys.inspect;
// createa table
// prepare a statement (with options) that inserts
// do a step
// check that the result has a lastInsertedId property
var suite = exports.suite = new TestSuite("node-sqlite last inserted id test");
function createTestTable(db, callback) {
db.prepare('CREATE TABLE table1 (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)',
function (error, createStatement) {
if (error) throw error;
createStatement.step(function (error, row) {
if (error) throw error;
callback();
});
});
}
function fetchAll(db, sql, callback) {
db.prepare(
sql,
function (error, statement) {
if (error) throw error;
var rows = [];
function doStep() {
statement.step(function (error, row) {
if (error) throw error;
if (row) {
rows.push(row);
doStep();
}
else {
callback(rows);
}
});
}
doStep();
});
}
var tests = [
{ 'insert a row with lastinsertedid':
function (assert, finished) {
var self = this;
self.db.open(':memory:', function (error) {
function createStatement(error, statement) {
if (error) throw error;
statement.step(function (error, row) {
puts("This is " + inspect(this));
assert.equal(this.lastInsertRowID, 101, "Last inserted id should be 1");
assert.equal(this.affectedRows, 1, "Last inserted id should be 1");
statement.reset();
statement.step(function (error, row) {
puts("This is " + inspect(this));
assert.equal(this.lastInsertRowID, 102, "Last inserted id should be 1");
assert.equal(this.affectedRows, 1, "Last inserted id should be 1");
finished();
});
});
}
createTestTable(self.db,
function () {
var insertSQL
= 'INSERT INTO table1 (id, name) VALUES (100, "first post!")';
self.db.prepareAndStep(insertSQL, function (error, row) {
if (error) throw error;
assert.ok(!row, "Row was trueish, but should be falseish");
self.db.prepare('INSERT INTO table1 (name) VALUES ("orlando")'
, { affectedRows: true, lastInsertRowID: true }
, createStatement);
});
});
});
}
}
];
// order matters in our tests
for (var i=0,il=tests.length; i < il; i++) {
suite.addTests(tests[i]);
}
var currentTest = 0;
var testCount = tests.length;
suite.setup(function(finished, test) {
this.db = new sqlite.Database();
finished();
});
suite.teardown(function(finished) {
if (this.db) this.db.close(function (error) {
finished();
});
++currentTest == testCount;
});
if (module == require.main) {
suite.runTests();
}
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