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
......@@ -257,31 +257,31 @@ int Database::EIO_AfterPrepareAndStep(eio_req *req) {
// if the prepare failed
if (req->result != SQLITE_OK) {
argv[0] = Exception::Error(
String::New(sqlite3_errmsg(prep_req->dbo->db_)));
String::New(sqlite3_errmsg(prep_req->dbo->db_)));
argc = 1;
}
else {
if (req->int1 == SQLITE_DONE) {
if (prep_req->mode != EXEC_EMPTY) {
argv[0] = Local<Value>::New(Undefined()); // no error
if (prep_req->mode != EXEC_EMPTY) {
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) {
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;
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;
argc = 0;
}
}
......@@ -289,7 +289,7 @@ int Database::EIO_AfterPrepareAndStep(eio_req *req) {
argv[0] = External::New(prep_req->stmt);
argv[1] = Integer::New(req->int1);
Persistent<Object> statement(
Statement::constructor_template->GetFunction()->NewInstance(2, argv));
Statement::constructor_template->GetFunction()->NewInstance(2, argv));
if (prep_req->tail) {
statement->Set(String::New("tail"), String::New(prep_req->tail));
......@@ -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();
......@@ -107,12 +109,12 @@ int Statement::EIO_BindArray(eio_req *req) {
case KEY_STRING:
index = sqlite3_bind_parameter_index(sto->stmt_,
(char*)(pair->key));
(char*)(pair->key));
break;
default: {
// this SHOULD be unreachable
}
default: {
// this SHOULD be unreachable
}
}
if (!index) {
......@@ -130,15 +132,13 @@ int Statement::EIO_BindArray(eio_req *req) {
break;
case VALUE_STRING:
rc = sqlite3_bind_text(sto->stmt_, index, (char*)(pair->value),
pair->value_size, SQLITE_TRANSIENT);
pair->value_size, SQLITE_TRANSIENT);
break;
case VALUE_NULL:
rc = sqlite3_bind_null(sto->stmt_, index);
break;
default: {
// should be unreachable
}
// should be unreachable
}
}
......@@ -159,16 +159,16 @@ Handle<Value> Statement::BindObject(const Arguments& args) {
if (! args[0]->IsObject())
return ThrowException(Exception::TypeError(
String::New("First argument must be an Array.")));
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));
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));
calloc(len, sizeof(struct bind_pair));
struct bind_pair *pairs = bind_req->pairs;
......@@ -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;
......@@ -213,7 +212,7 @@ Handle<Value> Statement::BindObject(const Arguments& args) {
else {
free(pairs->key);
return ThrowException(Exception::TypeError(
String::New("Unable to bind value of this type")));
String::New("Unable to bind value of this type")));
}
}
......@@ -235,15 +234,15 @@ Handle<Value> Statement::BindArray(const Arguments& args) {
REQ_FUN_ARG(1, cb);
if (! args[0]->IsArray())
return ThrowException(Exception::TypeError(
String::New("First argument must be an Array.")));
String::New("First argument must be an Array.")));
struct bind_request *bind_req = (struct bind_request *)
calloc(1, sizeof(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));
calloc(len, sizeof(struct bind_pair));
struct bind_pair *pairs = bind_req->pairs;
......@@ -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();
......@@ -288,7 +286,7 @@ Handle<Value> Statement::BindArray(const Arguments& args) {
else {
free(pairs->key);
return ThrowException(Exception::TypeError(
String::New("Unable to bind value of this type")));
String::New("Unable to bind value of this type")));
}
}
......@@ -328,24 +326,24 @@ Handle<Value> Statement::Bind(const Arguments& args) {
|| args[0]->IsArray()
|| args[0]->IsObject()))
return ThrowException(Exception::TypeError(
String::New("First argument must be a string, number, array or object.")));
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));
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));
calloc(1, sizeof(struct bind_pair));
// setup key
if (args[0]->IsString()) {
String::Utf8Value keyValue(args[0]);
pair->key_type = KEY_STRING;
String::Utf8Value keyValue(args[0]);
pair->key_type = KEY_STRING;
char *key = (char *) calloc(1, keyValue.length() + 1);
strcpy(key, *keyValue);
char *key = (char *) calloc(1, keyValue.length() + 1);
strcpy(key, *keyValue);
pair->key = key;
pair->key = key;
}
else if (args[0]->IsInt32()) {
pair->key_type = KEY_INT;
......@@ -384,7 +382,7 @@ Handle<Value> Statement::Bind(const Arguments& args) {
else {
free(pair->key);
return ThrowException(Exception::TypeError(
String::New("Unable to bind value of this type")));
String::New("Unable to bind value of this type")));
}
bind_req->cb = Persistent<Function>::New(cb);
......@@ -434,7 +432,7 @@ Handle<Value> Statement::Finalize(const Arguments& args) {
Statement* sto = ObjectWrap::Unwrap<Statement>(args.This());
if (sto->HasCallback()) {
return ThrowException(Exception::Error(String::New("Already stepping")));
return ThrowException(Exception::Error(String::New("Already stepping")));
}
REQ_FUN_ARG(0, cb);
......@@ -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();
......
......@@ -28,52 +28,52 @@ using namespace node;
class Statement : public EventEmitter {
public:
static Persistent<FunctionTemplate> constructor_template;
static void Init(v8::Handle<Object> target);
static Handle<Value> New(const Arguments& args);
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;
column_data_ = NULL;
}
~Statement() {
if (stmt_) sqlite3_finalize(stmt_);
if (column_types_) free(column_types_);
if (column_names_) free(column_names_);
if (column_data_) FreeColumnData();
}
static Handle<Value> Bind(const Arguments& args);
static Handle<Value> BindObject(const Arguments& args);
static Handle<Value> BindArray(const Arguments& args);
static int EIO_BindArray(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);
void FreeColumnData(void);
bool HasCallback();
void SetCallback(Local<Function> cb);
Local<Function> GetCallback();
private:
int column_count_;
......@@ -81,8 +81,9 @@ class Statement : public EventEmitter {
char **column_names_;
void **column_data_;
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