Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

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.
  • Loading branch information...
commit a4c1a7567beeb1fb13df1be6c96360f07ff82663 1 parent 858b70c
@orlandov orlandov authored
View
82 src/database.cc
@@ -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());
View
69 src/statement.cc
@@ -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();
View
31 src/statement.h
@@ -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_;
};
View
101 tests/test-affected-rows.js
@@ -0,0 +1,101 @@
+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();
+}
View
118 tests/test-last-inserted-id.js
@@ -0,0 +1,118 @@
+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();
+}
Please sign in to comment.
Something went wrong with that request. Please try again.