Commit 27106877 by Orlando Vazquez

Improve awkward binding behaviour with Statement#bindArray.

parent f89fa7e3
...@@ -29,13 +29,13 @@ additional steps. ...@@ -29,13 +29,13 @@ additional steps.
// bindings list is optional // bindings list is optional
var ponies = []; var ponies = [];
db.query(sql, [colour], function (pony) { db.query(sql, [colour], function (pony) {
if (!pony) { if (!pony) {
// no more ponies // no more ponies
if (!ponies.length) if (!ponies.length)
sys.puts('There are no ponies with ' + colour + ' tails. :('); sys.puts('There are no ponies with ' + colour + ' tails. :(');
else else
sys.puts('The following ponies have ' + colour + ' tails: ' + ponies.join(', ')); sys.puts('The following ponies have ' + colour + ' tails: ' + ponies.join(', '));
} }
sys.puts(sys.inspect(pony)); sys.puts(sys.inspect(pony));
...@@ -88,7 +88,7 @@ from the API have been made to improve performance. ...@@ -88,7 +88,7 @@ from the API have been made to improve performance.
}); });
}); });
}); });
DESCRIPTION DESCRIPTION
----------- -----------
......
...@@ -33,8 +33,10 @@ void Statement::Init(v8::Handle<Object> target) { ...@@ -33,8 +33,10 @@ void Statement::Init(v8::Handle<Object> target) {
constructor_template->SetClassName(String::NewSymbol("Statement")); constructor_template->SetClassName(String::NewSymbol("Statement"));
NODE_SET_PROTOTYPE_METHOD(t, "bind", Bind); NODE_SET_PROTOTYPE_METHOD(t, "bind", Bind);
NODE_SET_PROTOTYPE_METHOD(t, "bindArray", BindArray);
NODE_SET_PROTOTYPE_METHOD(t, "finalize", Finalize); NODE_SET_PROTOTYPE_METHOD(t, "finalize", Finalize);
NODE_SET_PROTOTYPE_METHOD(t, "reset", Reset); NODE_SET_PROTOTYPE_METHOD(t, "reset", Reset);
NODE_SET_PROTOTYPE_METHOD(t, "clearBindings", ClearBindings);
NODE_SET_PROTOTYPE_METHOD(t, "step", Step); NODE_SET_PROTOTYPE_METHOD(t, "step", Step);
callback_sym = Persistent<String>::New(String::New("callback")); callback_sym = Persistent<String>::New(String::New("callback"));
...@@ -52,6 +54,38 @@ Handle<Value> Statement::New(const Arguments& args) { ...@@ -52,6 +54,38 @@ Handle<Value> Statement::New(const Arguments& args) {
return args.This(); 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();
free(bind_req->pairs->key);;
free(bind_req->pairs->value);
free(bind_req->pairs);
free(bind_req);
return 0;
return 0;
}
int Statement::EIO_AfterBind(eio_req *req) { int Statement::EIO_AfterBind(eio_req *req) {
ev_unref(EV_DEFAULT_UC); ev_unref(EV_DEFAULT_UC);
...@@ -75,26 +109,91 @@ int Statement::EIO_AfterBind(eio_req *req) { ...@@ -75,26 +109,91 @@ int Statement::EIO_AfterBind(eio_req *req) {
bind_req->cb.Dispose(); bind_req->cb.Dispose();
free(bind_req->key); struct bind_pair *pair = bind_req->pairs;
free(bind_req->value);
for (size_t i = 0; i < bind_req->len; i++, pair++) {
free(pair->key);
free(pair->value);
}
free(bind_req->pairs);
free(bind_req); free(bind_req);
return 0; 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_NULL:
rc = sqlite3_bind_null(sto->stmt_, index);
break;
default: {
// should be unreachable
}
}
}
if (rc) {
req->result = rc;
return 0;
}
return 0;
}
int Statement::EIO_Bind(eio_req *req) { int Statement::EIO_Bind(eio_req *req) {
struct bind_request *bind_req = (struct bind_request *)(req->data); struct bind_request *bind_req = (struct bind_request *)(req->data);
Statement *sto = bind_req->sto; Statement *sto = bind_req->sto;
int index(0); int index(0);
switch(bind_req->key_type) { switch(bind_req->pairs->key_type) {
case KEY_INT: case KEY_INT:
index = *(int*)(bind_req->key); index = *(int*)(bind_req->pairs->key);
break; break;
case KEY_STRING: case KEY_STRING:
index = sqlite3_bind_parameter_index(sto->stmt_, index = sqlite3_bind_parameter_index(sto->stmt_,
(char*)(bind_req->key)); (char*)(bind_req->pairs->key));
break; break;
default: { default: {
...@@ -108,16 +207,16 @@ int Statement::EIO_Bind(eio_req *req) { ...@@ -108,16 +207,16 @@ int Statement::EIO_Bind(eio_req *req) {
} }
int rc = 0; int rc = 0;
switch(bind_req->value_type) { switch(bind_req->pairs->value_type) {
case VALUE_INT: case VALUE_INT:
rc = sqlite3_bind_int(sto->stmt_, index, *(int*)(bind_req->value)); rc = sqlite3_bind_int(sto->stmt_, index, *(int*)(bind_req->pairs->value));
break; break;
case VALUE_DOUBLE: case VALUE_DOUBLE:
rc = sqlite3_bind_double(sto->stmt_, index, *(double*)(bind_req->value)); rc = sqlite3_bind_double(sto->stmt_, index, *(double*)(bind_req->pairs->value));
break; break;
case VALUE_STRING: case VALUE_STRING:
rc = sqlite3_bind_text(sto->stmt_, index, (char*)(bind_req->value), rc = sqlite3_bind_text(sto->stmt_, index, (char*)(bind_req->pairs->value),
bind_req->value_size, SQLITE_TRANSIENT); bind_req->pairs->value_size, SQLITE_TRANSIENT);
break; break;
case VALUE_NULL: case VALUE_NULL:
rc = sqlite3_bind_null(sto->stmt_, index); rc = sqlite3_bind_null(sto->stmt_, index);
...@@ -136,6 +235,98 @@ int Statement::EIO_Bind(eio_req *req) { ...@@ -136,6 +235,98 @@ int Statement::EIO_Bind(eio_req *req) {
return 0; return 0;
} }
// 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 index:
// statement.bind(1, value, callback);
Handle<Value> Statement::BindArray(const Arguments& args) {
HandleScope scope;
Statement* sto = ObjectWrap::Unwrap<Statement>(args.This());
REQ_ARGS(2);
REQ_FUN_ARG(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;
pairs->value_size = sizeof(int);
// 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()+1, sizeof(char*));
strcpy(value, *text);
pairs->value = value;
pairs->value_size = text.length()+1;
}
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::Bind(const Arguments& args) { Handle<Value> Statement::Bind(const Arguments& args) {
HandleScope scope; HandleScope scope;
Statement* sto = ObjectWrap::Unwrap<Statement>(args.This()); Statement* sto = ObjectWrap::Unwrap<Statement>(args.This());
...@@ -143,61 +334,67 @@ Handle<Value> Statement::Bind(const Arguments& args) { ...@@ -143,61 +334,67 @@ Handle<Value> Statement::Bind(const Arguments& args) {
REQ_ARGS(2); REQ_ARGS(2);
REQ_FUN_ARG(2, cb); REQ_FUN_ARG(2, cb);
if (!args[0]->IsString() && !args[0]->IsInt32()) if (!( args[0]->IsString()
|| args[0]->IsInt32()
|| args[0]->IsArray()
|| args[0]->IsObject()))
return ThrowException(Exception::TypeError( return ThrowException(Exception::TypeError(
String::New("First argument must be a string or integer"))); String::New("First argument must be a string, number, array or object.")));
struct bind_request *bind_req = (struct bind_request *) struct bind_request *bind_req = (struct bind_request *)
calloc(1, sizeof(struct bind_request)); calloc(1, sizeof(struct bind_request));
bind_req->pairs = (struct bind_pair *)
calloc(1, sizeof(struct bind_pair));
// setup key // setup key
if (args[0]->IsString()) { if (args[0]->IsString()) {
String::Utf8Value keyValue(args[0]); String::Utf8Value keyValue(args[0]);
bind_req->key_type = KEY_STRING; bind_req->pairs->key_type = KEY_STRING;
char *key = (char *) calloc(1, keyValue.length() + 1); char *key = (char *) calloc(1, keyValue.length() + 1);
bind_req->value_size = keyValue.length() + 1; bind_req->pairs->value_size = keyValue.length() + 1;
strcpy(key, *keyValue); strcpy(key, *keyValue);
bind_req->key = key; bind_req->pairs->key = key;
} }
else if (args[0]->IsInt32()) { else if (args[0]->IsInt32()) {
bind_req->key_type = KEY_INT; bind_req->pairs->key_type = KEY_INT;
int *index = (int *) malloc(sizeof(int)); int *index = (int *) malloc(sizeof(int));
*index = args[0]->Int32Value(); *index = args[0]->Int32Value();
bind_req->value_size = sizeof(int); bind_req->pairs->value_size = sizeof(int);
// don't forget to `free` this // don't forget to `free` this
bind_req->key = index; bind_req->pairs->key = index;
} }
// setup value // setup value
if (args[1]->IsInt32()) { if (args[1]->IsInt32()) {
bind_req->value_type = VALUE_INT; bind_req->pairs->value_type = VALUE_INT;
int *value = (int *) malloc(sizeof(int)); int *value = (int *) malloc(sizeof(int));
*value = args[1]->Int32Value(); *value = args[1]->Int32Value();
bind_req->value = value; bind_req->pairs->value = value;
} }
else if (args[1]->IsNumber()) { else if (args[1]->IsNumber()) {
bind_req->value_type = VALUE_DOUBLE; bind_req->pairs->value_type = VALUE_DOUBLE;
double *value = (double *) malloc(sizeof(double)); double *value = (double *) malloc(sizeof(double));
*value = args[1]->NumberValue(); *value = args[1]->NumberValue();
bind_req->value = value; bind_req->pairs->value = value;
} }
else if (args[1]->IsString()) { else if (args[1]->IsString()) {
bind_req->value_type = VALUE_STRING; bind_req->pairs->value_type = VALUE_STRING;
String::Utf8Value text(args[1]); String::Utf8Value text(args[1]);
char *value = (char *) calloc(text.length()+1, sizeof(char*)); char *value = (char *) calloc(text.length()+1, sizeof(char*));
strcpy(value, *text); strcpy(value, *text);
bind_req->value = value; bind_req->pairs->value = value;
bind_req->value_size = text.length()+1; bind_req->pairs->value_size = text.length()+1;
} }
else if (args[1]->IsNull() || args[1]->IsUndefined()) { else if (args[1]->IsNull() || args[1]->IsUndefined()) {
bind_req->value_type = VALUE_NULL; bind_req->pairs->value_type = VALUE_NULL;
bind_req->value = NULL; bind_req->pairs->value = NULL;
} }
else { else {
free(bind_req->key); free(bind_req->pairs->key);
return ThrowException(Exception::TypeError( return ThrowException(Exception::TypeError(
String::New("Unable to bind value of this type"))); String::New("Unable to bind value of this type")));
} }
...@@ -263,9 +460,17 @@ Handle<Value> Statement::Finalize(const Arguments& args) { ...@@ -263,9 +460,17 @@ Handle<Value> Statement::Finalize(const Arguments& args) {
return Undefined(); 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) { Handle<Value> Statement::Reset(const Arguments& args) {
HandleScope scope; HandleScope scope;
Statement* sto = ObjectWrap::Unwrap<Statement>(args.This()); Statement* sto = ObjectWrap::Unwrap<Statement>(args.This());
sto->FreeColumnData();
SCHECK(sqlite3_reset(sto->stmt_)); SCHECK(sqlite3_reset(sto->stmt_));
return Undefined(); return Undefined();
} }
...@@ -295,29 +500,34 @@ int Statement::EIO_AfterStep(eio_req *req) { ...@@ -295,29 +500,34 @@ int Statement::EIO_AfterStep(eio_req *req) {
for (int i = 0; i < sto->column_count_; i++) { for (int i = 0; i < sto->column_count_; i++) {
assert(sto->column_data_); assert(sto->column_data_);
assert(((void**)sto->column_data_)[i]); if (((int*)sto->column_types_)[i] != SQLITE_NULL)
assert(((void**)sto->column_data_)[i]);
assert(sto->column_names_[i]); assert(sto->column_names_[i]);
assert(sto->column_types_[i]); assert(sto->column_types_[i]);
switch (sto->column_types_[i]) { switch (sto->column_types_[i]) {
// XXX why does using String::New make v8 croak here?
case SQLITE_INTEGER: case SQLITE_INTEGER:
row->Set(String::NewSymbol((char*) sto->column_names_[i]), row->Set(String::NewSymbol((char*) sto->column_names_[i]),
Int32::New(*(int*) (sto->column_data_[i]))); Int32::New(*(int*) (sto->column_data_[i])));
break; break;
case SQLITE_FLOAT: case SQLITE_FLOAT:
row->Set(String::New(sto->column_names_[i]), row->Set(String::NewSymbol(sto->column_names_[i]),
Number::New(*(double*) (sto->column_data_[i]))); Number::New(*(double*) (sto->column_data_[i])));
break; break;
case SQLITE_TEXT: case SQLITE_TEXT:
assert(strlen((char*)sto->column_data_[i])); assert(strlen((char*)sto->column_data_[i]));
row->Set(String::New(sto->column_names_[i]), row->Set(String::NewSymbol(sto->column_names_[i]),
String::New((char *) (sto->column_data_[i]))); String::New((char *) (sto->column_data_[i])));
// don't free this pointer, it's owned by sqlite3 // don't free this pointer, it's owned by sqlite3
break; break;
case SQLITE_NULL:
row->Set(String::New(sto->column_names_[i]),
Local<Value>::New(Null()));
break;
// no default // no default
} }
} }
...@@ -355,8 +565,12 @@ void Statement::FreeColumnData(void) { ...@@ -355,8 +565,12 @@ void Statement::FreeColumnData(void) {
} }
column_data_[i] = NULL; column_data_[i] = NULL;
} }
free(column_names_);
free(column_types_);
free(column_data_); free(column_data_);
column_count_ = 0;
column_types_ = NULL;
column_names_ = NULL;
column_data_ = NULL; column_data_ = NULL;
} }
...@@ -397,25 +611,31 @@ int Statement::EIO_Step(eio_req *req) { ...@@ -397,25 +611,31 @@ int Statement::EIO_Step(eio_req *req) {
sto->column_data_ = (void **) calloc(sto->column_count_, sto->column_data_ = (void **) calloc(sto->column_count_,
sizeof(void *)); sizeof(void *));
} }
}
for (int i = 0; i < sto->column_count_; i++) { for (int i = 0; i < sto->column_count_; i++) {
sto->column_types_[i] = sqlite3_column_type(stmt, i); sto->column_types_[i] = sqlite3_column_type(stmt, i);
sto->column_names_[i] = (char *) sqlite3_column_name(stmt, i);
switch(sto->column_types_[i]) { // Don't free this!
case SQLITE_INTEGER: sto->column_names_[i] = (char *) sqlite3_column_name(stmt, i);
sto->column_data_[i] = (int *) malloc(sizeof(int));
break;
case SQLITE_FLOAT: switch(sto->column_types_[i]) {
sto->column_data_[i] = (double *) malloc(sizeof(double)); case SQLITE_INTEGER:
break; sto->column_data_[i] = (int *) malloc(sizeof(int));
break;
// no need to allocate memory for strings case SQLITE_FLOAT:
sto->column_data_[i] = (double *) malloc(sizeof(double));
break;
default: { case SQLITE_NULL:
// unsupported type sto->column_data_[i] = NULL;
} break;
// no need to allocate memory for strings
default: {
// unsupported type
} }
} }
} }
...@@ -426,7 +646,7 @@ int Statement::EIO_Step(eio_req *req) { ...@@ -426,7 +646,7 @@ int Statement::EIO_Step(eio_req *req) {
int type = sto->column_types_[i]; int type = sto->column_types_[i];
switch(type) { switch(type) {
case SQLITE_INTEGER: case SQLITE_INTEGER:
*(int*)(sto->column_data_[i]) = sqlite3_column_int(stmt, i); *(int*)(sto->column_data_[i]) = sqlite3_column_int(stmt, i);
assert(sto->column_data_[i]); assert(sto->column_data_[i]);
break; break;
...@@ -445,11 +665,16 @@ int Statement::EIO_Step(eio_req *req) { ...@@ -445,11 +665,16 @@ int Statement::EIO_Step(eio_req *req) {
} }
break; break;
case SQLITE_NULL:
sto->column_data_[i] = NULL;
break;
default: { default: {
assert(0 && "unsupported type"); assert(0 && "unsupported type");
} }
} }
assert(sto->column_data_[i]); if (sto->column_types_[i] != SQLITE_NULL)
assert(sto->column_data_[i]);
assert(sto->column_names_[i]); assert(sto->column_names_[i]);
assert(sto->column_types_[i]); assert(sto->column_types_[i]);
} }
......
...@@ -53,12 +53,17 @@ class Statement : public EventEmitter { ...@@ -53,12 +53,17 @@ class Statement : public EventEmitter {
static int EIO_AfterBind(eio_req *req); static int EIO_AfterBind(eio_req *req);
static int EIO_Bind(eio_req *req); static int EIO_Bind(eio_req *req);
static Handle<Value> Bind(const Arguments& args); static Handle<Value> Bind(const Arguments& args);
static int EIO_AfterBindArray(eio_req *req);
static int EIO_BindArray(eio_req *req);
static Handle<Value> BindArray(const Arguments& args);
static int EIO_AfterFinalize(eio_req *req); static int EIO_AfterFinalize(eio_req *req);
static int EIO_Finalize(eio_req *req); static int EIO_Finalize(eio_req *req);
static Handle<Value> Finalize(const Arguments& args); static Handle<Value> Finalize(const Arguments& args);
static Handle<Value> Reset(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_AfterStep(eio_req *req);
static int EIO_Step(eio_req *req); static int EIO_Step(eio_req *req);
...@@ -100,6 +105,11 @@ struct bind_request { ...@@ -100,6 +105,11 @@ struct bind_request {
Persistent<Function> cb; Persistent<Function> cb;
Statement *sto; Statement *sto;
struct bind_pair *pairs;
size_t len;
};
struct bind_pair {
enum BindKeyType key_type; enum BindKeyType key_type;
enum BindValueType value_type; enum BindValueType value_type;
......
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