Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Add support for associative arrays #153

Merged
merged 13 commits into from

5 participants

@ValYouW

Hi,

In my work I needed to pass assoc arrays as parameters to stored-procedures so I've added this option to node-oracle.
I am using setDataBufferArray of occi and not SetVector because the latter doesn't support assoc arrays that are defined in Packages (SetVector should get the assoc array type name).

The last time I did C++ was > 10 years ago so my code might have issues, anyone care to review ??
Thx.

@bjouhier

Looks good to me overall. I just noticed 2 points that could be improved:

  • typo: elemetnsType -> elementsType
  • value_t is growing. Would'nt it be better to define a separate structure for the associative array info and store an instance of this structure in value_t's value.
@ValYouW

OK, Will give it a shot and see how it looks...

@ValYouW

Pushed new commit. Is this what you meant?
thx.

@vizjerai

I wasn't able to compile it until I added 2 missing includes at the top of executeBaton.cpp.

#include <string.h>
#include <cmath>
@ValYouW

I had no issues on Windows, will double check on my Ubuntu. Thx.

@bjouhier

Yes, that's what I meant. Looks good.

@ValYouW

@vizjerai Thank you, indeed on Linux those includes were missing... pushed a fix. Thx!

@fuson

Thank you for your work! Hope you will continue work on this module

@raztus raztus merged commit cbfcb27 into from
@raztus
Collaborator

@ValYouW Please try this again from the current master and confirm that your issue is resolved. I'd like to have some feedback before we push to npm.

@ValYouW

Hi,

Seems ok, thx.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Feb 28, 2014
  1. @ValYouW
Commits on Mar 1, 2014
  1. @ValYouW
  2. @ValYouW
  3. @ValYouW

    Remove OCCIVECTOR type

    ValYouW authored
Commits on Mar 2, 2014
  1. @ValYouW
  2. @ValYouW

    Fix spacing

    ValYouW authored
  3. @ValYouW

    Fix spacing

    ValYouW authored
  4. @ValYouW

    Merge branch 'master' of https://github.com/yuvalw/node-oracle

    ValYouW authored
    Conflicts:
    	src/executeBaton.cpp
Commits on Mar 3, 2014
  1. @ValYouW
  2. @ValYouW
Commits on Mar 6, 2014
  1. @ValYouW
  2. @ValYouW

    [=] Fix typo

    ValYouW authored
Commits on Mar 8, 2014
  1. @ValYouW
This page is out of date. Refresh to see the latest.
View
6 src/connection.cpp
@@ -205,6 +205,7 @@ int Connection::SetValuesOnStatement(oracle::occi::Statement* stmt, ExecuteBaton
uint32_t index = 1;
int outputParam = -1;
OutParam * outParam = NULL;
+ arrayParam_t* arrParam;
for (vector<value_t*>::iterator iterator = values.begin(), end = values.end(); iterator != end; ++iterator, index++) {
value_t* val = *iterator;
int outParamType;
@@ -222,6 +223,11 @@ int Connection::SetValuesOnStatement(oracle::occi::Statement* stmt, ExecuteBaton
case VALUE_TYPE_TIMESTAMP:
stmt->setTimestamp(index, *((oracle::occi::Timestamp*)val->value));
break;
+ case VALUE_TYPE_ARRAY:
+ stmt->setDatabaseNCHARParam(index, true);
+ arrParam = (arrayParam_t*)val->value;
+ stmt->setDataBufferArray(index, arrParam->value, arrParam->elementsType, arrParam->collectionLength, &arrParam->collectionLength, arrParam->elementsSize, arrParam->elementLength, NULL, NULL);
+ break;
case VALUE_TYPE_OUTPUT:
outParam = static_cast<OutParam*>(val->value);
// std::cout << "OutParam B: " << outParam << " "<< outParam->type() << " " << outParam->_inOut.hasInParam << std::endl;
View
128 src/executeBaton.cpp
@@ -3,6 +3,8 @@
#include "connection.h"
#include "outParam.h"
#include <iostream>
+#include <string.h>
+#include <cmath>
using namespace std;
ExecuteBaton::ExecuteBaton(Connection* connection, const char* sql, v8::Local<v8::Array>* values, v8::Handle<v8::Function>* callback) {
@@ -45,6 +47,18 @@ void ExecuteBaton::ResetValues() {
case VALUE_TYPE_TIMESTAMP:
delete (oracle::occi::Timestamp*)val->value;
break;
+ case VALUE_TYPE_ARRAY:
+ arrayParam_t* arrParam = (arrayParam_t*)val->value;
+ if (arrParam->value != NULL && arrParam->elementsType == oracle::occi::OCCI_SQLT_STR)
+ delete (char*)arrParam->value;
+ else if (arrParam->value != NULL && arrParam->elementsType == oracle::occi::OCCI_SQLT_NUM)
+ delete (char*)arrParam->value;
+
+ if (arrParam->elementLength != NULL)
+ delete arrParam->elementLength;
+
+ delete (arrayParam_t*)val->value;
+ break;
}
delete val;
}
@@ -136,6 +150,15 @@ void ExecuteBaton::CopyValuesToBaton(ExecuteBaton* baton, v8::Local<v8::Array>*
baton->values.push_back(value);
}
+ // array
+ else if (val->IsArray()) {
+ value->type = VALUE_TYPE_ARRAY;
+ Local<Array> arr = Local<Array>::Cast(val);
+ value->value = new arrayParam_t();
+ GetVectorParam(baton, (arrayParam_t*)value->value, arr);
+ baton->values.push_back(value);
+ }
+
// output
else if(val->IsObject() && val->ToObject()->FindInstanceInPrototypeChain(uni::Deref(OutParam::constructorTemplate)) != v8::Null()) {
OutParam* op = node::ObjectWrap::Unwrap<OutParam>(val->ToObject());
@@ -161,6 +184,111 @@ void ExecuteBaton::CopyValuesToBaton(ExecuteBaton* baton, v8::Local<v8::Array>*
baton->error = new std::string(message.str());
return;
}
+ }
+}
+
+void ExecuteBaton::GetVectorParam(ExecuteBaton* baton, arrayParam_t* arrParam, Local<Array> arr) {
+ // In case the array is empty just initialize the fields as we would need something in Connection::SetValuesOnStatement
+ if (arr->Length() < 1) {
+ arrParam->value = new int[0];
+ arrParam->collectionLength = 0;
+ arrParam->elementsSize = 0;
+ arrParam->elementLength = new ub2[0];
+ arrParam->elementsType = oracle::occi::OCCIINT;
+ return;
+ }
+
+ // Next we create the array buffer that will be used later as the value for the param (in Connection::SetValuesOnStatement)
+ // The array type will be derived from the type of the first element.
+ Local<Value> val = arr->Get(0);
+
+ // String array
+ if (val->IsString()) {
+ arrParam->elementsType = oracle::occi::OCCI_SQLT_STR;
+
+ // Find the longest string, this is necessary in order to create a buffer later.
+ int longestString = 0;
+ for(unsigned int i = 0; i < arr->Length(); i++) {
+ Local<Value> currVal = arr->Get(i);
+ if (currVal->ToString()->Utf8Length() > longestString)
+ longestString = currVal->ToString()->Utf8Length();
+ }
+
+ // Add 1 for '\0'
+ ++longestString;
+
+ // Create a long char* that will hold the entire array, it is important to create a FIXED SIZE array,
+ // meaning all strings have the same allocated length.
+ char* strArr = new char[arr->Length() * longestString];
+ arrParam->elementLength = new ub2[arr->Length()];
+
+ // loop thru the arr and copy the strings into the strArr
+ int bytesWritten = 0;
+ for(unsigned int i = 0; i < arr->Length(); i++) {
+ Local<Value> currVal = arr->Get(i);
+ if(!currVal->IsString()) {
+ std::ostringstream message;
+ message << "Input array has object with invalid type at index " << i << ", all object must be of type 'string' which is the type of the first element";
+ baton->error = new std::string(message.str());
+ return;
+ }
+
+ String::Utf8Value utfStr(currVal);
+
+ // Copy this string onto the strArr (we put \0 in the beginning as this is what strcat expects).
+ strArr[bytesWritten] = '\0';
+ strncat(strArr + bytesWritten, *utfStr, longestString);
+ bytesWritten += longestString;
+
+ // Set the length of this element, add +1 for the '\0'
+ arrParam->elementLength[i] = utfStr.length() + 1;
+ }
+
+ arrParam->value = strArr;
+ arrParam->collectionLength = arr->Length();
+ arrParam->elementsSize = longestString;
+ }
+
+ // Integer array.
+ else if (val->IsNumber()) {
+ arrParam->elementsType = oracle::occi::OCCI_SQLT_NUM;
+
+ // Allocate memory for the numbers array, Number in Oracle is 21 bytes
+ unsigned char* numArr = new unsigned char[arr->Length() * 21];
+ arrParam->elementLength = new ub2[arr->Length()];
+
+ for(unsigned int i = 0; i < arr->Length(); i++) {
+ Local<Value> currVal = arr->Get(i);
+ if(!currVal->IsNumber()) {
+ std::ostringstream message;
+ message << "Input array has object with invalid type at index " << i << ", all object must be of type 'number' which is the type of the first element";
+ baton->error = new std::string(message.str());
+ return;
+ }
+
+ // JS numbers can exceed oracle numbers, make sure this is not the case.
+ double d = currVal->ToNumber()->Value();
+ if (d > 9.99999999999999999999999999999999999999*std::pow(10, 125) || d < -9.99999999999999999999999999999999999999*std::pow(10, 125)) {
+ std::ostringstream message;
+ message << "Input array has number that is out of the range of Oracle numbers, check the number at index " << i;
+ baton->error = new std::string(message.str());
+ return;
+ }
+
+ // Convert the JS number into Oracle Number and get its bytes representation
+ oracle::occi::Number n = d;
+ oracle::occi::Bytes b = n.toBytes();
+ arrParam->elementLength[i] = b.length ();
+ b.getBytes(&numArr[i*21], b.length());
+ }
+
+ arrParam->value = numArr;
+ arrParam->collectionLength = arr->Length();
+ arrParam->elementsSize = 21;
+ }
+ // Unsupported type
+ else {
+ baton->error = new std::string("The type of the first element in the input array is not supported");
}
}
View
13 src/executeBaton.h
@@ -23,7 +23,8 @@ enum {
VALUE_TYPE_DATE = 5,
VALUE_TYPE_TIMESTAMP = 6,
VALUE_TYPE_CLOB = 7,
- VALUE_TYPE_BLOB = 8
+ VALUE_TYPE_BLOB = 8,
+ VALUE_TYPE_ARRAY = 9
};
struct column_t {
@@ -41,6 +42,15 @@ struct value_t {
void* value;
};
+struct arrayParam_t {
+ // This will hold the info needed for binding array values
+ void* value;
+ ub4 collectionLength;
+ sb4 elementsSize; // The size of each element in the array
+ ub2* elementLength; // An array that holds the actual length of each element in the array (in case of strings)
+ oracle::occi::Type elementsType;
+};
+
struct output_t {
int type;
int index;
@@ -77,6 +87,7 @@ class ExecuteBaton {
int updateCount;
static void CopyValuesToBaton(ExecuteBaton* baton, v8::Local<v8::Array>* values);
+ static void GetVectorParam(ExecuteBaton* baton, arrayParam_t *value, v8::Local<v8::Array> arr);
};
#endif
View
146 test/assocArrays.js
@@ -0,0 +1,146 @@
+/*
+ tests-settings.json:
+ {
+ "hostname": "localhost",
+ "user": "test",
+ "password": "test"
+ }
+ */
+
+var nodeunit = require("nodeunit");
+var oracle = require("../");
+
+var settings;
+
+try {
+ settings = JSON.parse(require('fs').readFileSync('./tests-settings-custom.json', 'utf8'));
+} catch (ex) {}
+settings = settings || JSON.parse(require('fs').readFileSync('./tests-settings.json', 'utf8'));
+
+function initDb(connection, cb) {
+ // Create the TEST_PKG for the test (first spec then body).
+
+ var spec = '\
+ CREATE OR REPLACE PACKAGE "TEST_PKG" IS \
+ TYPE Y_STRINGS_TABLE IS TABLE OF NVARCHAR2(4000) INDEX BY PLS_INTEGER; \
+ TYPE Y_NUMBERS_TABLE IS TABLE OF NUMBER INDEX BY PLS_INTEGER; \
+ PROCEDURE sp_get_numbers(i_arr Y_NUMBERS_TABLE, o_out OUT sys_refcursor); \
+ PROCEDURE sp_get_strings(i_arr Y_STRINGS_TABLE, o_out OUT sys_refcursor); \
+ END test_pkg;'
+
+ var body = '\
+ CREATE OR REPLACE PACKAGE BODY "TEST_PKG" IS \
+ PROCEDURE sp_get_numbers(i_arr Y_NUMBERS_TABLE, o_out OUT sys_refcursor) IS \
+ vals sys.ODCINumberList := sys.ODCINumberList(); \
+ BEGIN \
+ FOR i IN i_arr.first..i_arr.last LOOP \
+ vals.extend(1); \
+ vals(i) := i_arr(i); \
+ END LOOP; \
+ OPEN o_out FOR SELECT * FROM TABLE(vals); \
+ END; \
+ PROCEDURE sp_get_strings(i_arr Y_STRINGS_TABLE, o_out OUT sys_refcursor) IS \
+ vals sys.ODCIVarchar2List := sys.ODCIVarchar2List(); \
+ BEGIN \
+ FOR i IN i_arr.first..i_arr.last LOOP \
+ vals.extend(1); \
+ vals(i) := i_arr(i); \
+ END LOOP; \
+ OPEN o_out FOR SELECT * FROM TABLE(vals); \
+ END; \
+ END test_pkg;';
+
+ connection.execute(spec, [], function(err) {
+ if (err) throw err;
+
+ connection.execute(body, [], function(err) {
+ if (err) throw err;
+ cb();
+ });
+ });
+}
+
+
+exports['AssocArrays'] = nodeunit.testCase({
+ setUp: function(callback) {
+ var self = this;
+ oracle.connect(settings, function(err, connection) {
+ if (err) return callback(err);
+ self.connection = connection;
+ initDb(self.connection, callback);
+ });
+ },
+
+ tearDown: function(callback) {
+ if (this.connection) {
+ this.connection.close();
+ }
+ callback();
+ },
+
+ "AssocArrays - Select using a numbers array": function(test) {
+ var out = new oracle.OutParam(oracle.OCCICURSOR);
+ var arr = [12.453, -98.31, -5, 5, 3.876e123, -3.876e123];
+ this.connection.execute('Begin TEST_PKG.sp_get_numbers(:1, :2); End;', [arr, out], function(err, results) {
+ if(err) { console.error(err); return; }
+ test.equal(results.returnParam.length, arr.length);
+
+ // Loop thru all the values we passed and check that we got them back.
+ // Due to floating point precision (and exponential notation) we are testing
+ // that they are "close enough".
+ arr.forEach(function(val, i) {
+ var res = results.returnParam[i]['COLUMN_VALUE'];
+ test.ok(Math.abs(res/val - 1) < 0.001, 'Expected: ' + val + ' Got: ' + res);
+ });
+ test.done();
+ });
+ },
+
+ "AssocArrays - Select using too big positive number": function(test) {
+ var out = new oracle.OutParam(oracle.OCCICURSOR);
+ var arr = [12.453, Number.MAX_VALUE, -5, 5, 0];
+ var self = this;
+ test.throws(function(){
+ self.connection.execute('Begin TEST_PKG.sp_get_numbers(:1, :2); End;', [arr, out], function(err, results) {});
+ });
+ test.done();
+ },
+
+ "AssocArrays - Select using too big negative number": function(test) {
+ var out = new oracle.OutParam(oracle.OCCICURSOR);
+ var arr = [12.453, -1*Number.MAX_VALUE, -5, 5, 0];
+ var self = this;
+ test.throws(function(){
+ self.connection.execute('Begin TEST_PKG.sp_get_numbers(:1, :2); End;', [arr, out], function(err, results) {});
+ });
+ test.done();
+ },
+
+ "AssocArrays - Select using strings": function(test) {
+ var out = new oracle.OutParam(oracle.OCCICURSOR);
+ var arr = ['1234567890', 'ThE ', 'quick ', ' BrOwN ','fox' ,'Ju m p s', '!!!', 'noo ??', '~!@#$%^&*()_+', ''];
+ this.connection.execute('Begin TEST_PKG.sp_get_strings(:1, :2); End;', [arr, out], function(err, results) {
+ if(err) { console.error(err); return; }
+ test.equal(results.returnParam.length, arr.length);
+ arr.forEach(function(val, i) {
+ var res = results.returnParam[i]['COLUMN_VALUE'] || '';
+ test.equal(res, val);
+ });
+ test.done();
+ });
+ },
+
+ "AssocArrays - Select using UTF8 strings": function(test) {
+ var out = new oracle.OutParam(oracle.OCCICURSOR);
+ var arr = ['тест', ' тест ', '12тест34', 'AB тест'];
+ this.connection.execute('Begin TEST_PKG.sp_get_strings(:1, :2); End;', [arr, out], function(err, results) {
+ if(err) { console.error(err); return; }
+ test.equal(results.returnParam.length, arr.length);
+ arr.forEach(function(val, i) {
+ var res = results.returnParam[i]['COLUMN_VALUE'] || '';
+ test.equal(res, val);
+ });
+ test.done();
+ });
+ }
+});
Something went wrong with that request. Please try again.