From 27106877774747060c07bb100daeabeab5a09e57 Mon Sep 17 00:00:00 2001 From: Orlando Vazquez Date: Sun, 4 Jul 2010 19:43:01 -0700 Subject: [PATCH] Improve awkward binding behaviour with Statement#bindArray. --- README.md | 6 +- src/statement.cc | 323 ++++++++++++++++++++++++++++++++++++++++------- src/statement.h | 10 ++ 3 files changed, 287 insertions(+), 52 deletions(-) diff --git a/README.md b/README.md index 11e19858c..cb63cca3f 100644 --- a/README.md +++ b/README.md @@ -29,13 +29,13 @@ additional steps. // bindings list is optional var ponies = []; - + db.query(sql, [colour], function (pony) { if (!pony) { // no more ponies if (!ponies.length) sys.puts('There are no ponies with ' + colour + ' tails. :('); - else + else sys.puts('The following ponies have ' + colour + ' tails: ' + ponies.join(', ')); } sys.puts(sys.inspect(pony)); @@ -88,7 +88,7 @@ from the API have been made to improve performance. }); }); }); - + DESCRIPTION ----------- diff --git a/src/statement.cc b/src/statement.cc index cab7c37e6..c1ec48590 100644 --- a/src/statement.cc +++ b/src/statement.cc @@ -33,8 +33,10 @@ void Statement::Init(v8::Handle target) { constructor_template->SetClassName(String::NewSymbol("Statement")); 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, "reset", Reset); + NODE_SET_PROTOTYPE_METHOD(t, "clearBindings", ClearBindings); NODE_SET_PROTOTYPE_METHOD(t, "step", Step); callback_sym = Persistent::New(String::New("callback")); @@ -52,6 +54,38 @@ Handle Statement::New(const Arguments& args) { 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 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) { ev_unref(EV_DEFAULT_UC); @@ -75,26 +109,91 @@ int Statement::EIO_AfterBind(eio_req *req) { bind_req->cb.Dispose(); - free(bind_req->key); - free(bind_req->value); + struct bind_pair *pair = bind_req->pairs; + + for (size_t i = 0; i < bind_req->len; i++, pair++) { + free(pair->key); + free(pair->value); + } + + free(bind_req->pairs); free(bind_req); 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) { struct bind_request *bind_req = (struct bind_request *)(req->data); Statement *sto = bind_req->sto; int index(0); - switch(bind_req->key_type) { + switch(bind_req->pairs->key_type) { case KEY_INT: - index = *(int*)(bind_req->key); + index = *(int*)(bind_req->pairs->key); break; case KEY_STRING: index = sqlite3_bind_parameter_index(sto->stmt_, - (char*)(bind_req->key)); + (char*)(bind_req->pairs->key)); break; default: { @@ -108,16 +207,16 @@ int Statement::EIO_Bind(eio_req *req) { } int rc = 0; - switch(bind_req->value_type) { + switch(bind_req->pairs->value_type) { 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; 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; case VALUE_STRING: - rc = sqlite3_bind_text(sto->stmt_, index, (char*)(bind_req->value), - bind_req->value_size, SQLITE_TRANSIENT); + rc = sqlite3_bind_text(sto->stmt_, index, (char*)(bind_req->pairs->value), + bind_req->pairs->value_size, SQLITE_TRANSIENT); break; case VALUE_NULL: rc = sqlite3_bind_null(sto->stmt_, index); @@ -136,6 +235,98 @@ int Statement::EIO_Bind(eio_req *req) { 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 Statement::BindArray(const Arguments& args) { + HandleScope scope; + Statement* sto = ObjectWrap::Unwrap(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 = Local::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 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::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 Statement::Bind(const Arguments& args) { HandleScope scope; Statement* sto = ObjectWrap::Unwrap(args.This()); @@ -143,61 +334,67 @@ Handle Statement::Bind(const Arguments& args) { REQ_ARGS(2); 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( - 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 *) calloc(1, sizeof(struct bind_request)); + bind_req->pairs = (struct bind_pair *) + calloc(1, sizeof(struct bind_pair)); + // setup key if (args[0]->IsString()) { 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); - bind_req->value_size = keyValue.length() + 1; + bind_req->pairs->value_size = keyValue.length() + 1; strcpy(key, *keyValue); - bind_req->key = key; + bind_req->pairs->key = key; } else if (args[0]->IsInt32()) { - bind_req->key_type = KEY_INT; + bind_req->pairs->key_type = KEY_INT; int *index = (int *) malloc(sizeof(int)); *index = args[0]->Int32Value(); - bind_req->value_size = sizeof(int); + bind_req->pairs->value_size = sizeof(int); // don't forget to `free` this - bind_req->key = index; + bind_req->pairs->key = index; } // setup value if (args[1]->IsInt32()) { - bind_req->value_type = VALUE_INT; + bind_req->pairs->value_type = VALUE_INT; int *value = (int *) malloc(sizeof(int)); *value = args[1]->Int32Value(); - bind_req->value = value; + bind_req->pairs->value = value; } else if (args[1]->IsNumber()) { - bind_req->value_type = VALUE_DOUBLE; + bind_req->pairs->value_type = VALUE_DOUBLE; double *value = (double *) malloc(sizeof(double)); *value = args[1]->NumberValue(); - bind_req->value = value; + bind_req->pairs->value = value; } else if (args[1]->IsString()) { - bind_req->value_type = VALUE_STRING; + bind_req->pairs->value_type = VALUE_STRING; String::Utf8Value text(args[1]); char *value = (char *) calloc(text.length()+1, sizeof(char*)); strcpy(value, *text); - bind_req->value = value; - bind_req->value_size = text.length()+1; + bind_req->pairs->value = value; + bind_req->pairs->value_size = text.length()+1; } else if (args[1]->IsNull() || args[1]->IsUndefined()) { - bind_req->value_type = VALUE_NULL; - bind_req->value = NULL; + bind_req->pairs->value_type = VALUE_NULL; + bind_req->pairs->value = NULL; } else { - free(bind_req->key); + free(bind_req->pairs->key); return ThrowException(Exception::TypeError( String::New("Unable to bind value of this type"))); } @@ -263,9 +460,17 @@ Handle Statement::Finalize(const Arguments& args) { return Undefined(); } +Handle Statement::ClearBindings(const Arguments& args) { + HandleScope scope; + Statement* sto = ObjectWrap::Unwrap(args.This()); + SCHECK(sqlite3_clear_bindings(sto->stmt_)); + return Undefined(); +} + Handle Statement::Reset(const Arguments& args) { HandleScope scope; Statement* sto = ObjectWrap::Unwrap(args.This()); + sto->FreeColumnData(); SCHECK(sqlite3_reset(sto->stmt_)); return Undefined(); } @@ -295,29 +500,34 @@ int Statement::EIO_AfterStep(eio_req *req) { for (int i = 0; i < sto->column_count_; i++) { 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_types_[i]); switch (sto->column_types_[i]) { - // XXX why does using String::New make v8 croak here? case SQLITE_INTEGER: row->Set(String::NewSymbol((char*) sto->column_names_[i]), Int32::New(*(int*) (sto->column_data_[i]))); break; 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]))); break; case SQLITE_TEXT: 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]))); // don't free this pointer, it's owned by sqlite3 break; + case SQLITE_NULL: + row->Set(String::New(sto->column_names_[i]), + Local::New(Null())); + break; + // no default } } @@ -355,8 +565,12 @@ void Statement::FreeColumnData(void) { } column_data_[i] = NULL; } - + free(column_names_); + free(column_types_); free(column_data_); + column_count_ = 0; + column_types_ = NULL; + column_names_ = NULL; column_data_ = NULL; } @@ -397,25 +611,31 @@ int Statement::EIO_Step(eio_req *req) { sto->column_data_ = (void **) calloc(sto->column_count_, sizeof(void *)); } + } - for (int i = 0; i < sto->column_count_; i++) { - sto->column_types_[i] = sqlite3_column_type(stmt, i); - sto->column_names_[i] = (char *) sqlite3_column_name(stmt, i); + for (int i = 0; i < sto->column_count_; i++) { + sto->column_types_[i] = sqlite3_column_type(stmt, i); - switch(sto->column_types_[i]) { - case SQLITE_INTEGER: - sto->column_data_[i] = (int *) malloc(sizeof(int)); - break; + // Don't free this! + sto->column_names_[i] = (char *) sqlite3_column_name(stmt, i); - case SQLITE_FLOAT: - sto->column_data_[i] = (double *) malloc(sizeof(double)); - break; + switch(sto->column_types_[i]) { + case SQLITE_INTEGER: + 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: { - // unsupported type - } + case SQLITE_NULL: + 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) { int type = sto->column_types_[i]; switch(type) { - case SQLITE_INTEGER: + case SQLITE_INTEGER: *(int*)(sto->column_data_[i]) = sqlite3_column_int(stmt, i); assert(sto->column_data_[i]); break; @@ -445,11 +665,16 @@ int Statement::EIO_Step(eio_req *req) { } break; + case SQLITE_NULL: + sto->column_data_[i] = NULL; + break; + default: { 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_types_[i]); } diff --git a/src/statement.h b/src/statement.h index d5f60a1d7..1905faa0c 100644 --- a/src/statement.h +++ b/src/statement.h @@ -53,12 +53,17 @@ class Statement : public EventEmitter { static int EIO_AfterBind(eio_req *req); static int EIO_Bind(eio_req *req); static Handle Bind(const Arguments& args); + + static int EIO_AfterBindArray(eio_req *req); + static int EIO_BindArray(eio_req *req); + static Handle BindArray(const Arguments& args); static int EIO_AfterFinalize(eio_req *req); static int EIO_Finalize(eio_req *req); static Handle Finalize(const Arguments& args); static Handle Reset(const Arguments& args); + static Handle ClearBindings(const Arguments& args); static int EIO_AfterStep(eio_req *req); static int EIO_Step(eio_req *req); @@ -100,6 +105,11 @@ struct bind_request { Persistent cb; Statement *sto; + struct bind_pair *pairs; + size_t len; +}; + +struct bind_pair { enum BindKeyType key_type; enum BindValueType value_type;