From 023d51c1f5b53daf9fb8ff76882697c891597a40 Mon Sep 17 00:00:00 2001 From: Eric Waldheim Date: Tue, 21 Oct 2014 22:44:40 -0600 Subject: [PATCH 1/2] add options argument to connection.execute as optional 3rd argument. Allows specification of {getColumnMetaData:true} for column meta data retrieval --- src/connection.cpp | 39 +++++++++++++++++++++++++++++++++++---- src/connection.h | 1 + src/executeBaton.cpp | 7 ++++++- src/executeBaton.h | 4 +++- src/statementBaton.h | 2 +- test/integration.js | 32 +++++++++++++++++++++++++++++++- test/outparams.js | 8 +++++++- 7 files changed, 84 insertions(+), 9 deletions(-) diff --git a/src/connection.cpp b/src/connection.cpp index d061fbe..a806638 100644 --- a/src/connection.cpp +++ b/src/connection.cpp @@ -56,11 +56,18 @@ uni::CallbackType Connection::Execute(const uni::FunctionCallbackInfo& args) { REQ_STRING_ARG(0, sql); REQ_ARRAY_ARG(1, values); - REQ_FUN_ARG(2, callback); + Local options; + int cbIndex = 2; + if (args.Length() > 2 && args[2]->IsObject() && !args[2]->IsFunction()) + { + options = Local::Cast(args[2]); + ++cbIndex; + } + REQ_FUN_ARG(cbIndex, callback); String::Utf8Value sqlVal(sql); - ExecuteBaton* baton = new ExecuteBaton(connection, *sqlVal, &values, &callback); + ExecuteBaton* baton = new ExecuteBaton(connection, *sqlVal, &values, &options, &callback); if (baton->error) { Local message = String::New(baton->error->c_str()); delete baton; @@ -732,6 +739,22 @@ Local Connection::CreateV8ArrayFromRows(ExecuteBaton* baton, vector Connection::CreateV8ArrayFromCols(std::vector columns) +{ + Local v8cols = Array::New(columns.size()); + uint32_t index = 0; + for (std::vector::iterator iterator = columns.begin(), end =columns.end(); + iterator != end; ++iterator, ++index) + { + column_t* col = *iterator; + v8::Local v8col = v8::Object::New(); + v8col->Set(v8::String::New("name"), String::New(col->name.c_str())); + v8col->Set(v8::String::New("type"), Number::New((double)(col->type))); + v8cols->Set(index, v8col); + } + return v8cols; +} + void Connection::EIO_AfterExecute(uv_work_t* req, int status) { UNI_SCOPE(scope); @@ -761,7 +784,12 @@ void Connection::handleResult(ExecuteBaton* baton, Handle (&argv)[2]) { } else { argv[0] = Undefined(); if(baton->rows) { - argv[1] = CreateV8ArrayFromRows(baton, baton->columns, baton->rows); + Local obj = CreateV8ArrayFromRows(baton, baton->columns, baton->rows); + if (baton->getColumnMetaData) { + obj->Set(String::New("columnMetaData"), + CreateV8ArrayFromCols(baton->columns)); + } + argv[1] = obj; if (baton->error) goto failed; // delete argv[1] ?? } else { Local obj = Object::New(); @@ -870,10 +898,13 @@ uni::CallbackType Connection::ExecuteSync(const uni::FunctionCallbackInfo& args) REQ_STRING_ARG(0, sql); REQ_ARRAY_ARG(1, values); + Local options; + if (args.Length() > 2 && args[2]->IsObject() && !args[2]->IsFunction()) + options = Local::Cast(args[2]); String::Utf8Value sqlVal(sql); - ExecuteBaton* baton = new ExecuteBaton(connection, *sqlVal, &values, NULL); + ExecuteBaton* baton = new ExecuteBaton(connection, *sqlVal, &values, &options, NULL); if (baton->error) { Local message = String::New(baton->error->c_str()); delete baton; diff --git a/src/connection.h b/src/connection.h index b59d5be..5fe42c2 100644 --- a/src/connection.h +++ b/src/connection.h @@ -125,6 +125,7 @@ class Connection : public ObjectWrap { private: static Local CreateV8ArrayFromRows(ExecuteBaton* baton, std::vector columns, std::vector* rows); static Local CreateV8ObjectFromRow(ExecuteBaton* baton, std::vector columns, row_t* currentRow); + static Local CreateV8ArrayFromCols(std::vector columns); oracle::occi::Connection* m_connection; oracle::occi::Environment* m_environment; diff --git a/src/executeBaton.cpp b/src/executeBaton.cpp index 558bd64..f015f31 100644 --- a/src/executeBaton.cpp +++ b/src/executeBaton.cpp @@ -7,7 +7,7 @@ #include using namespace std; -ExecuteBaton::ExecuteBaton(Connection* connection, const char* sql, v8::Local* values, v8::Handle* callback) { +ExecuteBaton::ExecuteBaton(Connection* connection, const char* sql, v8::Local* values, v8::Local* options, v8::Handle* callback) { this->connection = connection; this->sql = sql; if(callback!=NULL) { @@ -16,6 +16,7 @@ ExecuteBaton::ExecuteBaton(Connection* connection, const char* sql, v8::Localoutputs = new std::vector(); this->error = NULL; if (values) CopyValuesToBaton(this, values); + SetOptionsInBaton(this, options); this->rows = NULL; } @@ -187,6 +188,10 @@ void ExecuteBaton::CopyValuesToBaton(ExecuteBaton* baton, v8::Local* } } +void ExecuteBaton::SetOptionsInBaton(ExecuteBaton* baton, v8::Local* options) { + baton->getColumnMetaData = (!options->IsEmpty()) ? (*options)->Get(v8::String::New("getColumnMetaData"))->BooleanValue() : false; +} + void ExecuteBaton::GetVectorParam(ExecuteBaton* baton, arrayParam_t* arrParam, Local arr) { // In case the array is empty just initialize the fields as we would need something in Connection::SetValuesOnStatement if (arr->Length() < 1) { diff --git a/src/executeBaton.h b/src/executeBaton.h index 451c45b..488b790 100644 --- a/src/executeBaton.h +++ b/src/executeBaton.h @@ -69,7 +69,7 @@ struct output_t { class ExecuteBaton { public: - ExecuteBaton(Connection* connection, const char* sql, v8::Local* values, v8::Handle* callback); + ExecuteBaton(Connection* connection, const char* sql, v8::Local* values, v8::Local* options, v8::Handle* callback); ~ExecuteBaton(); void ResetValues(); void ResetRows(); @@ -84,9 +84,11 @@ class ExecuteBaton { std::vector* rows; std::vector* outputs; std::string* error; + bool getColumnMetaData; int updateCount; static void CopyValuesToBaton(ExecuteBaton* baton, v8::Local* values); + static void SetOptionsInBaton(ExecuteBaton* baton, v8::Local* options); static void GetVectorParam(ExecuteBaton* baton, arrayParam_t *value, v8::Local arr); }; diff --git a/src/statementBaton.h b/src/statementBaton.h index e2cd57d..6f8ed75 100644 --- a/src/statementBaton.h +++ b/src/statementBaton.h @@ -7,7 +7,7 @@ class StatementBaton : public ExecuteBaton { public: - StatementBaton(Connection* connection, const char* sql, v8::Local* values) : ExecuteBaton(connection, sql, values, NULL) { + StatementBaton(Connection* connection, const char* sql, v8::Local* values) : ExecuteBaton(connection, sql, values, NULL, NULL) { stmt = NULL; done = false; busy = false; diff --git a/test/integration.js b/test/integration.js index d93b444..b7960da 100644 --- a/test/integration.js +++ b/test/integration.js @@ -199,6 +199,35 @@ exports['IntegrationTest'] = nodeunit.testCase({ }); }, + "select with getColumnMetaData": function(test) { + var self = this; + self.connection.execute("INSERT INTO person (name) VALUES (:1)", ["Bill O'Neil"], function(err, results) { + if(err) { console.error(err); return; } + self.connection.execute("SELECT * FROM person", [], {getColumnMetaData:true}, function(err, results) { + if(err) { console.error(err); return; } + test.equal(results.length, 1); + test.deepEqual(results.columnMetaData, [ { name: 'ID', type: 4 }, { name: 'NAME', type: 3 } ]); + self.connection.execute("SELECT * FROM datatype_test", [], {getColumnMetaData:true}, function(err, results) { + if(err) { console.error(err); return; } + test.equal(results.length, 0); + test.deepEqual(results.columnMetaData, + [ { name: 'ID', type: 4 }, + { name: 'TVARCHAR2', type: 3 }, + { name: 'TNVARCHAR2', type: 3 }, + { name: 'TCHAR', type: 3 }, + { name: 'TNCHAR', type: 3 }, + { name: 'TNUMBER', type: 4 }, + { name: 'TDATE', type: 5 }, + { name: 'TTIMESTAMP', type: 6 }, + { name: 'TCLOB', type: 7 }, + { name: 'TNCLOB', type: 7 }, + { name: 'TBLOB', type: 8 } ]); + test.done(); + }); + }); + }); + }, + "utf8_chars_in_query": function(test) { var self = this, cyrillicString = "ั‚ะตัั‚"; @@ -209,5 +238,6 @@ exports['IntegrationTest'] = nodeunit.testCase({ test.equal(results[0]['TEST'], cyrillicString, "UTF8 characters in sql query should be preserved"); test.done(); }); - } + }, + }); diff --git a/test/outparams.js b/test/outparams.js index 43dc7e7..a69a438 100644 --- a/test/outparams.js +++ b/test/outparams.js @@ -41,7 +41,13 @@ outParam1 := 43; END; / - + CREATE OR REPLACE PROCEDURE procDateTimeOutParam(outParam1 OUT DATE, outParam2 OUT TIMESTAMP) + IS + BEGIN + outParam1 := CURRENT_TIMESTAMP; + outParam2 := CURRENT_TIMESTAMP; + END; + / CREATE OR REPLACE PROCEDURE procTwoOutParams(param1 IN VARCHAR2, outParam1 OUT NUMBER, outParam2 OUT STRING) IS BEGIN From d38192c8ae92030da12d3aac3d6ae0b96fab1d4b Mon Sep 17 00:00:00 2001 From: Eric Waldheim Date: Wed, 22 Oct 2014 08:30:41 -0600 Subject: [PATCH 2/2] fix callback argument error message. fix indentation. add callback argument error tests. --- src/connection.cpp | 31 ++++++++++++++++++------------- test/integration.js | 12 ++++++++++++ 2 files changed, 30 insertions(+), 13 deletions(-) diff --git a/src/connection.cpp b/src/connection.cpp index a806638..ddbed78 100644 --- a/src/connection.cpp +++ b/src/connection.cpp @@ -63,7 +63,13 @@ uni::CallbackType Connection::Execute(const uni::FunctionCallbackInfo& args) { options = Local::Cast(args[2]); ++cbIndex; } - REQ_FUN_ARG(cbIndex, callback); + if (args.Length() <= cbIndex || !args[cbIndex]->IsFunction()) + { + ostringstream oss; + oss << "Argument " << cbIndex << " must be a function"; + UNI_THROW(Exception::TypeError(String::New(oss.str().c_str()))); + } + Local callback = Local::Cast(args[cbIndex]); String::Utf8Value sqlVal(sql); @@ -743,14 +749,13 @@ Local Connection::CreateV8ArrayFromCols(std::vector columns) { Local v8cols = Array::New(columns.size()); uint32_t index = 0; - for (std::vector::iterator iterator = columns.begin(), end =columns.end(); - iterator != end; ++iterator, ++index) + for (std::vector::iterator iterator = columns.begin(), end =columns.end(); iterator != end; ++iterator, ++index) { - column_t* col = *iterator; - v8::Local v8col = v8::Object::New(); - v8col->Set(v8::String::New("name"), String::New(col->name.c_str())); - v8col->Set(v8::String::New("type"), Number::New((double)(col->type))); - v8cols->Set(index, v8col); + column_t* col = *iterator; + v8::Local v8col = v8::Object::New(); + v8col->Set(v8::String::New("name"), String::New(col->name.c_str())); + v8col->Set(v8::String::New("type"), Number::New((double)(col->type))); + v8cols->Set(index, v8col); } return v8cols; } @@ -784,11 +789,11 @@ void Connection::handleResult(ExecuteBaton* baton, Handle (&argv)[2]) { } else { argv[0] = Undefined(); if(baton->rows) { - Local obj = CreateV8ArrayFromRows(baton, baton->columns, baton->rows); - if (baton->getColumnMetaData) { - obj->Set(String::New("columnMetaData"), - CreateV8ArrayFromCols(baton->columns)); - } + Local obj = CreateV8ArrayFromRows(baton, baton->columns, baton->rows); + if (baton->getColumnMetaData) { + obj->Set(String::New("columnMetaData"), + CreateV8ArrayFromCols(baton->columns)); + } argv[1] = obj; if (baton->error) goto failed; // delete argv[1] ?? } else { diff --git a/test/integration.js b/test/integration.js index b7960da..483d3fc 100644 --- a/test/integration.js +++ b/test/integration.js @@ -240,4 +240,16 @@ exports['IntegrationTest'] = nodeunit.testCase({ }); }, + "errors": function(test) { + var self = this; + try { self.connection.execute("SELECT * FROM person", [], "bad arg"); } + catch(e) { test.equal(e.message, "Argument 2 must be a function"); } + try { self.connection.execute("SELECT * FROM person", [], {}); } + catch(e) { test.equal(e.message, "Argument 3 must be a function"); } + try { self.connection.execute("SELECT * FROM person", [], {}, "bad arg"); } + catch(e) { test.equal(e.message, "Argument 3 must be a function"); } + + test.done(); + }, + });