Skip to content

Commit cbfcb27

Browse files
committed
Merge pull request #153 from ValYouW/master
Add support for associative arrays
2 parents 5c5a488 + 6449473 commit cbfcb27

File tree

4 files changed

+292
-1
lines changed

4 files changed

+292
-1
lines changed

src/connection.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ int Connection::SetValuesOnStatement(oracle::occi::Statement* stmt, ExecuteBaton
205205
uint32_t index = 1;
206206
int outputParam = -1;
207207
OutParam * outParam = NULL;
208+
arrayParam_t* arrParam;
208209
for (vector<value_t*>::iterator iterator = values.begin(), end = values.end(); iterator != end; ++iterator, index++) {
209210
value_t* val = *iterator;
210211
int outParamType;
@@ -222,6 +223,11 @@ int Connection::SetValuesOnStatement(oracle::occi::Statement* stmt, ExecuteBaton
222223
case VALUE_TYPE_TIMESTAMP:
223224
stmt->setTimestamp(index, *((oracle::occi::Timestamp*)val->value));
224225
break;
226+
case VALUE_TYPE_ARRAY:
227+
stmt->setDatabaseNCHARParam(index, true);
228+
arrParam = (arrayParam_t*)val->value;
229+
stmt->setDataBufferArray(index, arrParam->value, arrParam->elementsType, arrParam->collectionLength, &arrParam->collectionLength, arrParam->elementsSize, arrParam->elementLength, NULL, NULL);
230+
break;
225231
case VALUE_TYPE_OUTPUT:
226232
outParam = static_cast<OutParam*>(val->value);
227233
// std::cout << "OutParam B: " << outParam << " "<< outParam->type() << " " << outParam->_inOut.hasInParam << std::endl;

src/executeBaton.cpp

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
#include "connection.h"
44
#include "outParam.h"
55
#include <iostream>
6+
#include <string.h>
7+
#include <cmath>
68
using namespace std;
79

810
ExecuteBaton::ExecuteBaton(Connection* connection, const char* sql, v8::Local<v8::Array>* values, v8::Handle<v8::Function>* callback) {
@@ -45,6 +47,18 @@ void ExecuteBaton::ResetValues() {
4547
case VALUE_TYPE_TIMESTAMP:
4648
delete (oracle::occi::Timestamp*)val->value;
4749
break;
50+
case VALUE_TYPE_ARRAY:
51+
arrayParam_t* arrParam = (arrayParam_t*)val->value;
52+
if (arrParam->value != NULL && arrParam->elementsType == oracle::occi::OCCI_SQLT_STR)
53+
delete (char*)arrParam->value;
54+
else if (arrParam->value != NULL && arrParam->elementsType == oracle::occi::OCCI_SQLT_NUM)
55+
delete (char*)arrParam->value;
56+
57+
if (arrParam->elementLength != NULL)
58+
delete arrParam->elementLength;
59+
60+
delete (arrayParam_t*)val->value;
61+
break;
4862
}
4963
delete val;
5064
}
@@ -136,6 +150,15 @@ void ExecuteBaton::CopyValuesToBaton(ExecuteBaton* baton, v8::Local<v8::Array>*
136150
baton->values.push_back(value);
137151
}
138152

153+
// array
154+
else if (val->IsArray()) {
155+
value->type = VALUE_TYPE_ARRAY;
156+
Local<Array> arr = Local<Array>::Cast(val);
157+
value->value = new arrayParam_t();
158+
GetVectorParam(baton, (arrayParam_t*)value->value, arr);
159+
baton->values.push_back(value);
160+
}
161+
139162
// output
140163
else if(val->IsObject() && val->ToObject()->FindInstanceInPrototypeChain(uni::Deref(OutParam::constructorTemplate)) != v8::Null()) {
141164
OutParam* op = node::ObjectWrap::Unwrap<OutParam>(val->ToObject());
@@ -161,6 +184,111 @@ void ExecuteBaton::CopyValuesToBaton(ExecuteBaton* baton, v8::Local<v8::Array>*
161184
baton->error = new std::string(message.str());
162185
return;
163186
}
187+
}
188+
}
189+
190+
void ExecuteBaton::GetVectorParam(ExecuteBaton* baton, arrayParam_t* arrParam, Local<Array> arr) {
191+
// In case the array is empty just initialize the fields as we would need something in Connection::SetValuesOnStatement
192+
if (arr->Length() < 1) {
193+
arrParam->value = new int[0];
194+
arrParam->collectionLength = 0;
195+
arrParam->elementsSize = 0;
196+
arrParam->elementLength = new ub2[0];
197+
arrParam->elementsType = oracle::occi::OCCIINT;
198+
return;
199+
}
200+
201+
// Next we create the array buffer that will be used later as the value for the param (in Connection::SetValuesOnStatement)
202+
// The array type will be derived from the type of the first element.
203+
Local<Value> val = arr->Get(0);
204+
205+
// String array
206+
if (val->IsString()) {
207+
arrParam->elementsType = oracle::occi::OCCI_SQLT_STR;
208+
209+
// Find the longest string, this is necessary in order to create a buffer later.
210+
int longestString = 0;
211+
for(unsigned int i = 0; i < arr->Length(); i++) {
212+
Local<Value> currVal = arr->Get(i);
213+
if (currVal->ToString()->Utf8Length() > longestString)
214+
longestString = currVal->ToString()->Utf8Length();
215+
}
216+
217+
// Add 1 for '\0'
218+
++longestString;
219+
220+
// Create a long char* that will hold the entire array, it is important to create a FIXED SIZE array,
221+
// meaning all strings have the same allocated length.
222+
char* strArr = new char[arr->Length() * longestString];
223+
arrParam->elementLength = new ub2[arr->Length()];
224+
225+
// loop thru the arr and copy the strings into the strArr
226+
int bytesWritten = 0;
227+
for(unsigned int i = 0; i < arr->Length(); i++) {
228+
Local<Value> currVal = arr->Get(i);
229+
if(!currVal->IsString()) {
230+
std::ostringstream message;
231+
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";
232+
baton->error = new std::string(message.str());
233+
return;
234+
}
235+
236+
String::Utf8Value utfStr(currVal);
237+
238+
// Copy this string onto the strArr (we put \0 in the beginning as this is what strcat expects).
239+
strArr[bytesWritten] = '\0';
240+
strncat(strArr + bytesWritten, *utfStr, longestString);
241+
bytesWritten += longestString;
242+
243+
// Set the length of this element, add +1 for the '\0'
244+
arrParam->elementLength[i] = utfStr.length() + 1;
245+
}
246+
247+
arrParam->value = strArr;
248+
arrParam->collectionLength = arr->Length();
249+
arrParam->elementsSize = longestString;
250+
}
251+
252+
// Integer array.
253+
else if (val->IsNumber()) {
254+
arrParam->elementsType = oracle::occi::OCCI_SQLT_NUM;
255+
256+
// Allocate memory for the numbers array, Number in Oracle is 21 bytes
257+
unsigned char* numArr = new unsigned char[arr->Length() * 21];
258+
arrParam->elementLength = new ub2[arr->Length()];
259+
260+
for(unsigned int i = 0; i < arr->Length(); i++) {
261+
Local<Value> currVal = arr->Get(i);
262+
if(!currVal->IsNumber()) {
263+
std::ostringstream message;
264+
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";
265+
baton->error = new std::string(message.str());
266+
return;
267+
}
268+
269+
// JS numbers can exceed oracle numbers, make sure this is not the case.
270+
double d = currVal->ToNumber()->Value();
271+
if (d > 9.99999999999999999999999999999999999999*std::pow(10, 125) || d < -9.99999999999999999999999999999999999999*std::pow(10, 125)) {
272+
std::ostringstream message;
273+
message << "Input array has number that is out of the range of Oracle numbers, check the number at index " << i;
274+
baton->error = new std::string(message.str());
275+
return;
276+
}
277+
278+
// Convert the JS number into Oracle Number and get its bytes representation
279+
oracle::occi::Number n = d;
280+
oracle::occi::Bytes b = n.toBytes();
281+
arrParam->elementLength[i] = b.length ();
282+
b.getBytes(&numArr[i*21], b.length());
283+
}
284+
285+
arrParam->value = numArr;
286+
arrParam->collectionLength = arr->Length();
287+
arrParam->elementsSize = 21;
288+
}
164289

290+
// Unsupported type
291+
else {
292+
baton->error = new std::string("The type of the first element in the input array is not supported");
165293
}
166294
}

src/executeBaton.h

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ enum {
2323
VALUE_TYPE_DATE = 5,
2424
VALUE_TYPE_TIMESTAMP = 6,
2525
VALUE_TYPE_CLOB = 7,
26-
VALUE_TYPE_BLOB = 8
26+
VALUE_TYPE_BLOB = 8,
27+
VALUE_TYPE_ARRAY = 9
2728
};
2829

2930
struct column_t {
@@ -41,6 +42,15 @@ struct value_t {
4142
void* value;
4243
};
4344

45+
struct arrayParam_t {
46+
// This will hold the info needed for binding array values
47+
void* value;
48+
ub4 collectionLength;
49+
sb4 elementsSize; // The size of each element in the array
50+
ub2* elementLength; // An array that holds the actual length of each element in the array (in case of strings)
51+
oracle::occi::Type elementsType;
52+
};
53+
4454
struct output_t {
4555
int type;
4656
int index;
@@ -77,6 +87,7 @@ class ExecuteBaton {
7787
int updateCount;
7888

7989
static void CopyValuesToBaton(ExecuteBaton* baton, v8::Local<v8::Array>* values);
90+
static void GetVectorParam(ExecuteBaton* baton, arrayParam_t *value, v8::Local<v8::Array> arr);
8091
};
8192

8293
#endif

test/assocArrays.js

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
/*
2+
tests-settings.json:
3+
{
4+
"hostname": "localhost",
5+
"user": "test",
6+
"password": "test"
7+
}
8+
*/
9+
10+
var nodeunit = require("nodeunit");
11+
var oracle = require("../");
12+
13+
var settings;
14+
15+
try {
16+
settings = JSON.parse(require('fs').readFileSync('./tests-settings-custom.json', 'utf8'));
17+
} catch (ex) {}
18+
settings = settings || JSON.parse(require('fs').readFileSync('./tests-settings.json', 'utf8'));
19+
20+
function initDb(connection, cb) {
21+
// Create the TEST_PKG for the test (first spec then body).
22+
23+
var spec = '\
24+
CREATE OR REPLACE PACKAGE "TEST_PKG" IS \
25+
TYPE Y_STRINGS_TABLE IS TABLE OF NVARCHAR2(4000) INDEX BY PLS_INTEGER; \
26+
TYPE Y_NUMBERS_TABLE IS TABLE OF NUMBER INDEX BY PLS_INTEGER; \
27+
PROCEDURE sp_get_numbers(i_arr Y_NUMBERS_TABLE, o_out OUT sys_refcursor); \
28+
PROCEDURE sp_get_strings(i_arr Y_STRINGS_TABLE, o_out OUT sys_refcursor); \
29+
END test_pkg;'
30+
31+
var body = '\
32+
CREATE OR REPLACE PACKAGE BODY "TEST_PKG" IS \
33+
PROCEDURE sp_get_numbers(i_arr Y_NUMBERS_TABLE, o_out OUT sys_refcursor) IS \
34+
vals sys.ODCINumberList := sys.ODCINumberList(); \
35+
BEGIN \
36+
FOR i IN i_arr.first..i_arr.last LOOP \
37+
vals.extend(1); \
38+
vals(i) := i_arr(i); \
39+
END LOOP; \
40+
OPEN o_out FOR SELECT * FROM TABLE(vals); \
41+
END; \
42+
PROCEDURE sp_get_strings(i_arr Y_STRINGS_TABLE, o_out OUT sys_refcursor) IS \
43+
vals sys.ODCIVarchar2List := sys.ODCIVarchar2List(); \
44+
BEGIN \
45+
FOR i IN i_arr.first..i_arr.last LOOP \
46+
vals.extend(1); \
47+
vals(i) := i_arr(i); \
48+
END LOOP; \
49+
OPEN o_out FOR SELECT * FROM TABLE(vals); \
50+
END; \
51+
END test_pkg;';
52+
53+
connection.execute(spec, [], function(err) {
54+
if (err) throw err;
55+
56+
connection.execute(body, [], function(err) {
57+
if (err) throw err;
58+
cb();
59+
});
60+
});
61+
}
62+
63+
64+
exports['AssocArrays'] = nodeunit.testCase({
65+
setUp: function(callback) {
66+
var self = this;
67+
oracle.connect(settings, function(err, connection) {
68+
if (err) return callback(err);
69+
self.connection = connection;
70+
initDb(self.connection, callback);
71+
});
72+
},
73+
74+
tearDown: function(callback) {
75+
if (this.connection) {
76+
this.connection.close();
77+
}
78+
callback();
79+
},
80+
81+
"AssocArrays - Select using a numbers array": function(test) {
82+
var out = new oracle.OutParam(oracle.OCCICURSOR);
83+
var arr = [12.453, -98.31, -5, 5, 3.876e123, -3.876e123];
84+
this.connection.execute('Begin TEST_PKG.sp_get_numbers(:1, :2); End;', [arr, out], function(err, results) {
85+
if(err) { console.error(err); return; }
86+
test.equal(results.returnParam.length, arr.length);
87+
88+
// Loop thru all the values we passed and check that we got them back.
89+
// Due to floating point precision (and exponential notation) we are testing
90+
// that they are "close enough".
91+
arr.forEach(function(val, i) {
92+
var res = results.returnParam[i]['COLUMN_VALUE'];
93+
test.ok(Math.abs(res/val - 1) < 0.001, 'Expected: ' + val + ' Got: ' + res);
94+
});
95+
test.done();
96+
});
97+
},
98+
99+
"AssocArrays - Select using too big positive number": function(test) {
100+
var out = new oracle.OutParam(oracle.OCCICURSOR);
101+
var arr = [12.453, Number.MAX_VALUE, -5, 5, 0];
102+
var self = this;
103+
test.throws(function(){
104+
self.connection.execute('Begin TEST_PKG.sp_get_numbers(:1, :2); End;', [arr, out], function(err, results) {});
105+
});
106+
test.done();
107+
},
108+
109+
"AssocArrays - Select using too big negative number": function(test) {
110+
var out = new oracle.OutParam(oracle.OCCICURSOR);
111+
var arr = [12.453, -1*Number.MAX_VALUE, -5, 5, 0];
112+
var self = this;
113+
test.throws(function(){
114+
self.connection.execute('Begin TEST_PKG.sp_get_numbers(:1, :2); End;', [arr, out], function(err, results) {});
115+
});
116+
test.done();
117+
},
118+
119+
"AssocArrays - Select using strings": function(test) {
120+
var out = new oracle.OutParam(oracle.OCCICURSOR);
121+
var arr = ['1234567890', 'ThE ', 'quick ', ' BrOwN ','fox' ,'Ju m p s', '!!!', 'noo ??', '~!@#$%^&*()_+', ''];
122+
this.connection.execute('Begin TEST_PKG.sp_get_strings(:1, :2); End;', [arr, out], function(err, results) {
123+
if(err) { console.error(err); return; }
124+
test.equal(results.returnParam.length, arr.length);
125+
arr.forEach(function(val, i) {
126+
var res = results.returnParam[i]['COLUMN_VALUE'] || '';
127+
test.equal(res, val);
128+
});
129+
test.done();
130+
});
131+
},
132+
133+
"AssocArrays - Select using UTF8 strings": function(test) {
134+
var out = new oracle.OutParam(oracle.OCCICURSOR);
135+
var arr = ['тест', ' тест ', '12тест34', 'AB тест'];
136+
this.connection.execute('Begin TEST_PKG.sp_get_strings(:1, :2); End;', [arr, out], function(err, results) {
137+
if(err) { console.error(err); return; }
138+
test.equal(results.returnParam.length, arr.length);
139+
arr.forEach(function(val, i) {
140+
var res = results.returnParam[i]['COLUMN_VALUE'] || '';
141+
test.equal(res, val);
142+
});
143+
test.done();
144+
});
145+
}
146+
});

0 commit comments

Comments
 (0)