Commit d9d87db7 by Konstantin Käfer

named parameters when inserting

parent e16b8bc7
......@@ -9,15 +9,12 @@ var Statement = sqlite3.Statement;
Database.prototype.prepare = function(sql) {
var callback, params = Array.prototype.slice.call(arguments, 1);
if (params.length && typeof params[params.length - 1] !== 'function') {
params.push(undefined);
if (!params.length || (params.length === 1 && typeof params[0] === 'function')) {
return new Statement(this, sql, params[0]);
}
if (params.length > 1) {
else {
var statement = new Statement(this, sql);
return statement.bind.apply(statement, params);
} else {
return new Statement(this, sql, params.pop());
}
};
......
......@@ -162,6 +162,38 @@ int Statement::EIO_AfterPrepare(eio_req *req) {
return 0;
}
inline Data::Field* Statement::BindParameter(const Handle<Value> source) {
if (source->IsString() || source->IsRegExp()) {
String::Utf8Value val(source->ToString());
return new Data::Text(val.length(), *val);
}
else if (source->IsInt32()) {
return new Data::Integer(source->Int32Value());
}
else if (source->IsNumber()) {
return new Data::Float(source->NumberValue());
}
else if (source->IsBoolean()) {
return new Data::Integer(source->BooleanValue() ? 1 : 0);
}
else if (source->IsNull()) {
return new Data::Null();
}
else if (Buffer::HasInstance(source)) {
Local<Object> buffer = source->ToObject();
return new Data::Blob(Buffer::Length(buffer), Buffer::Data(buffer));
}
else if (source->IsDate()) {
return new Data::Float(source->NumberValue());
}
else if (source->IsUndefined()) {
return NULL;
}
else {
return NULL;
}
}
template <class T> T* Statement::Bind(const Arguments& args, int start) {
int last = args.Length();
Local<Function> callback;
......@@ -172,33 +204,46 @@ template <class T> T* Statement::Bind(const Arguments& args, int start) {
T* baton = new T(this, callback);
for (int i = start; i < last; i++) {
if (args[i]->IsString() || args[i]->IsRegExp()) {
String::Utf8Value val(args[i]->ToString());
baton->parameters.push_back(new Data::Text(val.length(), *val));
if (start < last) {
if (args[start]->IsArray()) {
Local<Array> array = Local<Array>::Cast(args[start]);
int length = array->Length();
// Note: bind parameters start with 1.
for (int i = 0, pos = 1; i < length; i++, pos++) {
baton->parameters.push_back(
new Data::Parameter(pos, BindParameter(array->Get(i))));
}
else if (args[i]->IsInt32()) {
baton->parameters.push_back(new Data::Integer(args[i]->Int32Value()));
}
else if (args[i]->IsNumber() || args[i]->IsDate()) {
baton->parameters.push_back(new Data::Float(args[i]->NumberValue()));
else if (!args[start]->IsObject() || args[start]->IsRegExp() || args[start]->IsDate()) {
// Parameters directly in array.
// Note: bind parameters start with 1.
for (int i = start, pos = 1; i < last; i++, pos++) {
baton->parameters.push_back(
new Data::Parameter(pos, BindParameter(args[i])));
}
else if (args[i]->IsBoolean()) {
baton->parameters.push_back(new Data::Integer(args[i]->BooleanValue() ? 1 : 0));
}
else if (args[i]->IsNull()) {
baton->parameters.push_back(new Data::Null());
else if (args[start]->IsObject()) {
Local<Object> object = Local<Object>::Cast(args[start]);
Local<Array> array = object->GetPropertyNames();
int length = array->Length();
for (int i = 0; i < length; i++) {
Local<Value> name = array->Get(i);
if (name->IsInt32()) {
baton->parameters.push_back(new Data::Parameter(
name->Int32Value(),
BindParameter(object->Get(name))
));
}
else {
baton->parameters.push_back(new Data::Parameter(
*String::Utf8Value(Local<String>::Cast(name)),
BindParameter(object->Get(name))
));
}
else if (Buffer::HasInstance(args[i])) {
Local<Object> buffer = args[i]->ToObject();
baton->parameters.push_back(new Data::Blob(Buffer::Length(buffer), Buffer::Data(buffer)));
}
else if (args[i]->IsUndefined()) {
// Skip parameter position.
baton->parameters.push_back(NULL);
}
else {
delete baton;
return NULL;
}
}
......@@ -217,36 +262,42 @@ bool Statement::Bind(const Data::Parameters parameters) {
Data::Parameters::const_iterator it = parameters.begin();
Data::Parameters::const_iterator end = parameters.end();
// Note: bind parameters start with 1.
for (int i = 1; it < end; it++, i++) {
Data::Field* field = *it;
if (field == NULL) {
continue;
for (; it < end; it++) {
Data::Parameter* param = *it;
int pos;
if (param->position > 0) {
pos = param->position;
}
else {
pos = sqlite3_bind_parameter_index(handle, param->name.c_str());
}
switch (field->type) {
if (param->field != NULL) {
switch (param->field->type) {
case SQLITE_INTEGER: {
status = sqlite3_bind_int(handle, i,
((Data::Integer*)field)->value);
status = sqlite3_bind_int(handle, pos,
((Data::Integer*)param->field)->value);
} break;
case SQLITE_FLOAT: {
status = sqlite3_bind_double(handle, i,
((Data::Float*)field)->value);
status = sqlite3_bind_double(handle, pos,
((Data::Float*)param->field)->value);
} break;
case SQLITE_TEXT: {
status = sqlite3_bind_text(handle, i,
((Data::Text*)field)->value.c_str(),
((Data::Text*)field)->value.size(), SQLITE_TRANSIENT);
status = sqlite3_bind_text(handle, pos,
((Data::Text*)param->field)->value.c_str(),
((Data::Text*)param->field)->value.size(), SQLITE_TRANSIENT);
} break;
case SQLITE_BLOB: {
status = sqlite3_bind_blob(handle, i,
((Data::Blob*)field)->value,
((Data::Blob*)field)->length, SQLITE_TRANSIENT);
status = sqlite3_bind_blob(handle, pos,
((Data::Blob*)param->field)->value,
((Data::Blob*)param->field)->length, SQLITE_TRANSIENT);
} break;
case SQLITE_NULL: {
status = sqlite3_bind_null(handle, i);
status = sqlite3_bind_null(handle, pos);
} break;
}
}
if (status != SQLITE_OK) {
message = std::string(sqlite3_errmsg(db->handle));
......
......@@ -21,31 +21,31 @@ namespace node_sqlite3 {
namespace Data {
struct Field {
Field(unsigned short _type = SQLITE_NULL) : type(_type) {}
inline Field(unsigned short _type = SQLITE_NULL) : type(_type) {}
unsigned short type;
};
struct Integer : Field {
Integer(int val) : Field(SQLITE_INTEGER), value(val) {}
inline Integer(int val) : Field(SQLITE_INTEGER), value(val) {}
int value;
};
struct Float : Field {
Float(double val) : Field(SQLITE_FLOAT), value(val) {}
inline Float(double val) : Field(SQLITE_FLOAT), value(val) {}
double value;
};
struct Text : Field {
Text(size_t len, const char* val) : Field(SQLITE_TEXT), value(val, len) {}
inline Text(size_t len, const char* val) : Field(SQLITE_TEXT), value(val, len) {}
std::string value;
};
struct Blob : Field {
Blob(size_t len, const void* val) : Field(SQLITE_BLOB), length(len) {
inline Blob(size_t len, const void* val) : Field(SQLITE_BLOB), length(len) {
value = (char*)malloc(len);
memcpy(value, val, len);
}
~Blob() {
inline ~Blob() {
free(value);
}
int length;
......@@ -54,8 +54,24 @@ namespace Data {
typedef Field Null;
typedef std::vector<Field*> Row;
typedef Row Parameters;
typedef std::vector<Row*> Rows;
struct Parameter {
unsigned short position;
std::string name;
Field* field;
inline Parameter(unsigned short pos_, Field* field_) : position(pos_), field(field_) {}
inline Parameter(const char* name_, Field* field_) : position(0), name(name_), field(field_) {}
inline ~Parameter() {
if (field) {
delete field;
}
}
};
typedef std::vector<Parameter*> Parameters;
}
......@@ -74,6 +90,10 @@ public:
Data::Parameters parameters;
Baton(Statement* stmt_, Handle<Function> cb_) : stmt(stmt_) {
Data::Parameters::const_iterator it = parameters.begin();
Data::Parameters::const_iterator end = parameters.end();
for (; it < end; it++) delete *it;
stmt->Ref();
ev_ref(EV_DEFAULT_UC);
callback = Persistent<Function>::New(cb_);
......@@ -177,6 +197,7 @@ protected:
static void Finalize(Baton* baton);
void Finalize();
Data::Field* BindParameter(const Handle<Value> source);
template <class T> T* Bind(const Arguments& args, int start = 0);
bool Bind(const Data::Parameters parameters);
......
var sqlite3 = require('sqlite3');
var assert = require('assert');
exports['test named parameters'] = function(beforeExit) {
var db = new sqlite3.Database(':memory:');
var finished = false;
db.serialize(function() {
db.run("CREATE TABLE foo (txt TEXT, num INT)");
db.run("INSERT INTO foo VALUES($text, $id)", {
$id: 1,
$text: "Lorem Ipsum"
});
db.run("INSERT INTO foo VALUES(:text, :id)", {
":id": 2,
":text": "Dolor Sit Amet"
});
db.run("INSERT INTO foo VALUES(@txt, @id)", {
"@id": 3,
"@txt": "Consectetur Adipiscing Elit"
});
db.run("INSERT INTO foo VALUES(@txt, @id)", [ 'Sed Do Eiusmod', 4 ]);
db.run("INSERT INTO foo VALUES(?2, ?4)", [ null, 'Tempor Incididunt', null, 5 ]);
db.run("INSERT INTO foo VALUES(?, ?)", {
2: 6,
1: "Ut Labore Et Dolore"
});
db.all("SELECT txt, num FROM foo ORDER BY num", function(err, rows) {
if (err) throw err;
assert.equal(rows[0][0], "Lorem Ipsum");
assert.equal(rows[0][1], 1);
assert.equal(rows[1][0], "Dolor Sit Amet");
assert.equal(rows[1][1], 2);
assert.equal(rows[2][0], "Consectetur Adipiscing Elit");
assert.equal(rows[2][1], 3);
assert.equal(rows[3][0], "Sed Do Eiusmod");
assert.equal(rows[3][1], 4);
assert.equal(rows[4][0], "Tempor Incididunt");
assert.equal(rows[4][1], 5);
assert.equal(rows[5][0], "Ut Labore Et Dolore");
assert.equal(rows[5][1], 6);
finished = true;
});
});
beforeExit(function() {
assert.ok(finished);
});
};
\ No newline at end of file
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