From 1d40b96cff3e1b3fb798a6dc63c6150d5b9889fb Mon Sep 17 00:00:00 2001 From: Dieter Oberkofler Date: Sun, 31 Jan 2016 12:04:57 +0100 Subject: [PATCH] Merged with version 1.6 and reintegrated the positional binding of PL/SQL indexed tables --- CHANGELOG.md | 20 + INSTALL.md | 56 +- README.md | 50 +- binding.gyp | 2 +- doc/api.md | 191 ++++- examples/blobinsert1.js | 35 +- examples/blobstream1.js | 11 +- examples/blobstream2.js | 5 +- examples/clobinsert1.js | 21 +- examples/clobinsert2.js | 25 +- examples/clobstream1.js | 11 +- examples/clobstream2.js | 14 +- examples/clobupdate1.js | 30 +- examples/demo.sql | 49 +- examples/demodrop.sql | 6 +- examples/selectjsonclob.js | 52 +- lib/oracledb.js | 21 +- my_test.cmd | 2 +- package.json | 2 +- src/dpi/include/dpiStmt.h | 4 +- src/dpi/src/dpiStmtImpl.cpp | 9 +- src/dpi/src/dpiStmtImpl.h | 4 +- src/njs/src/njsConnection.cpp | 931 +++++++++++------------- src/njs/src/njsConnection.h | 48 +- src/njs/src/njsMessages.cpp | 18 +- src/njs/src/njsMessages.h | 11 +- src/njs/src/njsOracle.h | 2 +- src/njs/src/njsUtils.h | 19 +- test/clobPlsqlString.js | 173 +++-- test/dataTypeChar.js | 211 +++++- test/dataTypeRaw.js | 23 +- test/dmlReturning.js | 63 ++ test/list.txt | 44 +- test/{PLSQLbinds.js => plsqlBinding.js} | 697 ++++++++++++++---- 34 files changed, 1979 insertions(+), 881 deletions(-) rename test/{PLSQLbinds.js => plsqlBinding.js} (50%) diff --git a/CHANGELOG.md b/CHANGELOG.md index e032d21..df5a108 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,25 @@ # Change Log +## node-oracledb v1.6.0 (30 Jan 2016) + +- Added support for binding PL/SQL Collection Associative Array + (Index-by) types containing numbers and strings. + +- Fixed a LOB problem causing an uncaught error to be generated. + +- Removed the 'close' event that was incorrectly emitted for LOB Writable + Streams. The Node.js Streams documentation specifies it only for + Readable Streams. + +- Updated the LOB examples to show connection release. + +- Updated README so first-time users see pre-requisites earlier. + +- Extended the OS X install instructions with a way to install that doesn't + need root access for Instant Client 11.2 on El Capitan. + +- Added RPATH link option when building on OS X in preparation for future client. + ## node-oracledb v1.5.0 (21 Dec 2015) - Treat Oracle Database 'Success With Info' warnings as success. diff --git a/INSTALL.md b/INSTALL.md index 26ff8a6..bfb32de 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -399,19 +399,18 @@ Building node-oracledb requires Xcode from the Mac App store. ### 5.2 Install Node.js -Node.js can be installed from various sources, such as via *brew*. +Download the [Node.js package](http://nodejs.org) for OS X 64-bit and install it. -``` -brew install node -``` +### 5.3 Install the free Oracle Instant Client 'Basic' and 'SDK' ZIPs -Set your PATH to include the *node* and *npm* binaries: +Do either of the options given in [5.3.1](#instosxICroot) or [5.3.2](#instosxICuser). -``` -export PATH=/usr/local/bin:$PATH -``` +### 5.3.1 Install Instant Client in /opt -### 5.3 Install the free Oracle Instant Client 'Basic' and 'SDK' ZIPs +This first installation option puts Instant Client in the default +location used by the node-oracledb installer. It requires root +access. If you don't want to update system directories then follow +the alternative steps in [5.3.2](#instosxICuser). Download the free **Basic** and **SDK** ZIPs from [Oracle Technology Network](http://www.oracle.com/technetwork/topics/intel-macsoft-096467.html) @@ -434,7 +433,44 @@ Link the OCI libraries into the default library path: ln -s /opt/oracle/instantclient/{libclntsh.dylib.11.1,libnnz11.dylib,libociei.dylib} /usr/local/lib/ ``` -### 5.4 Install the add-on +Continue with [5.4](#instosxICaddon). + +### 5.3.2 Install Instant Client in a user directory + +This is an alternative to [5.3.1](#instosxICroot) that does not require root access. + +Download the free **Basic** and **SDK** 64-bit ZIPs from +[Oracle Technology Network](http://www.oracle.com/technetwork/topics/intel-macsoft-096467.html) +and +[install them](http://www.oracle.com/technetwork/topics/intel-macsoft-096467.html#ic_osx_inst) +into the same directory: + +``` +unzip instantclient-basic-macos.x64-11.2.0.4.0.zip +unzip instantclient-sdk-macos.x64-11.2.0.4.0.zip +cd instantclient_11_2 +ln -s libclntsh.dylib.11.1 libclntsh.dylib +``` + +Link the OCI libraries into the user default library path: + +``` +mkdir ~/lib +ln -s $(pwd)/{libclntsh.dylib,libclntsh.dylib.11.1,libnnz11.dylib,libociei.dylib} ~/lib/ +``` + +To allow the node-oracledb installer to find the Instant Client +libraries and headers, set the install-time variables `OCI_LIB_DIR` +and `OCI_INC_DIR` to the appropriate directories: + +``` +export OCI_LIB_DIR=~/lib +export OCI_INC_DIR=~/instantclient_11_2/sdk/include +``` + +These variables are only needed during installation. + +### 5.4 Install the add-on If you are behind a firewall you may need to set your proxy, for example: diff --git a/README.md b/README.md index c4f94a1..9a9578a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# node-oracledb version 1.5 +# node-oracledb version 1.6 -## 1. About node-oracledb +## About node-oracledb The node-oracledb add-on for Node.js powers high performance Oracle Database applications. @@ -38,6 +38,23 @@ We are actively working on supporting the best Oracle Database features, and on functionality requests from [users involved in the project](https://github.com/oracle/node-oracledb/issues). +## Installation + +Prerequisites: + +- [Python 2.7](https://www.python.org/downloads/) +- C Compiler with support for C++ 11 (Xcode, gcc, Visual Studio or similar) +- The small, free [Oracle Instant Client](http://www.oracle.com/technetwork/database/features/instant-client/index-100365.html) libraries if your database is remote. Or use a locally installed database such as the free [Oracle XE](http://www.oracle.com/technetwork/database/database-technologies/express-edition/overview/index.html) release +- Set `OCI_LIB_DIR` and `OCI_INC_DIR` during installation if the Oracle libraries and headers are in a non-default location + +Run `npm install oracledb` to install from the [NPM registry](https://www.npmjs.com/package/oracledb). + +See [INSTALL](https://github.com/oracle/node-oracledb/tree/master/INSTALL.md) for details. + +## Examples + +There are examples in the [examples](https://github.com/oracle/node-oracledb/tree/master/examples) directory. + ### A simple query example: ```javascript @@ -72,44 +89,29 @@ With Oracle's sample HR schema, the output is: [ [ 60, 'IT' ], [ 90, 'Executive' ], [ 100, 'Finance' ] ] ``` -## 2. Examples - -There are examples in the [examples](https://github.com/oracle/node-oracledb/tree/master/examples) directory. - -## 3. Installation - -The basic install steps are: - -- Install the small, free [Oracle Instant Client](http://www.oracle.com/technetwork/database/features/instant-client/index-100365.html) libraries if your database is remote. Or use a locally installed database such as the free [Oracle XE](http://www.oracle.com/technetwork/database/database-technologies/express-edition/overview/index.html) release. -- Run `npm install oracledb` to install from the [NPM registry](https://www.npmjs.com/package/oracledb). - -See [INSTALL](https://github.com/oracle/node-oracledb/tree/master/INSTALL.md) for details. - -## 4. Documentation +## Documentation See [Documentation for the Oracle Database Node.js Add-on](https://github.com/oracle/node-oracledb/tree/master/doc/api.md). -## 5. Changes +## Changes See [CHANGELOG](https://github.com/oracle/node-oracledb/tree/master/CHANGELOG.md) -*Note* there were two small, backward-compatibility breaking attribute name changes in node-oracledb 0.5. - -## 6. Testsuite +## Testsuite To run the included testsuite see [test/README](https://github.com/oracle/node-oracledb/tree/master/test/README.md). -## 7. Contributing +## Contributing -Node-oracledb is an open source project. See +Node-oracledb is an open source project. See [CONTRIBUTING](https://github.com/oracle/node-oracledb/tree/master/CONTRIBUTING.md) for details. Oracle gratefully acknowledges the contributions to node-oracledb that have been made by the community. -## 8. License +## License -Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. +Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved. You may not use the identified files except in compliance with the Apache License, Version 2.0 (the "License.") diff --git a/binding.gyp b/binding.gyp index a3f89c0..f22b829 100644 --- a/binding.gyp +++ b/binding.gyp @@ -52,7 +52,7 @@ "cflags_cc" : ['-fexceptions'], "libraries" : ["-lclntsh"], "link_settings" : { - "libraries" : ['-L<(oci_lib_dir)'] + "libraries" : ['-L<(oci_lib_dir) -Wl,-rpath,<(oci_lib_dir)'] } } ], diff --git a/doc/api.md b/doc/api.md index 254e291..f906595 100644 --- a/doc/api.md +++ b/doc/api.md @@ -1,4 +1,4 @@ -# node-oracledb 1.5: Documentation for the Oracle Database Node.js Add-on +# node-oracledb 1.6: Documentation for the Oracle Database Node.js Add-on *Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.* @@ -124,6 +124,7 @@ limitations under the License. - 12.3 [DML RETURNING Bind Parameters](#dmlreturn) - 12.4 [REF CURSOR Bind Parameters](#refcursors) - 12.5 [LOB Bind Parameters](#lobbinds) + - 12.6 [PL/SQL Collection Associative Array (Index-by) Bind Parameters](#plsqlindexbybinds) 13. [Transaction Management](#transactionmgt) 14. [Statement Caching](#stmtcache) 15. [External Configuration](#oraaccess) @@ -1167,10 +1168,11 @@ If a bind value is an object it may have the following properties: Bind Property | Description ---------------|------------ -`val` | The input value or variable to be used for an IN or IN OUT bind variable. `dir` | The direction of the bind. One of the [Oracledb Constants](#oracledbconstants) `BIND_IN`, `BIND_INOUT`, or `BIND_OUT`. -`type` | The datatype to be bound. One of the [Oracledb Constants](#oracledbconstants) `STRING`, `NUMBER`, `DATE`, `CURSOR` or `BUFFER`. +`maxArraySize` | The number of array elements to be allocated for a PL/SQL Collection `INDEX OF` associative array OUT or IN OUT array bind variable. `maxSize` | The maximum number of bytes that an OUT or IN OUT bind variable of type STRING or BUFFER can use. The default value is 200. The maximum limit is 32767. +`type` | The datatype to be bound. One of the [Oracledb Constants](#oracledbconstants) `STRING`, `NUMBER`, `DATE`, `CURSOR` or `BUFFER`. +`val` | The input value or variable to be used for an IN or IN OUT bind variable. The maximum size of a `BUFFER` type is 2000 bytes, unless you are using Oracle Database 12c and the database initialization parameter @@ -2785,19 +2787,33 @@ connection.execute( var lob = result.outBinds.lobbv[0]; lob.on('error', function(err) { console.error(err); }); + lob.on('finish', + function() + { + connection.commit( + function(err) + { + if (err) + console.error(err.message); + else + console.log("Text inserted successfully."); + connection.release(function(err) { + if (err) console.error(err.message); + }); + }); + }); console.log('Reading from ' + inFileName); var inStream = fs.createReadStream(inFileName); - inStream.on('end', function() { - connection.commit( - function(err) { - if (err) - console.error(err.message); - else - console.log("Text inserted successfully."); - }); - }); - inStream.on('error', function(err) { console.error(err); }); + inStream.on('error', + function(err) + { + console.error(err); + connection.release(function(err) { + if (err) console.error(err.message); + }); + }); + inStream.pipe(lob); // copies the text to the CLOB }); ``` @@ -2835,6 +2851,9 @@ connection.execute( lob.setEncoding('utf8'); // we want text, not binary output lob.on('error', function(err) { console.error(err); }); + lob.on('close', function() { + connection.release(function(err) { if (err) console.error(err.message); }); + }); console.log('Writing to ' + outFileName); var outStream = fs.createWriteStream(outFileName); @@ -2953,7 +2972,9 @@ bind a Node.js Buffer to an Oracle Database `RAW` type. The type For each OUT and IN OUT bind parameter, a bind value object containing [`val`](#executebindParams), [`dir`](#executebindParams), [`type`](#executebindParams) and [`maxSize`](#executebindParams) -properties is used. +properties is used. For +[PL/SQL Associative Array binds](#plsqlindexbybinds) a +[`maxArraySize`](#executebindParams) property is required. The `dir` attribute should be `BIND_OUT` or `BIND_INOUT`. @@ -2988,9 +3009,6 @@ bind-by-position is done by passing an array of bind values, then the OUT and IN OUT binds are in an array with the bind positions in the same order. -With PL/SQL statements, only scalar parameters can be bound. An array -of values cannot be passed to a PL/SQL indexed table bind parameter. - Here is an example program showing the use of binds: ```javascript @@ -3235,6 +3253,145 @@ connection.execute( See [Working with CLOB and BLOB Data](#lobhandling) for more information on working with Lob streams. +### 12.6 PL/SQL Collection Associative Array (Index-by) Bind Parameters + +Arrays of strings and numbers can be bound to PL/SQL IN, IN OUT, and +OUT parameters of PL/SQL `INDEX BY` associative array type. This type +was formerly called PL/SQL tables or index-by tables. This method of +binding can be a very efficient way of transferring small data sets. +Note PL/SQL's `VARRAY` and nested table collection types cannot be +bound. + +To bind arrays use the named bind syntax: + +```javascript +connection.execute( + "BEGIN mypkg.myinproc(:bv); END;", + { + bv: { type: oracledb.NUMBER, + dir: oracledb.BIND_IN, + val: [1, 2, 23, 4, 10] + } + }, . . . +``` + +Positional bind syntax is not supported in this release. + +For OUT and IN OUT binds, the [`maxArraySize`](#executebindParams) +bind property must be set. Its value is the maximum number of +elements that can be returned in an array. An error will occur if the +PL/SQL block attempts to insert data beyond this limit. If the PL/SQL +code returns fewer items, the JavaScript array will have the actual +number of data elements and will not contain null entries. Setting +`maxArraySize` larger than needed will cause unnecessary memory +allocation. + +For IN OUT binds, `maxArraySize` can be greater than the number of +elements in the input array. This allows more values to be returned +than are passed in. + +For IN binds, `maxArraySize` is ignored, as also is `maxSize`. + +For `STRING` IN OUT or OUT binds, the string length +[`maxSize`](#executebindParams) property may be set. If it is not set +the memory allocated per string will default to 200 bytes. If the +value is not large enough to hold the longest string data item in the +collection a runtime error occurs. To avoid unnecessary memory +allocation, do not let the size be larger than needed. + +See +[plsqlarray.js](https://github.com/oracle/node-oracledb/tree/master/examples/plsqlarray.js) +for a full example. + +The following example passes an array of values to a PL/SQL procedure +which inserts them into a table. The procedure is defined as: + +```sql +CREATE TABLE mytab (numcol NUMBER); + +CREATE OR REPLACE PACKAGE mypkg IS + TYPE numtype IS TABLE OF NUMBER INDEX BY BINARY_INTEGER; + PROCEDURE myinproc(p IN numtype); +END; +/ + +CREATE OR REPLACE PACKAGE BODY mypkg IS + PROCEDURE myinproc(p IN numtype) IS + BEGIN + FORALL i IN INDICES OF p + INSERT INTO mytab (numcol) VALUES (p(i)); + END; +END; +/ +``` + +With this, the following JavaScript will result in `mytab` containing +one row per value: + +```javascript +connection.execute( + "BEGIN mypkg.myinproc(:bv); END;", + { + bv: { type: oracledb.NUMBER, + dir: oracledb.BIND_IN, + val: [1, 2, 23, 4, 10] + } + }, + function (err) { . . . }); +``` + +The next example fetches an array of values from a table: + +```sql +CREATE TABLE mytab (numcol NUMBER); +INSERT INTO mytable (numcol) VALUES (10); +INSERT INTO mytable (numcol) VALUES (25); +INSERT INTO mytable (numcol) VALUES (50); +COMMIT; + +CREATE OR REPLACE PACKAGE mypkg IS + TYPE numtype IS TABLE OF NUMBER INDEX BY BINARY_INTEGER; + PROCEDURE myoutproc(p OUT numtype); +END; +/ + +CREATE OR REPLACE PACKAGE BODY mypkg IS + PROCEDURE myoutproc(p OUT numtype) IS + BEGIN + SELECT numcol BULK COLLECT INTO p FROM mytab ORDER BY 1; + END; +END; +/ +``` + +With this table and package, the following JavaScript will print +`[ 10, 25, 50 ]`. + +```javascript +connection.execute( + "BEGIN mypkg.myoutproc(:bv); END;", + { + bv: { type: oracledb.NUMBER, + dir: oracledb.BIND_OUT, + maxArraySize: 10 // allocate memory to hold 10 numbers + } + }, + function (err, result) { + if (err) { console.error(err.message); return; } + console.log(result.outBinds.bv); + }); +``` + +If `maxArraySize` was reduced to `2`, the script would fail with: + +``` +ORA-06513: PL/SQL: index for PL/SQL table out of range for host language array +``` + +See [Oracledb Constants](#oracledbconstants) and +[execute(): Bind Parameters](#executebindParams) for more information +about binding. + ## 13. Transaction Management By default, diff --git a/examples/blobinsert1.js b/examples/blobinsert1.js index b021ddd..26bc18d 100644 --- a/examples/blobinsert1.js +++ b/examples/blobinsert1.js @@ -1,4 +1,4 @@ -/* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. */ +/* Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved. */ /****************************************************************************** * @@ -55,13 +55,19 @@ oracledb.getConnection( return; } - console.log('Reading from ' + inFileName); - var inStream = fs.createReadStream(inFileName); - inStream.on( - 'end', + var lob = result.outBinds.lobbv[0]; + lob.on( + 'error', + function(err) + { + console.log("lob.on 'error' event"); + console.error(err); + }); + lob.on( + 'finish', function() { - console.log("inStream.on 'end' event"); + console.log("lob.on 'finish' event"); connection.commit( function(err) { @@ -69,24 +75,25 @@ oracledb.getConnection( console.error(err.message); else console.log("Image uploaded successfully."); + connection.release(function(err) { + if (err) console.error(err.message); + }); }); }); + + console.log('Reading from ' + inFileName); + var inStream = fs.createReadStream(inFileName); inStream.on( 'error', function(err) { console.log("inStream.on 'error' event"); console.error(err); + connection.release(function(err) { + if (err) console.error(err.message); + }); }); - var lob = result.outBinds.lobbv[0]; - lob.on( - 'error', - function(err) - { - console.log("lob.on 'error' event"); - console.error(err); - }); inStream.pipe(lob); // copies the text to the BLOB }); }); diff --git a/examples/blobstream1.js b/examples/blobstream1.js index 19a8ebc..566841c 100644 --- a/examples/blobstream1.js +++ b/examples/blobstream1.js @@ -1,4 +1,4 @@ -/* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. */ +/* Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved. */ /****************************************************************************** * @@ -61,6 +61,15 @@ oracledb.getConnection( console.log("lob.on 'error' event"); console.error(err); }); + lob.on( + 'close', + function() + { + console.log("lob.on 'close' event"); + connection.release(function(err) { + if (err) console.error(err.message); + }); + }); console.log('Writing to ' + outFileName); var outStream = fs.createWriteStream(outFileName); diff --git a/examples/blobstream2.js b/examples/blobstream2.js index 34d9745..efc6d5d 100644 --- a/examples/blobstream2.js +++ b/examples/blobstream2.js @@ -1,4 +1,4 @@ -/* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. */ +/* Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved. */ /****************************************************************************** * @@ -90,6 +90,9 @@ oracledb.getConnection( function() { console.log("lob.on 'close' event"); + connection.release(function(err) { + if (err) console.error(err); + }); }); lob.on('error', function(err) diff --git a/examples/clobinsert1.js b/examples/clobinsert1.js index 65d1056..644945d 100644 --- a/examples/clobinsert1.js +++ b/examples/clobinsert1.js @@ -1,4 +1,4 @@ -/* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. */ +/* Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved. */ /****************************************************************************** * @@ -63,14 +63,11 @@ oracledb.getConnection( console.log("lob.on 'error' event"); console.error(err); }); - - console.log('Reading from ' + inFileName); - var inStream = fs.createReadStream(inFileName); - inStream.on( - 'end', + lob.on( + 'finish', function() { - console.log("inStream.on 'end' event"); + console.log("lob.on 'finish' event"); connection.commit( function(err) { @@ -78,15 +75,25 @@ oracledb.getConnection( console.error(err.message); else console.log("Text inserted successfully."); + connection.release(function(err) { + if (err) console.error(err.message); + }); }); }); + + console.log('Reading from ' + inFileName); + var inStream = fs.createReadStream(inFileName); inStream.on( 'error', function(err) { console.log("inStream.on 'error' event"); console.error(err); + connection.release(function(err) { + if (err) console.error(err.message); + }); }); + inStream.pipe(lob); // copies the text to the CLOB }); }); diff --git a/examples/clobinsert2.js b/examples/clobinsert2.js index e1cc252..3fd7062 100644 --- a/examples/clobinsert2.js +++ b/examples/clobinsert2.js @@ -1,4 +1,4 @@ -/* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. */ +/* Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved. */ /****************************************************************************** * @@ -41,7 +41,7 @@ oracledb.getConnection( connection.execute( "INSERT INTO mylobs (id, c) VALUES (:id, EMPTY_CLOB()) RETURNING c INTO :lobbv", - { id: 3, lobbv: {type: oracledb.CLOB, dir: oracledb.BIND_OUT} }, + { id: 2, lobbv: {type: oracledb.CLOB, dir: oracledb.BIND_OUT} }, { autoCommit: false }, // a transaction needs to span the INSERT and pipe() function(err, result) { @@ -52,14 +52,6 @@ oracledb.getConnection( } var lob = result.outBinds.lobbv[0]; - lob.on( - 'error', - function(err) - { - console.log("lob.on 'error' event"); - console.error(err); - }); - lob.on( 'finish', function() @@ -72,8 +64,21 @@ oracledb.getConnection( console.error(err.message); else console.log("Text inserted successfully."); + connection.release(function(err) { + if (err) console.error(err); + }); }); }); + lob.on( + 'error', + function(err) + { + console.log("lob.on 'error' event"); + console.error(err); + connection.release(function(err) { + if (err) console.error(err.message); + }); + }); // See Node.js Streams examples for how to use 'drain' if write() returns false lob.write('Hello, ', 'utf8', function () { console.log("lob.write callback"); }); diff --git a/examples/clobstream1.js b/examples/clobstream1.js index c8c9bc2..a739681 100644 --- a/examples/clobstream1.js +++ b/examples/clobstream1.js @@ -1,4 +1,4 @@ -/* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. */ +/* Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved. */ /****************************************************************************** * @@ -62,6 +62,15 @@ oracledb.getConnection( console.log("lob.on 'error' event"); console.error(err); }); + lob.on( + 'close', + function() + { + console.log("lob.on 'close' event"); + connection.release(function(err) { + if (err) console.error(err.message); + }); + }); console.log('Writing to ' + outFileName); var outStream = fs.createWriteStream(outFileName); diff --git a/examples/clobstream2.js b/examples/clobstream2.js index 2f5cad8..f3d126d 100644 --- a/examples/clobstream2.js +++ b/examples/clobstream2.js @@ -1,4 +1,4 @@ -/* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. */ +/* Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved. */ /****************************************************************************** * @@ -60,6 +60,10 @@ oracledb.getConnection( console.log("CLOB chunkSize is", lob.chunkSize); lob.setEncoding('utf8'); // set the encoding so we get a 'string' not a 'buffer' + // pieceSize is the number of bytes retrieved for each readable 'data' event. + // The default is lob.chunkSize. The recommendation is for it to be a multiple of chunkSize. + // lob.pieceSize = 100; // fetch smaller chunks to show repeated 'data' events + lob.on('data', function(chunk) { @@ -74,17 +78,19 @@ oracledb.getConnection( console.log("lob.on 'end' event"); console.log("clob size is " + clob.length); fs.writeFile(outFileName, clob, function(err) { - if (err) { + if (err) console.error(err); - } else { + else console.log("Completed write to " + outFileName); - } }); }); lob.on('close', function() { console.log("lob.on 'close' event"); + connection.release(function(err) { + if (err) console.error(err); + }); }); lob.on('error', function(err) diff --git a/examples/clobupdate1.js b/examples/clobupdate1.js index 4b195b8..16348d3 100644 --- a/examples/clobupdate1.js +++ b/examples/clobupdate1.js @@ -1,4 +1,4 @@ -/* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. */ +/* Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved. */ /****************************************************************************** * @@ -56,6 +56,23 @@ oracledb.getConnection( } var lob = result.outBinds.lobbv[0]; + lob.on( + 'finish', + function() + { + console.log("lob.on 'finish' event"); + connection.commit( + function(err) + { + if (err) + console.error(err.message); + else + console.log("Text inserted successfully."); + connection.release(function(err) { + if (err) console.error(err); + }); + }); + }); lob.on( 'error', function(err) @@ -71,14 +88,6 @@ oracledb.getConnection( function() { console.log("inStream.on 'end' event"); - connection.commit( - function(err) - { - if (err) - console.error(err.message); - else - console.log("Text inserted successfully."); - }); }); inStream.on( 'error', @@ -86,6 +95,9 @@ oracledb.getConnection( { console.log("inStream.on 'error' event"); console.error(err); + connection.release(function(err) { + if (err) console.error(err.message); + }); }); inStream.pipe(lob); // copies the text to the CLOB }); diff --git a/examples/demo.sql b/examples/demo.sql index 735fc55..ec6137d 100644 --- a/examples/demo.sql +++ b/examples/demo.sql @@ -1,4 +1,4 @@ -/* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. */ +/* Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved. */ /****************************************************************************** * @@ -59,6 +59,53 @@ END; / SHOW ERRORS +-- For plsqlarray.js example for PL/SQL 'INDEX BY' array binds +DROP TABLE waveheight; +CREATE TABLE waveheight (beach VARCHAR2(50), depth NUMBER); + +CREATE OR REPLACE PACKAGE beachpkg IS + TYPE beachType IS TABLE OF VARCHAR2(30) INDEX BY BINARY_INTEGER; + TYPE depthType IS TABLE OF NUMBER INDEX BY BINARY_INTEGER; + PROCEDURE array_in(beaches IN beachType, depths IN depthType); + PROCEDURE array_out(beaches OUT beachType, depths OUT depthType); + PROCEDURE array_inout(beaches IN OUT beachType, depths IN OUT depthType); +END; +/ +SHOW ERRORS + +CREATE OR REPLACE PACKAGE BODY beachpkg IS + + -- Insert array values into a table + PROCEDURE array_in(beaches IN beachType, depths IN depthType) IS + BEGIN + IF beaches.COUNT <> depths.COUNT THEN + RAISE_APPLICATION_ERROR(-20000, 'Array lengths must match for this example.'); + END IF; + FORALL i IN INDICES OF beaches + INSERT INTO waveheight (beach, depth) VALUES (beaches(i), depths(i)); + END; + + -- Return the values from a table + PROCEDURE array_out(beaches OUT beachType, depths OUT depthType) IS + BEGIN + SELECT beach, depth BULK COLLECT INTO beaches, depths FROM waveheight; + END; + + -- Return the arguments sorted + PROCEDURE array_inout(beaches IN OUT beachType, depths IN OUT depthType) IS + BEGIN + IF beaches.COUNT <> depths.COUNT THEN + RAISE_APPLICATION_ERROR(-20001, 'Array lengths must match for this example.'); + END IF; + FORALL i IN INDICES OF beaches + INSERT INTO waveheight (beach, depth) VALUES (beaches(i), depths(i)); + SELECT beach, depth BULK COLLECT INTO beaches, depths FROM waveheight ORDER BY 1; + END; + +END; +/ +SHOW ERRORS + -- For selectjson.js example of JSON datatype. Requires Oracle Database 12.1.0.2 DROP TABLE j_purchaseorder; -- Note if your applications always insert valid JSON, you may delete diff --git a/examples/demodrop.sql b/examples/demodrop.sql index 1c2f3df..cf3c11e 100644 --- a/examples/demodrop.sql +++ b/examples/demodrop.sql @@ -1,4 +1,4 @@ -/* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. */ +/* Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved. */ /****************************************************************************** * @@ -31,6 +31,8 @@ DROP FUNCTION testfunc; DROP PROCEDURE get_emp_rs; +DROP PACKAGE beachpkg; + DROP TABLE j_purchaseorder; DROP TABLE j_purchaseorder_c; @@ -44,3 +46,5 @@ DROP TYPE dorow; DROP FUNCTION mydofetch; DROP TABLE myraw; + +DROP TABLE waveheight; diff --git a/examples/selectjsonclob.js b/examples/selectjsonclob.js index 7fbf203..4fc94d6 100644 --- a/examples/selectjsonclob.js +++ b/examples/selectjsonclob.js @@ -1,4 +1,4 @@ -/* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. */ +/* Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved. */ /****************************************************************************** * @@ -61,11 +61,10 @@ oracledb.getConnection( connection, function(err, result) { - if (err) { + if (err) console.error(err.message); - } else { + else console.log('Query results: ', result); - } doRelease(connection); }); }); @@ -85,15 +84,14 @@ function doInsert(connection, data, cb) } var lob = result.outBinds.lobbv[0]; - lob.on('error', function(err) { return cb(err); }); - - var inStream = new stream.Readable(); - inStream._read = function noop() {}; - inStream.push(data); - inStream.push(null); + lob.on( + 'error', + function(err) { + return cb(err); + }); - inStream.on( - 'end', + lob.on( + 'finish', function() { connection.commit( @@ -107,7 +105,17 @@ function doInsert(connection, data, cb) } }); }); - inStream.on('error', function(err) { return cb(err); }); + + var inStream = new stream.Readable(); + inStream._read = function noop() {}; + inStream.push(data); + inStream.push(null); + + inStream.on( + 'error', + function(err) { + return cb(err); + }); inStream.pipe(lob); }); } @@ -125,9 +133,21 @@ function doQuery(connection, cb) var lob = result.rows[0][0]; // just show first record if (lob === null) { return cb(new Error('CLOB was NULL')); } lob.setEncoding('utf8'); // set the encoding so we get a 'string' not a 'buffer' - lob.on('data', function(chunk) { clob += chunk; }); - lob.on('close', function() { return cb(null, JSON.parse(clob)); }); - lob.on('error', function(err) { return cb(err); }); + lob.on('data', + function(chunk) + { + clob += chunk; + }); + lob.on('close', + function() + { + return cb(null, JSON.parse(clob)); + }); + lob.on('error', + function(err) + { + return cb(err); + }); }); } diff --git a/lib/oracledb.js b/lib/oracledb.js index b3e6ef3..d9f3229 100644 --- a/lib/oracledb.js +++ b/lib/oracledb.js @@ -1,4 +1,4 @@ -/* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. */ +/* Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved. */ /****************************************************************************** * @@ -62,14 +62,19 @@ Lob.prototype._read = function() function(err, str) { if (err) { - self.close(); + self.close(); // Ignore if any error occurs during close self.emit('error', err); + self.emit('close'); return; } self.push(str); if (!str) { process.nextTick(function() { - self.close(); + err = self.close(); + if (err) { + self.emit('error', err); + } + self.emit('close'); }); } }); @@ -84,7 +89,7 @@ Lob.prototype._write = function(data, encoding, cb) function(err) { if (err) { - self.close(); + self.close(); // Ignore if any error occurs during close return cb(err); } cb(); @@ -100,10 +105,14 @@ Lob.prototype.close = function(cb) } if (self.iLob != null) { - self.iLob.release(); + try { + self.iLob.release(); + } catch(err) { + self.iLob = null; + return err; + } self.iLob = null; } - self.emit('close'); } oracledb.Oracledb.prototype.newLob = function(iLob) diff --git a/my_test.cmd b/my_test.cmd index 489ffe7..3af6f86 100644 --- a/my_test.cmd +++ b/my_test.cmd @@ -1,2 +1,2 @@ rem test -node node_modules\mocha\bin\mocha -R spec --timeout 0 test\PLSQLbinds.js +node node_modules\mocha\bin\mocha -R spec --timeout 0 test\plsqlBinding.js diff --git a/package.json b/package.json index 8c9bd6a..5ca02d8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "oracledb", - "version": "1.5.0", + "version": "1.6.0", "description": "Oracle Database driver by Oracle Corp.", "license": "Apache-2.0", "homepage": "http://www.oracle.com/technetwork/database/database-technologies/scripting-languages/node_js/", diff --git a/src/dpi/include/dpiStmt.h b/src/dpi/include/dpiStmt.h index e74bea9..05f43db 100644 --- a/src/dpi/include/dpiStmt.h +++ b/src/dpi/include/dpiStmt.h @@ -175,16 +175,16 @@ class Stmt // methods virtual void bind(unsigned int pos, unsigned short type, void *buf, DPI_SZ_TYPE bufSize, short *ind, DPI_BUFLEN_TYPE *bufLen, - void *data, unsigned int maxarr_len, unsigned int *curelen, + void *data, cbtype cb = NULL ) = 0; virtual void bind(const unsigned char *name, int nameLen, unsigned int bndpos, unsigned short type, void *buf, DPI_SZ_TYPE bufSize, short *ind, DPI_BUFLEN_TYPE *bufLen, - void *data, unsigned int maxarr_len, unsigned int *curelen, + void *data, cbtype cb = NULL ) = 0; virtual void execute ( int numIterations, bool autoCommit = false) = 0; diff --git a/src/dpi/src/dpiStmtImpl.cpp b/src/dpi/src/dpiStmtImpl.cpp index b00e7b7..5f2a2cd 100644 --- a/src/dpi/src/dpiStmtImpl.cpp +++ b/src/dpi/src/dpiStmtImpl.cpp @@ -246,8 +246,8 @@ void StmtImpl::prefetchRows (unsigned int prefetchRows) */ void StmtImpl::bind (unsigned int pos, unsigned short type, void *buf, DPI_SZ_TYPE bufSize, short *ind, DPI_BUFLEN_TYPE *bufLen, - void *data, unsigned int maxarr_len, unsigned int *curelen, + void *data, cbtype cb) { OCIBind *b = (OCIBind *)0; @@ -288,18 +288,23 @@ void StmtImpl::bind (unsigned int pos, unsigned short type, void *buf, PARAMETERS name - name of the variable nameLen - len of name. + bndpos - position in array in case of DML Returning. type - data type buf (IN/OUT) - data buffer for value bufSize - size of buffer ind - indicator bufLen - returned buffer size + maxarr_len - max array len in case of PL/SQL array binds + curelen - current array len in case of PL/SQL array binds. + data - if callback specified, data for callback + cb - callback used in case of DML Returning. */ void StmtImpl::bind (const unsigned char *name, int nameLen, unsigned int bndpos, unsigned short type, void *buf, DPI_SZ_TYPE bufSize, short *ind, DPI_BUFLEN_TYPE *bufLen, - void *data, unsigned int maxarr_len, unsigned int *curelen, + void *data, cbtype cb) { OCIBind *b = (OCIBind *)0; diff --git a/src/dpi/src/dpiStmtImpl.h b/src/dpi/src/dpiStmtImpl.h index d3c1179..fe4cd2d 100644 --- a/src/dpi/src/dpiStmtImpl.h +++ b/src/dpi/src/dpiStmtImpl.h @@ -76,16 +76,16 @@ class StmtImpl : public Stmt virtual void bind (unsigned int pos, unsigned short type, void *buf, DPI_SZ_TYPE bufSize, short *ind, DPI_BUFLEN_TYPE *bufLen, - void *data, unsigned int maxarr_len, unsigned int *curelen, + void *data, cbtype cb); virtual void bind (const unsigned char *name, int nameLen, unsigned int bndpos, unsigned short type, void *buf, DPI_SZ_TYPE bufSize, short *ind, DPI_BUFLEN_TYPE *bufLen, - void *data, unsigned int maxarr_len, unsigned int *curelen, + void *data, cbtype cb); virtual void execute ( int numIterations, bool autoCommit ); diff --git a/src/njs/src/njsConnection.cpp b/src/njs/src/njsConnection.cpp index 1bda0dc..b357ee8 100644 --- a/src/njs/src/njsConnection.cpp +++ b/src/njs/src/njsConnection.cpp @@ -1,4 +1,5 @@ -/* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. */ +/* Copyright (c) 2015, 2016, Oracle and/or its affiliates. + All rights reserved. */ /****************************************************************************** * @@ -52,8 +53,6 @@ #include "njsResultSet.h" #include "njsIntLob.h" #include -#include -#include #include using namespace std; @@ -75,50 +74,6 @@ Nan::Persistent Connection::connectionTemplate_s; ( ( ( maxSize != 0 ) && \ ( ( ( NJS_SIZE_T_MAX ) / ( (size_t)maxSize ) ) < (maxRows) ) ) ? 1 : 0) \ -/*****************************************************************************/ -/* - DESCRIPTION - An abstract v8 value type -*/ -typedef enum { - TYPE_INVALID = -1, - TYPE_NULL = 0, - TYPE_STRING, - TYPE_INTEGER, - TYPE_UINTEGER, - TYPE_NUMBER, - TYPE_DATE -} abstractValueType; - -/*****************************************************************************/ -/* - DESCRIPTION - Get the abstract data type that can be used when binding a value. - - PARAMETERS - value - Abstract value type for all v8 JavaScript values and objects. - - RETURNS - bindDataType for the values that can be bound. -*/ -static abstractValueType getAbstractValueType(v8::Local value) -{ - if (value->IsUndefined() || value->IsNull()) { - return TYPE_NULL; - } else if (value->IsString()) { - return TYPE_STRING; - } else if (value->IsInt32()) { - return TYPE_INTEGER; - } else if (value->IsUint32()) { - return TYPE_UINTEGER; - } else if (value->IsNumber()) { - return TYPE_NUMBER; - } else if (value->IsDate()) { - return TYPE_DATE; - } else { - return TYPE_INVALID; - } -} /*****************************************************************************/ /* @@ -747,9 +702,37 @@ void Connection::GetBindUnit (Local val, Bind* bind, goto exitGetBindUnit; } - bind->maxArraySize = 0; - NJS_GET_UINT_FROM_JSON(bind->maxArraySize, executeBaton->error, bind_unit, "maxArraySize", 1, exitGetBindUnit); + NJS_GET_UINT_FROM_JSON(bind->maxArraySize, executeBaton->error, bind_unit, + "maxArraySize", 1, exitGetBindUnit); + + + Local element = bind_unit->Get( + Nan::New("val").ToLocalChecked()); + + /* + * For IN binds maxArraySize is ignored and obtained from array size + * For INOUT bind, we do need maxArraySize to be specified by application + * For OUT bind, we can NOT determine the out value as ARRAY and so + * no validation done here. + */ + if ( element->IsArray () ) + { + Localarr = Local::Cast (element); + + if ( dir == BIND_INOUT && ( arr->Length() > bind->maxArraySize ) ) + { + executeBaton->error = NJSMessages::getErrorMsg ( errInvalidArraySize ); + goto exitGetBindUnit; + } + /* For IN bind, empty array is not allowed */ + if ( ( dir == BIND_IN || dir == BIND_INOUT ) && ( arr->Length () == 0 ) ) + { + executeBaton->error = NJSMessages::getErrorMsg ( errEmptyArray ) ; + goto exitGetBindUnit; + } + } + /* REFCURSOR(s) are supported only as OUT Binds now */ if ( bind->type == DATA_CURSOR && dir != BIND_OUT ) { @@ -758,9 +741,7 @@ void Connection::GetBindUnit (Local val, Bind* bind, "type", 2 ) ; goto exitGetBindUnit; } - - Local element = bind_unit->Get(Nan::New("val").ToLocalChecked()); - + switch(dir) { case BIND_IN : @@ -788,7 +769,8 @@ void Connection::GetBindUnit (Local val, Bind* bind, if(!executeBaton->error.empty()) goto exitGetBindUnit; break; default : - executeBaton->error = NJSMessages::getErrorMsg (errInvalidBindDirection); + executeBaton->error = NJSMessages::getErrorMsg ( + errInvalidBindDirection); goto exitGetBindUnit; break; } @@ -817,128 +799,81 @@ void Connection::GetOutBindParams (unsigned short dataType, Bind* bind, { Nan::HandleScope scope; - if (bind->maxArraySize > 0) - { - // We are dealing with an PL/SQL indexed table bind variable - GetOutBindParamsArray(dataType, bind, executeBaton); - } - else + if ( bind->maxArraySize > 0 ) { - GetOutBindParamsScalar(dataType, bind, executeBaton); + if ( (dataType != DATA_STR ) && ( dataType != DATA_NUM ) ) + { + executeBaton->error = NJSMessages::getErrorMsg ( + errInvalidTypeForArrayBind ); + goto exitGetOutBindParams; + } + else + { + bind->isArray = true; + } } -} - -/*****************************************************************************/ -/* - DESCRIPTION - Processing out binds for scalar value - - PARAMETERS: - dataType - datatype of the bind - bind struct, eBaton struct -*/ -void Connection::GetOutBindParamsScalar (unsigned short dataType, Bind* bind, - eBaton *executeBaton) -{ - Nan::HandleScope scope; - + switch(dataType) { case DATA_STR : bind->type = dpi::DpiVarChar; break; + case DATA_NUM : bind->type = dpi::DpiDouble; bind->maxSize = sizeof(double); break; + case DATA_DATE : bind->type = dpi::DpiTimestampLTZ; - bind->maxSize = 0; break; + case DATA_CURSOR : bind->type = dpi::DpiRSet; - bind->maxSize = 0; break; + case DATA_BUFFER : bind->type = dpi::DpiRaw; break; + case DATA_CLOB : bind->type = dpi::DpiClob; - bind->maxSize = 0; break; + case DATA_BLOB : bind->type = dpi::DpiBlob; - bind->maxSize = 0; - break; - default : - executeBaton->error= NJSMessages::getErrorMsg(errInvalidBindDataType,2); break; - } - - executeBaton->binds.push_back(bind); -} -/*****************************************************************************/ -/* - DESCRIPTION - Processing out binds for PL/SQL indexed table value - - PARAMETERS: - dataType - datatype of the bind - bind struct, eBaton struct -*/ -void Connection::GetOutBindParamsArray(unsigned short dataType, Bind *bind, eBaton *executeBaton) -{ - Nan::HandleScope scope; - - // Mark bind as an array bind - bind->isArray = true; - - // Check for supported bind types - switch(dataType) - { - case DATA_STR : - bind->type = dpi::DpiVarChar; - break; - case DATA_NUM : - bind->type = dpi::DpiDouble; - bind->maxSize = sizeof(double); - break; - /* - case DATA_DATE : - bind->type = dpi::DpiTimestampLTZ; - bind->maxSize = 0; - break; - */ default : - executeBaton->error = NJSMessages::getErrorMsg(errInvalidBindArray, bind->key, "the \"type\" property must be specified and only STRING and NUMBER are (currently) supported"); + executeBaton->error= NJSMessages::getErrorMsg(errInvalidBindDataType,2); break; } - // Allocate for OUT Binds of PL/SQL indexed tables variables - size_t arrayElementSize = bind->maxSize; - Connection::AllocateBindArray(dataType, bind, executeBaton, &arrayElementSize); + executeBaton->binds.push_back(bind); - executeBaton->binds.push_back(bind); +exitGetOutBindParams: + ; } + /*****************************************************************************/ /* DESCRIPTION Processing in binds PARAMETERS: - Handle value, bind struct, eBaton struct + Handle value, bind struct, eBaton struct, Bind Type ( IN, INOUT, OUT) NOTE: For IN Bind only len field field is used, and for only a scalar value now, allocate for one unit. */ -void Connection::GetInBindParams(Local v8val, Bind* bind, eBaton* executeBaton, BindType type) +void Connection::GetInBindParams(Local v8val, Bind* bind, + eBaton* executeBaton, BindType type) { Nan::HandleScope scope; - if (v8val->IsArray() || (bind->isInOut && bind->maxArraySize > 0)) + if (v8val->IsArray() ) { GetInBindParamsArray(Local::Cast(v8val), bind, executeBaton, type); } @@ -954,153 +889,173 @@ void Connection::GetInBindParams(Local v8val, Bind* bind, eBaton* execute Processing in binds for scalar values PARAMETERS: - Handle value, bind struct, eBaton struct + Handle value, bind struct, eBaton struct, BindType (IN, INOUT, OUT) NOTE: For IN Bind only len field field is used, and for only a scalar value now, allocate for one unit. */ -void Connection::GetInBindParamsScalar(Local v8val, Bind* bind, eBaton* executeBaton, BindType type) +void Connection::GetInBindParamsScalar(Local v8val, Bind* bind, + eBaton* executeBaton, BindType type) { Nan::HandleScope scope; + ValueType dataType = VALUETYPE_INVALID; /* Allocate for scalar indicator & length */ bind->ind = (short *)malloc ( sizeof ( short ) ); bind->len = (DPI_BUFLEN_TYPE *)malloc ( sizeof ( DPI_BUFLEN_TYPE ) ); *(bind->ind) = 0; + + dataType = Connection::GetValueType ( v8val ); - if(v8val->IsUndefined() || v8val->IsNull()) + switch ( dataType ) { - bind->value = NULL; - *(bind->ind) = -1; - bind->type = dpi::DpiVarChar; - } - else if(v8val->IsString()) - { - if( bind->type && bind->type != DATA_STR ) - { - executeBaton->error= NJSMessages::getErrorMsg( - errBindValueAndTypeMismatch, 2); - goto exitGetInBindParams; - } - - v8::String::Utf8Value str(v8val->ToString()); + case VALUETYPE_NULL: + bind->value = NULL; + *(bind->ind) = -1; + bind->type = dpi::DpiVarChar; + break; - bind->type = dpi::DpiVarChar; - if(type == BIND_INOUT) - { - *(bind->len) = str.length(); - } - else // IN - { - bind->maxSize = *(bind->len) = str.length(); - } - DPI_SZ_TYPE size = (bind->maxSize >= *(bind->len) ) ? - bind->maxSize : *(bind->len); - if(size) + case VALUETYPE_STRING: { - bind->value = (char*)malloc((size_t)size); - if( !bind->value ) + if( bind->type && bind->type != DATA_STR ) { - executeBaton->error = NJSMessages::getErrorMsg( - errInsufficientMemory ); - return; + executeBaton->error= NJSMessages::getErrorMsg( + errBindValueAndTypeMismatch, 2); + goto exitGetInBindParamsScalar; } - if(str.length()) - memcpy(bind->value, *str, str.length()); - } - } - else if(v8val->IsInt32()) - { - if( bind->type && bind->type != DATA_NUM ) - { - executeBaton->error= NJSMessages::getErrorMsg( - errBindValueAndTypeMismatch, 2); - goto exitGetInBindParams; - } - bind->type = dpi::DpiInteger; - bind->maxSize = *(bind->len) = sizeof(int); - bind->value = (int*)malloc(*(bind->len)); - *(int*)(bind->value) = v8val->ToInt32()->Value(); - } - else if(v8val->IsUint32()) - { - if( bind->type && bind->type != DATA_NUM ) - { - executeBaton->error= NJSMessages::getErrorMsg( - errBindValueAndTypeMismatch, 2); - goto exitGetInBindParams; - } - bind->type = dpi::DpiUnsignedInteger; - bind->maxSize = *(bind->len) = sizeof(unsigned int); - bind->value = (unsigned int*)malloc(*(bind->len)); - *(unsigned int*)(bind->value) = v8val->ToUint32()->Value(); - } - else if(v8val->IsNumber()) - { - if( bind->type && bind->type != DATA_NUM ) - { - executeBaton->error= NJSMessages::getErrorMsg(errBindValueAndTypeMismatch, 2); - goto exitGetInBindParams; - } - bind->type = dpi::DpiDouble; - bind->maxSize = *(bind->len) = sizeof(double); - bind->value = (double*)malloc(*(bind->len)); - *(double*)(bind->value) = v8val->NumberValue(); - } - else if(v8val->IsDate ()) - { - if( bind->type && bind->type != DATA_DATE ) - { - executeBaton->error= NJSMessages::getErrorMsg(errBindValueAndTypeMismatch, 2); - goto exitGetInBindParams; - } - /* This has to be allocated after stmt is initialized */ - bind->dttmarr = NULL ; - bind->extvalue = (long double *) malloc (sizeof ( long double ) ); - bind->value = NULL; - bind->type = dpi::DpiTimestampLTZ; - *(bind->len) = 0; - bind->maxSize = 0; - *(reinterpret_cast(bind->extvalue)) = Connection::v8Date2OraDate(v8val); - } - else if(v8val->IsObject ()) - { - Local obj = v8val->ToObject(); - if (Buffer::HasInstance(obj)) { - size_t bufLen = Buffer::Length(obj); - bind->type = dpi::DpiRaw; + v8::String::Utf8Value str(v8val->ToString()); + + bind->type = dpi::DpiVarChar; if(type == BIND_INOUT) { - *(bind->len) = (DPI_BUFLEN_TYPE) bufLen; + *(bind->len) = str.length(); } else // IN { - bind->maxSize = (DPI_SZ_TYPE ) bufLen; - *(bind->len) = (DPI_BUFLEN_TYPE) bufLen; + bind->maxSize = *(bind->len) = str.length(); } DPI_SZ_TYPE size = (bind->maxSize >= *(bind->len) ) ? bind->maxSize : *(bind->len); if(size) { - bind->value = (char*)malloc(size); - if(bufLen) - memcpy(bind->value, Buffer::Data(obj), bufLen); + bind->value = (char*)malloc((size_t)size); + if( !bind->value ) + { + executeBaton->error = NJSMessages::getErrorMsg( + errInsufficientMemory ); + return; + } + + if(str.length()) + memcpy(bind->value, *str, str.length()); } - } else { - executeBaton->error= NJSMessages::getErrorMsg(errInvalidBindDataType,2); - goto exitGetInBindParams; } - } - else - { + break; + + case VALUETYPE_INTEGER: + if( bind->type && bind->type != DATA_NUM ) + { + executeBaton->error= NJSMessages::getErrorMsg( + errBindValueAndTypeMismatch, 2); + goto exitGetInBindParamsScalar; + } + bind->type = dpi::DpiInteger; + bind->maxSize = *(bind->len) = sizeof(int); + bind->value = (int*)malloc(*(bind->len)); + *(int*)(bind->value) = v8val->ToInt32()->Value(); + break; + + case VALUETYPE_UINTEGER: + if( bind->type && bind->type != DATA_NUM ) + { + executeBaton->error= NJSMessages::getErrorMsg( + errBindValueAndTypeMismatch, 2); + goto exitGetInBindParamsScalar; + } + bind->type = dpi::DpiUnsignedInteger; + bind->maxSize = *(bind->len) = sizeof(unsigned int); + bind->value = (unsigned int*)malloc(*(bind->len)); + *(unsigned int*)(bind->value) = v8val->ToUint32()->Value(); + break; + + case VALUETYPE_NUMBER: + if( bind->type && bind->type != DATA_NUM ) + { + executeBaton->error= NJSMessages::getErrorMsg( + errBindValueAndTypeMismatch, 2); + goto exitGetInBindParamsScalar; + } + bind->type = dpi::DpiDouble; + bind->maxSize = *(bind->len) = sizeof(double); + bind->value = (double*)malloc(*(bind->len)); + *(double*)(bind->value) = v8val->NumberValue(); + break; + + case VALUETYPE_DATE: + if( bind->type && bind->type != DATA_DATE ) + { + executeBaton->error= NJSMessages::getErrorMsg( + errBindValueAndTypeMismatch, 2); + goto exitGetInBindParamsScalar; + } + + /* This has to be allocated after stmt is initialized */ + bind->dttmarr = NULL ; + bind->extvalue = (long double *) malloc (sizeof ( long double ) ); + bind->value = NULL; + bind->type = dpi::DpiTimestampLTZ; + *(bind->len) = 0; + bind->maxSize = 0; + /* Convert v8::Date value to long double */ + Connection::v8Date2OraDate ( v8val, bind); + break; + + case VALUETYPE_OBJECT: + { + Local obj = v8val->ToObject(); + if (Buffer::HasInstance(obj)) + { + size_t bufLen = Buffer::Length(obj); + bind->type = dpi::DpiRaw; + if(type == BIND_INOUT) + { + *(bind->len) = (DPI_BUFLEN_TYPE) bufLen; + } + else // IN + { + bind->maxSize = (DPI_SZ_TYPE ) bufLen; + *(bind->len) = (DPI_BUFLEN_TYPE) bufLen; + } + DPI_SZ_TYPE size = (bind->maxSize >= *(bind->len) ) ? + bind->maxSize : *(bind->len); + if(size) + { + bind->value = (char*)malloc(size); + if(bufLen) + memcpy(bind->value, Buffer::Data(obj), bufLen); + } + } + else + { + executeBaton->error= NJSMessages::getErrorMsg( + errInvalidBindDataType,2); + goto exitGetInBindParamsScalar; + } + } + break; + + default: executeBaton->error= NJSMessages::getErrorMsg(errInvalidBindDataType,2); - goto exitGetInBindParams; + goto exitGetInBindParamsScalar; + break; } + executeBaton->binds.push_back(bind); - exitGetInBindParams: + +exitGetInBindParamsScalar: ; } @@ -1110,97 +1065,67 @@ void Connection::GetInBindParamsScalar(Local v8val, Bind* bind, eBaton* e Processing in binds for PL/SQL indexed table value PARAMETERS: - Handle value, bind struct, eBaton struct + Handle value, bind struct, eBaton struct, Bindtype (IN, INOUT, OUT). NOTE: For IN Bind only len field field is used, and for only a scalar value now, allocate for one unit. */ -void Connection::GetInBindParamsArray(Local va8vals, Bind *bind, eBaton *executeBaton, BindType type) +void Connection::GetInBindParamsArray(Local va8vals, Bind *bind, + eBaton *executeBaton, BindType type) { Nan::HandleScope scope; - - // this is the actual array element size - size_t arrayElementSize = 0; - + size_t arrayElementSize = 0; // actual array element size + size_t bufferSize = 0; + char* buffer = 0; + // - // Step 1 - Analyze the bind parameter to determine if we actually can bind the array of values + // Step 1 - Analyze the bind parameter to determine if we actually can + // bind the array of values // - // get the number of elements in the array - bind->curArraySize = va8vals->Length(); + bind->curArraySize = va8vals->Length(); // # of elements in Array // Validate the "maxArraySize" property if (!bind->isInOut) { - // for INPUT parameter - if (bind->maxArraySize != 0) - { - executeBaton->error = NJSMessages::getErrorMsg(errInvalidBindArray, bind->key, "the \"maxArraySize\" property is not allowed when specifying a BIND_IN parameter"); - goto exitGetInBindParams; - } - else - { - bind->maxArraySize = static_cast(bind->curArraySize); - } - } - else - { - // for INPUT/OUTPUT parameter - if (bind->maxArraySize == 0) - { - executeBaton->error = NJSMessages::getErrorMsg(errInvalidBindArray, bind->key, "the \"maxArraySize\" property is mandatory when specifying a BIND_INOUT parameter"); - goto exitGetInBindParams; - } - if (bind->curArraySize > bind->maxArraySize) - { - std::ostringstream s; - s << "the number of array elements \"" << bind->curArraySize << "\" is larger then the \"maxArraySize\" property \"" << bind->maxArraySize << "\""; - executeBaton->error = NJSMessages::getErrorMsg(errInvalidBindArray, bind->key, s.str().c_str()); - goto exitGetInBindParams; - } + bind->maxArraySize = static_cast(bind->curArraySize); } - // make sure that the bind type is valid - switch (bind->type) + // Currently only STRING & NUMBER are supported for Array Bind(s) + if ( (bind->type != DATA_STR) && (bind->type != DATA_NUM) ) { - case DATA_STR: - case DATA_NUM: - /* - case DATA_DATE: - */ - break; - - default: - executeBaton->error = NJSMessages::getErrorMsg(errInvalidBindArray, bind->key, "the \"type\" property must be specified and only STRING and NUMBER are (currently) supported"); - goto exitGetInBindParams; + executeBaton->error = NJSMessages::getErrorMsg( + errInvalidTypeForArrayBind); + goto exitGetInBindParamsArray; } - // Make sure that all (not NULL) elements in the array have a valid and consistent type + // Make sure that all (not NULL) elements in the array have a valid and + // consistent type for (unsigned int index = 0; index < bind->curArraySize; index++) { Local value = va8vals->Get(index); - abstractValueType type = getAbstractValueType(value); + ValueType vtype = GetValueType (value); // make sure that we generally have a valid value type - if (type == TYPE_INVALID) + if (vtype == VALUETYPE_INVALID) { - std::ostringstream s; - s << "the array element \"" << index << "\" of the \"val\" property contains has an unsupported type"; - executeBaton->error = NJSMessages::getErrorMsg(errInvalidBindArray, bind->key, s.str().c_str()); - goto exitGetInBindParams; + executeBaton->error = NJSMessages::getErrorMsg( + errInvalidTypeForArrayBind); + + goto exitGetInBindParamsArray; } - // make sure that all values in the array have the exact same type or are null + // make sure that all values in the array have the exact same type or are + // null switch (bind->type) { case DATA_STR: - if (type != TYPE_NULL && type != TYPE_STRING) + if (vtype != VALUETYPE_NULL && vtype != VALUETYPE_STRING) { - std::ostringstream s; - s << "the type of the array element \"" << index << "\" of the \"val\" property is not compatible with the \"type\" property STRING"; - executeBaton->error = NJSMessages::getErrorMsg(errInvalidBindArray, bind->key, s.str().c_str()); - goto exitGetInBindParams; + executeBaton->error = NJSMessages::getErrorMsg( + errIncompatibleTypeArrayBind); + goto exitGetInBindParamsArray; } else { @@ -1210,52 +1135,27 @@ void Connection::GetInBindParamsArray(Local va8vals, Bind *bind, eBaton * { arrayElementSize = stringLength; } - - // Check if we have a string with a size larger then the specified maxSize - // (there is actually a default for maxSize if not specified) - if (stringLength > static_cast(bind->maxSize)) - { - std::ostringstream s; - s << "the string length " << stringLength << " of the array element \"" << index << "\" of the \"val\" property is larger then the \"maxSize\" property " << bind->maxSize; - executeBaton->error = NJSMessages::getErrorMsg(errInvalidBindArray, bind->key, s.str().c_str()); - goto exitGetInBindParams; - } } break; case DATA_NUM: - if (type != TYPE_NULL && type != TYPE_INTEGER && type != TYPE_UINTEGER && type != TYPE_NUMBER) - { - std::ostringstream s; - s << "the type of the array element \"" << index << "\" of the \"val\" property is not compatible with the \"type\" property NUMBER"; - executeBaton->error = NJSMessages::getErrorMsg(errInvalidBindArray, bind->key, s.str().c_str()); - goto exitGetInBindParams; - } - break; - - /* - case DATA_DATE: - if (type != TYPE_NULL && type != TYPE_DATE) + if (vtype != VALUETYPE_NULL && vtype != VALUETYPE_INTEGER && + vtype != VALUETYPE_UINTEGER && vtype != VALUETYPE_NUMBER) { - std::ostringstream s; - s << "the type of the array element \"" << index << "\" of the \"val\" property is not compatible with the \"type\" property DATE"; - executeBaton->error = NJSMessages::getErrorMsg(errInvalidBindArray, bind->key, s.str().c_str()); - goto exitGetInBindParams; + executeBaton->error = NJSMessages::getErrorMsg( + errIncompatibleTypeArrayBind); + goto exitGetInBindParamsArray; } break; - */ } } // - // Step 2 - Allocate the needed buffers for the arrays of values and the indicators + // Step 2 - Allocate the needed buffers for the arrays of values and the + // indicators // - // Because we we must bind a single Oracle numbertype, but in JavaScript - // there might be different number types (Int, UInt and Number) we only - // bind number columns as double. - size_t bufferSize = 0; - char* buffer = 0; + switch (bind->type) { case DATA_STR: @@ -1264,13 +1164,12 @@ void Connection::GetInBindParamsArray(Local va8vals, Bind *bind, eBaton * // If we are dealing with an OUT binding if (bind->isOut) { - // If we are dealing with an OUT binding, it is not allowed to have am actual element largen than the maxSize argument + // If we are dealing with an OUT binding, it is not allowed to have + // am actual element largen than the maxSize argument if (arrayElementSize > static_cast(bind->maxSize)) { - std::ostringstream s; - s << "no array element size \"" << arrayElementSize << "\" is allowed to be larger then the given \"maxSize\" \"" << bind->maxSize << "\" property"; - executeBaton->error = NJSMessages::getErrorMsg(errInvalidBindArray, bind->key, s.str().c_str()); - goto exitGetInBindParams; + executeBaton->error = NJSMessages::getErrorMsg(errInvalidArraySize); + goto exitGetInBindParamsArray; } else { @@ -1278,9 +1177,13 @@ void Connection::GetInBindParamsArray(Local va8vals, Bind *bind, eBaton * } } - // Because we also need to store the 0 at the end of the string, we need to increase the array element size by 1 - arrayElementSize = arrayElementSize + 1; - bufferSize = static_cast(arrayElementSize * bind->maxArraySize); + if ( NJS_SIZE_T_OVERFLOW (arrayElementSize, bind->maxArraySize ) ) + { + executeBaton->error = NJSMessages::getErrorMsg ( errResultsTooLarge ); + goto exitGetInBindParamsArray; + } + bufferSize = static_cast(arrayElementSize * + bind->maxArraySize); buffer = reinterpret_cast(malloc(bufferSize)); bind->value = buffer; break; @@ -1288,29 +1191,21 @@ void Connection::GetInBindParamsArray(Local va8vals, Bind *bind, eBaton * case DATA_NUM: bind->type = dpi::DpiDouble; arrayElementSize = sizeof(double); - bufferSize = static_cast(arrayElementSize * bind->maxArraySize); + if ( NJS_SIZE_T_OVERFLOW (arrayElementSize, bind->maxArraySize ) ) + { + executeBaton->error = NJSMessages::getErrorMsg ( errResultsTooLarge ); + goto exitGetInBindParamsArray; + } + bufferSize = static_cast(arrayElementSize * + bind->maxArraySize); buffer = reinterpret_cast(malloc(bufferSize)); bind->value = buffer; break; - /* - case DATA_DATE: - bind->type = dpi::DpiTimestampLTZ; - arrayElementSize = sizeof(long double); - bufferSize = static_cast(arrayElementSize * bind->maxArraySize); - buffer = reinterpret_cast(malloc(bufferSize)); - bind->value = 0; - bind->extvalue = buffer; - bind->dttmarr = NULL; - break; - */ - default: - { - std::ostringstream s; - s << "Internal Error: invalid case for value \"" << bind->type << "\""; - Nan::ThrowError(s.str().c_str()); - } + executeBaton->error = NJSMessages::getErrorMsg ( + errInvalidTypeForArrayBind ); + goto exitGetInBindParamsArray; break; } @@ -1318,40 +1213,58 @@ void Connection::GetInBindParamsArray(Local va8vals, Bind *bind, eBaton * if (!buffer) { executeBaton->error = NJSMessages::getErrorMsg(errInsufficientMemory); - goto exitGetInBindParams; + goto exitGetInBindParamsArray; } memset(buffer, 0, bufferSize); // Allocate indicator and len arrays - bind->ind = reinterpret_cast(malloc(sizeof(short) * bind->maxArraySize)); - bind->len = reinterpret_cast(malloc(sizeof(DPI_BUFLEN_TYPE) * bind->maxArraySize)); - bind->len2 = reinterpret_cast(malloc(sizeof(unsigned int) * bind->maxArraySize)); - if (!bind->ind || !bind->len || !bind->len2) + if ( NJS_SIZE_T_OVERFLOW ( sizeof (short), bind->maxArraySize )) + { + executeBaton->error = NJSMessages::getErrorMsg ( errResultsTooLarge ); + goto exitGetInBindParamsArray; + } + bind->ind = reinterpret_cast(malloc( + sizeof(short) * bind->maxArraySize)); + + if ( NJS_SIZE_T_OVERFLOW ( sizeof ( DPI_BUFLEN_TYPE ), bind->maxArraySize ) ) + { + executeBaton->error = NJSMessages::getErrorMsg ( errResultsTooLarge ); + goto exitGetInBindParamsArray; + } + bind->len = reinterpret_cast( + malloc( sizeof(DPI_BUFLEN_TYPE) * bind->maxArraySize)); + if ( NJS_SIZE_T_OVERFLOW ( sizeof ( unsigned int ), bind->maxArraySize ) ) + { + executeBaton->error = NJSMessages::getErrorMsg ( errResultsTooLarge ); + goto exitGetInBindParamsArray; + } + + if (!bind->ind || !bind->len) { executeBaton->error = NJSMessages::getErrorMsg(errInsufficientMemory); - goto exitGetInBindParams; + goto exitGetInBindParamsArray; } // - // Step 3 - Convert and copy the values from the JavaScript values to the OCI buffers + // Step 3 - Convert and copy the values from the JavaScript values to the + // OCI buffers // - // Because maxSize will not be increased here but rather only ion the Connection::PrepareAndBind method, - // we must also increase the buffer pointer by 1 here. - for (unsigned int index = 0; index < bind->curArraySize; index++, buffer += arrayElementSize) + for (unsigned int index = 0; + index < bind->curArraySize; + index++, buffer += arrayElementSize) { Local value = va8vals->Get(index); - abstractValueType type = getAbstractValueType(value); + ValueType type = GetValueType(value); switch (type) { - case TYPE_NULL: + case VALUETYPE_NULL: bind->ind[index] = -1; bind->len[index] = 0; - bind->len2[index] = 0; break; - case TYPE_STRING: + case VALUETYPE_STRING: { v8::String::Utf8Value str(value->ToString()); size_t stringLength = str.length(); @@ -1360,35 +1273,22 @@ void Connection::GetInBindParamsArray(Local va8vals, Bind *bind, eBaton * memcpy(buffer, *str, stringLength); } bind->ind[index] = 0; - bind->len[index] = 0; - bind->len2[index] = static_cast(stringLength); + bind->len[index] = static_cast(stringLength); } break; - case TYPE_INTEGER: - case TYPE_UINTEGER: - case TYPE_NUMBER: + case VALUETYPE_INTEGER: + case VALUETYPE_UINTEGER: + case VALUETYPE_NUMBER: *(reinterpret_cast(buffer)) = value->NumberValue(); bind->ind[index] = 0; - bind->len[index] = 0; - bind->len2[index] = sizeof(double); - break; - - /* - case TYPE_DATE: - *(reinterpret_cast(buffer)) = Connection::v8Date2OraDate(value); - bind->ind[index] = 0; - bind->len[index] = 0; - bind->len2[index] = sizeof(long double); + bind->len[index] = sizeof ( double ) ; break; - */ default: - { - std::ostringstream s; - s << "Internal Error: invalid case for value \"" << type << "\""; - Nan::ThrowError(s.str().c_str()); - } + executeBaton->error = NJSMessages::getErrorMsg ( + errInvalidTypeForArrayBind ) ; + goto exitGetInBindParamsArray; break; } } @@ -1402,7 +1302,7 @@ void Connection::GetInBindParamsArray(Local va8vals, Bind *bind, eBaton * executeBaton->binds.push_back(bind); - exitGetInBindParams: +exitGetInBindParamsArray: ; } @@ -1416,93 +1316,102 @@ void Connection::GetInBindParamsArray(Local va8vals, Bind *bind, eBaton * NOTE: */ -bool Connection::AllocateBindArray(unsigned short dataType, Bind* bind, eBaton *executeBaton, size_t *arrayElementSize) +bool Connection::AllocateBindArray(unsigned short dataType, Bind* bind, + eBaton *executeBaton, + size_t *arrayElementSize) { - Nan::HandleScope scope; - - size_t bufferSize = 0; - char* buffer = 0; + size_t bufferSize = 0; + char* buffer = 0; + bool ret = false; - // Because we we must bind a single Oracle numbertype, but in JavaScript - // there might be different number types (Int, UInt and Number) we only - // bind number columns as double. switch (dataType) { - case DATA_STR: - bind->type = dpi::DpiVarChar; - - // If we are dealing with an OUT binding - if (bind->isOut) - { - // If we are dealing with an OUT binding, it is not allowed to have an actual element largen than the maxSize argument - if (*arrayElementSize > static_cast(bind->maxSize)) - { - std::ostringstream s; - s << "no array element size \"" << *arrayElementSize << "\" is allowed to be larger then the given \"maxSize\" \"" << bind->maxSize << "\" property"; - executeBaton->error = NJSMessages::getErrorMsg(errInvalidBindArray, bind->key, s.str().c_str()); - return false; - } - else - { - *arrayElementSize = static_cast(bind->maxSize); - } - } - - // Because we also need to store the 0 at the end of the string, we need to increase the array element size by 1 - *arrayElementSize = *arrayElementSize + 1; - bufferSize = static_cast(*arrayElementSize * bind->maxArraySize); - buffer = reinterpret_cast(malloc(bufferSize)); - bind->value = buffer; - break; - - case DATA_NUM: - bind->type = dpi::DpiDouble; - *arrayElementSize = sizeof(double); - bufferSize = static_cast(*arrayElementSize * bind->maxArraySize); - buffer = reinterpret_cast(malloc(bufferSize)); - bind->value = buffer; - break; + case dpi::DpiVarChar: + // If we are dealing with an OUT binding, it is not allowed to have + // an actual element largen than the maxSize argument + if (*arrayElementSize > static_cast(bind->maxSize)) + { + executeBaton->error = NJSMessages::getErrorMsg(errInvalidArraySize); + goto exitAllocateBindArray; + } + else + { + *arrayElementSize = static_cast(bind->maxSize); + } + + if ( NJS_SIZE_T_OVERFLOW (*arrayElementSize, bind->maxArraySize ) ) + { + executeBaton->error = NJSMessages::getErrorMsg ( errResultsTooLarge ); + goto exitAllocateBindArray; + } + bufferSize = static_cast(*arrayElementSize * + bind->maxArraySize); + buffer = reinterpret_cast(malloc(bufferSize)); + bind->value = buffer; + ret = true; + break; - /* - case DATA_DATE: - bind->type = dpi::DpiTimestampLTZ; - *arrayElementSize = sizeof(long double); - bufferSize = static_cast(*arrayElementSize * bind->maxArraySize); - buffer = reinterpret_cast(malloc(bufferSize)); - bind->value = 0; - bind->extvalue = buffer; - bind->dttmarr = NULL; - break; - */ + case dpi::DpiDouble: + bind->type = dpi::DpiDouble; + *arrayElementSize = sizeof(double); + if ( NJS_SIZE_T_OVERFLOW (*arrayElementSize, bind->maxArraySize ) ) + { + executeBaton->error = NJSMessages::getErrorMsg ( errResultsTooLarge ); + goto exitAllocateBindArray; + } + bufferSize = static_cast(*arrayElementSize * + bind->maxArraySize); + buffer = reinterpret_cast(malloc(bufferSize)); + bind->value = buffer; + ret = true; + break; - default: - { - std::ostringstream s; - s << "Internal Error: invalid case for value \"" << dataType << "\""; - Nan::ThrowError(s.str().c_str()); - } - break; + default: + executeBaton->error = NJSMessages::getErrorMsg ( + errInvalidTypeForArrayBind ) ; + goto exitAllocateBindArray; + break; } - // Initialize buffer - if (!buffer) + if ( ret ) { - executeBaton->error = NJSMessages::getErrorMsg(errInsufficientMemory); - return false; + // Initialize buffer + if (!buffer) + { + executeBaton->error = NJSMessages::getErrorMsg(errInsufficientMemory); + ret = false; + } } - memset(buffer, 0, bufferSize); - - // Allocate indicator and len arrays - bind->ind = reinterpret_cast(malloc(sizeof(short) * bind->maxArraySize)); - bind->len = reinterpret_cast(malloc(sizeof(DPI_BUFLEN_TYPE) * bind->maxArraySize)); - bind->len2 = reinterpret_cast(malloc(sizeof(unsigned int) * bind->maxArraySize)); - if (!bind->ind || !bind->len || !bind->len2) + if ( ret ) { - executeBaton->error = NJSMessages::getErrorMsg(errInsufficientMemory); - return false; + memset(buffer, 0, bufferSize); + + // Allocate indicator and len arrays + if ( NJS_SIZE_T_OVERFLOW ( sizeof(short), bind->maxArraySize ) ) + { + executeBaton->error = NJSMessages::getErrorMsg ( errResultsTooLarge ); + goto exitAllocateBindArray; + } + bind->ind = reinterpret_cast(malloc( + sizeof(short) * bind->maxArraySize)); + if ( NJS_SIZE_T_OVERFLOW ( sizeof ( DPI_BUFLEN_TYPE ), + bind->maxArraySize ) ) + { + executeBaton->error = NJSMessages::getErrorMsg ( errResultsTooLarge ); + goto exitAllocateBindArray; + } + bind->len = reinterpret_cast( + malloc( sizeof(DPI_BUFLEN_TYPE) * bind->maxArraySize ) ); + + if (!bind->ind || !bind->len) + { + executeBaton->error = NJSMessages::getErrorMsg(errInsufficientMemory); + ret = false; + } } - return true; +exitAllocateBindArray: + return ret; } /*****************************************************************************/ @@ -1785,12 +1694,14 @@ void Connection::PrepareAndBind (eBaton* executeBaton) (executeBaton->binds[index]->maxSize + 1) : executeBaton->binds[index]->maxSize, executeBaton->binds[index]->ind, - (executeBaton->binds[index]->isArray) ? executeBaton->binds[index]->len2 : executeBaton->binds[index]->len, + executeBaton->binds[index]->len, + (executeBaton->binds[index]->isArray) ? + executeBaton->binds[index]->maxArraySize : 0, + (executeBaton->binds[index]->isArray) ? + &(executeBaton->binds[index]->curArraySize) : 0, (executeBaton->stmtIsReturning && executeBaton->binds[index]->isOut) ? (void *)executeBaton : NULL, - (executeBaton->binds[index]->isArray) ? executeBaton->binds[index]->maxArraySize : 0, - (executeBaton->binds[index]->isArray) ? &(executeBaton->binds[index]->curArraySize) : 0, (executeBaton->stmtIsReturning && executeBaton->binds[index]->isOut) ? Connection::cbDynBufferGet : NULL); @@ -1832,12 +1743,14 @@ void Connection::PrepareAndBind (eBaton* executeBaton) (executeBaton->binds[index]->maxSize + 1) : executeBaton->binds[index]->maxSize, executeBaton->binds[index]->ind, - (executeBaton->binds[index]->isArray) ? executeBaton->binds[index]->len2 : executeBaton->binds[index]->len, + executeBaton->binds[index]->len, + (executeBaton->binds[index]->isArray) ? + executeBaton->binds[index]->maxArraySize : 0, + (executeBaton->binds[index]->isArray) ? + &(executeBaton->binds[index]->curArraySize) : 0, (executeBaton->stmtIsReturning && executeBaton->binds[index]->isOut ) ? (void *)executeBaton : NULL, - (executeBaton->binds[index]->isArray) ? executeBaton->binds[index]->maxArraySize : 0, - (executeBaton->binds[index]->isArray) ? &(executeBaton->binds[index]->curArraySize) : 0, (executeBaton->stmtIsReturning && executeBaton->binds[index]->isOut) ? Connection::cbDynBufferGet : NULL); @@ -2040,7 +1953,7 @@ boolean Connection::MapByType ( eBaton *executeBaton, unsigned short &dbType ) RETURNS dbType - As default is provided, always a DB Column type will - be returned. + be returned. */ unsigned short Connection::GetTargetType ( eBaton *executeBaton, std::string &name, @@ -2745,7 +2658,8 @@ Local Connection::GetValue ( eBaton *executeBaton, define->fetchType, (define->fetchType == DpiTimestampLTZ ) ? (void *) &dblArr[row] : - (void *) ((char *)(define->buf) + ( row * (define->maxSize ))), + (void *) ((char *)(define->buf) + + ( row * (define->maxSize ))), define->len[row] ); return scope.Escape( value ); } @@ -2753,14 +2667,24 @@ Local Connection::GetValue ( eBaton *executeBaton, { // DML, PL/SQL execution Bind *bind = executeBaton->binds[col]; + if(executeBaton->stmtIsReturning) { + // SQL statement with RETURNING INTO clause, will return an array Local value = Connection::GetArrayValue ( executeBaton, executeBaton->binds[col], (unsigned long)executeBaton->rowsAffected ); return scope.Escape(value); } + else if ( bind->isArray ) + { + // PL/SQL array bind + Local value = Connection::GetArrayValue(executeBaton, + bind, + static_cast(bind->curArraySize)); + return scope.Escape(value); + } else if(bind->type == DpiRSet) { return scope.Escape ( Connection::GetValueRefCursor ( @@ -2775,21 +2699,13 @@ Local Connection::GetValue ( eBaton *executeBaton, } else { - if (!bind->isArray) - { - return scope.Escape ( Connection::GetValueCommon ( + return scope.Escape ( Connection::GetValueCommon ( executeBaton, bind->ind[row], bind->type, (bind->type == DpiTimestampLTZ ) ? bind->extvalue : bind->value, bind->len[row] )); - } - else - { - Local value = Connection::GetArrayValue(executeBaton, bind, static_cast(bind->curArraySize)); - return scope.Escape(value); - } } } } @@ -2910,7 +2826,9 @@ Local Connection::GetValueCommon ( eBaton *executeBaton, value = date; break; case (dpi::DpiRaw) : - // TODO: We could use NewBuffer to save memory and CPU, but it gets the ownership of buffer to itself (behaviour changed in Nan 2.0) + // TODO: We could use NewBuffer to save memory and CPU, but it + // gets the ownership of buffer to itself (behaviour changed in + // Nan 2.0) value = Nan::CopyBuffer((char*)val, len).ToLocalChecked(); break; // The LOB types are hit only by the define code path @@ -2943,6 +2861,7 @@ Local Connection::GetValueCommon ( eBaton *executeBaton, To get an array as v8-Value from Bind structure - used in DML Returning PARAMETERS + eBaton - executeBaton structure bind - bind structure count - row count @@ -2950,7 +2869,8 @@ Local Connection::GetValueCommon ( eBaton *executeBaton, v8::Value - this will be an array (even for 1 row, array or 1). */ v8::Local Connection::GetArrayValue ( eBaton *executeBaton, - Bind *binds, unsigned long count ) + Bind *binds, + unsigned long count ) { Nan::EscapableHandleScope scope; Local date; @@ -2985,7 +2905,9 @@ v8::Local Connection::GetArrayValue ( eBaton *executeBaton, Nan::Set(arrVal, index, Nan::New ((char *)binds->value + (index * binds->maxSize ), - binds->len2[index]).ToLocalChecked()); + executeBaton->stmtIsReturning ? + binds->len2[index] : + binds->len[index] ).ToLocalChecked()); break; case dpi::DpiInteger: Nan::Set(arrVal, index, @@ -3597,22 +3519,22 @@ void Connection::Async_AfterBreak (uv_work_t *req) * * PARAMETERS * val - expected to be a v8::Date Value + * bind - bind structure to update * - * RETURNS - * date as a long double with seconds since 1970-1-1 0:0:0 * * NOTE: * This function is used for IN Bind parameters, when v8::Date value is * passed, conversion to Oracle-DB Type happens here. * */ -long double Connection::v8Date2OraDate(v8::Local val) +void Connection::v8Date2OraDate(v8::Local val, Bind *bind) { Nan::HandleScope scope; Local date = val.As(); // Expects to be of v8::Date type // Get the number of seconds from 1970-1-1 0:0:0 - return static_cast(date->NumberValue ()); + *(long double *)(bind->extvalue) = date->NumberValue (); + } /***************************************************************************/ @@ -3625,7 +3547,6 @@ long double Connection::v8Date2OraDate(v8::Local val) * PARAMETERS * ebaton - execute Baton * index - position in the binds array - * arraySize - the number of array entries when an array is bound * * NOTE: * When execution process starts, base date is not initialized yet, @@ -3640,17 +3561,13 @@ void Connection::UpdateDateValue ( eBaton * ebaton, unsigned int index ) if (bind->type == dpi::DpiTimestampLTZ) { - const int arraySize = bind->isArray ? bind->curArraySize : 1; - - bind->dttmarr = ebaton->dpienv->getDateTimeArray(ebaton->dpistmt->getError()); - bind->value = bind->dttmarr->init(arraySize); + bind->dttmarr = ebaton->dpienv->getDateTimeArray( + ebaton->dpistmt->getError()); + bind->value = bind->dttmarr->init(1); if (!bind->isOut) { - long double* buffer = reinterpret_cast(bind->extvalue); - for (int i = 0; i < arraySize; i++, buffer++) - { - bind->dttmarr->setDateTime(i, *buffer); - } + bind->dttmarr->setDateTime( 0, + (*(long double *)bind->extvalue)); } } } @@ -3683,6 +3600,16 @@ void Connection::cbDynBufferAllocate ( void *ctx, bool dmlReturning, eBaton *executeBaton = (eBaton *)ctx; Bind *bind = (Bind *)executeBaton->binds[bndpos]; + if ( bind->isArray ) + { + size_t arrayElementSize = bind->maxSize; + + Connection::AllocateBindArray ( bind->type, bind, executeBaton, + &arrayElementSize ); + return; + } + + if ( NJS_SIZE_T_OVERFLOW ( sizeof ( short ), nRows ) ) { executeBaton->error = NJSMessages::getErrorMsg( errResultsTooLarge ); diff --git a/src/njs/src/njsConnection.h b/src/njs/src/njsConnection.h index 924656e..0ef1428 100644 --- a/src/njs/src/njsConnection.h +++ b/src/njs/src/njsConnection.h @@ -1,4 +1,5 @@ -/* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. */ +/* Copyright (c) 2015, 2016, Oracle and/or its affiliates. + All rights reserved. */ /****************************************************************************** * @@ -65,6 +66,7 @@ using namespace dpi; class Connection; class ProtoILob; + /** * Structure used for binds **/ @@ -360,10 +362,6 @@ class Connection: public Nan::ObjectWrap static void GetOutBindParams (unsigned short dataType, Bind* bind, eBaton* executeBaton); - static void GetOutBindParamsScalar (unsigned short dataType, Bind* bind, - eBaton* executeBaton); - static void GetOutBindParamsArray (unsigned short dataType, Bind* bind, - eBaton* executeBaton); static void Descr2Double ( Define* defines, unsigned int numCols, unsigned int rowsFetched, bool getRS ); static void Descr2protoILob ( eBaton *executeBaton, unsigned int numCols, @@ -389,9 +387,8 @@ class Connection: public Nan::ObjectWrap // for lobs static v8::Local GetValueLob (eBaton *executeBaton, Bind *bind); - //static void UpdateDateValue ( eBaton *executeBaton ); static void UpdateDateValue ( eBaton *executeBaton, unsigned int index ); - static long double v8Date2OraDate(v8::Local val); + static void v8Date2OraDate(v8::Local val, Bind *bind); static ConnectionBusyStatus getConnectionBusyStatus ( Connection *conn ); // Callback/Utility function used to allocate buffer(s) for Bind Structs @@ -419,6 +416,43 @@ class Connection: public Nan::ObjectWrap static v8::Local NewLob(eBaton* executeBaton, ProtoILob *protoILob); + static inline ValueType GetValueType ( v8::Local v ) + { + ValueType type = VALUETYPE_INVALID; + + if ( v->IsUndefined () || v->IsNull () ) + { + type = VALUETYPE_NULL; + } + else if ( v->IsString () ) + { + type = VALUETYPE_STRING; + } + else if ( v->IsInt32 () ) + { + type = VALUETYPE_INTEGER; + } + else if ( v->IsUint32 () ) + { + type = VALUETYPE_UINTEGER; + } + else if ( v->IsNumber () ) + { + type = VALUETYPE_NUMBER; + } + else if ( v->IsDate () ) + { + type = VALUETYPE_DATE; + } + else if ( v->IsObject () ) + { + type = VALUETYPE_OBJECT; + } + + return type; + } + + dpi::Conn* dpiconn_; bool isValid_; unsigned int oracleServerVersion_; diff --git a/src/njs/src/njsMessages.cpp b/src/njs/src/njsMessages.cpp index bf24ac6..5c5fcc2 100644 --- a/src/njs/src/njsMessages.cpp +++ b/src/njs/src/njsMessages.cpp @@ -1,4 +1,5 @@ -/* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. */ +/* Copyright (c) 2015, 2016, Oracle and/or its affiliates. + All rights reserved. */ /****************************************************************************** * @@ -63,14 +64,19 @@ static const char *errMsg[] = "NJS-027: unexpected SQL parsing error", "NJS-028: raw database type is not supported with DML Returning statements", "NJS-029: Invalid object from javascript", - "NJS-030: Connection cannot be released because Lob operations are in" + "NJS-030: connection cannot be released because Lob operations are in" " progress", - "NJS-031: Connection cannot be released because ResultSet operations are" + "NJS-031: connection cannot be released because ResultSet operations are" " in progress", - "NJS-032: Connection cannot be released because a database call is in" + "NJS-032: connection cannot be released because a database call is in" " progress", - "NJS-033: An internal error occurred. [%s][%s]", - "NJS-034: invalid (array) binding of parameter \"%s\": %s" + "NJS-033: an internal error occurred. [%s][%s]", + "NJS-034: data type is unsupported for array bind", + "NJS-035: maxArraySize is required for IN OUT array bind", + "NJS-036: given array is of size greater than maxArraySize", + "NJS-037: incompatible type of value provided", + "NJS-038: maxArraySize value should be greater than 0", + "NJS-039: empty array is not allowed for IN bind", }; string NJSMessages::getErrorMsg ( NJSErrorType err, ... ) diff --git a/src/njs/src/njsMessages.h b/src/njs/src/njsMessages.h index 01154aa..442f0d5 100644 --- a/src/njs/src/njsMessages.h +++ b/src/njs/src/njsMessages.h @@ -1,4 +1,5 @@ -/* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. */ +/* Copyright (c) 2015, 2016, Oracle and/or its affiliates. + All rights reserved. */ /****************************************************************************** * @@ -66,7 +67,13 @@ typedef enum errBusyConnRS, errBusyConnDB, errInternalError, - errInvalidBindArray, + errInvalidTypeForArrayBind, + errReqdMaxArraySize, + errInvalidArraySize, + errIncompatibleTypeArrayBind, + errInvalidValueArrayBind, + errEmptyArray, + // New ones should be added here diff --git a/src/njs/src/njsOracle.h b/src/njs/src/njsOracle.h index 3f61532..cf41b42 100644 --- a/src/njs/src/njsOracle.h +++ b/src/njs/src/njsOracle.h @@ -69,7 +69,7 @@ using namespace v8; /* Keep the version in sync with package.json */ #define NJS_NODE_ORACLEDB_MAJOR 1 -#define NJS_NODE_ORACLEDB_MINOR 5 +#define NJS_NODE_ORACLEDB_MINOR 6 #define NJS_NODE_ORACLEDB_PATCH 0 /* Used for Oracledb.version */ diff --git a/src/njs/src/njsUtils.h b/src/njs/src/njsUtils.h index 6b9017f..8af5786 100644 --- a/src/njs/src/njsUtils.h +++ b/src/njs/src/njsUtils.h @@ -1,4 +1,5 @@ -/* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. */ +/* Copyright (c) 2015, 2016 Oracle and/or its affiliates. + All rights reserved. */ /****************************************************************************** * @@ -115,6 +116,22 @@ typedef enum CONN_BUSY_DB = 5003, // Connection busy with DB operation }ConnectionBusyStatus; +/* + * v8 Value Type + */ +typedef enum +{ + VALUETYPE_INVALID = -1, /* Types not supported now */ + VALUETYPE_NULL = 0, /* Null or Undefined */ + VALUETYPE_STRING, /* string */ + VALUETYPE_INTEGER, /* Integer */ + VALUETYPE_UINTEGER, /* Unsigned Integer */ + VALUETYPE_NUMBER, /* Number */ + VALUETYPE_DATE, /* Date */ + VALUETYPE_OBJECT, /* JSON object type */ +} ValueType ; + + /* * This class used to increment LOB, ResultSet and connection operation * counts before each operation and decrements after finishing operation diff --git a/test/clobPlsqlString.js b/test/clobPlsqlString.js index 25aee93..054da35 100644 --- a/test/clobPlsqlString.js +++ b/test/clobPlsqlString.js @@ -26,6 +26,9 @@ * PL/SQL OUT CLOB parameters can also be bound as `STRING` * The returned length is limited to the maximum size of maxSize option. * + * When the types of bind out variables are not STRING or BUFFER, + * maxSize option will not take effect. + * * NUMBERING RULE * Test numbers follow this numbering rule: * 1 - 20 are reserved for basic functional tests @@ -38,6 +41,7 @@ var oracledb = require('oracledb'); var async = require('async'); var should = require('should'); +var stream = require('stream'); var dbConfig = require('./dbConfig.js'); var assist = require('./dataTypeAssist.js'); @@ -51,35 +55,25 @@ describe('60. clobPlsqlString.js', function() { var connection = null; var tableName = "oracledb_myclobs"; - + before('get one connection, prepare table', function(done) { async.series([ function(callback) { oracledb.getConnection( - credential, + credential, function(err, conn) { - should.not.exist(err); - connection = conn; - callback(); + should.not.exist(err); + connection = conn; + callback(); } ); }, function(callback) { assist.createTable(connection, tableName, callback); - }, - function(callback) { - connection.execute( - "INSERT INTO oracledb_myclobs (num, content) VALUES (1, 'abcdefghijklmnopqrstuvwxyz')", - function(err) { - should.not.exist(err); - callback(); - } - ); } ], done); + }) // before - }) - after('release connection', function(done) { async.series([ function(callback) { @@ -98,37 +92,122 @@ describe('60. clobPlsqlString.js', function() { }); } ], done); + }) // after - }) + describe('60.1 BIND OUT as STRING', function() { + before('insert data', function(done) { + connection.execute( + "INSERT INTO oracledb_myclobs (num, content) VALUES (1, 'abcdefghijklmnopqrstuvwxyz')", + function(err) { + should.not.exist(err); + done(); + } + ); + }) // before + + it('60.1.1 PL/SQL OUT CLOB parameters can also be bound as STRING', function(done) { + connection.execute( + "BEGIN SELECT content INTO :cbv FROM oracledb_myclobs WHERE num = :id; END;", + { + id: 1, + cbv: { type: oracledb.STRING, dir: oracledb.BIND_OUT} + }, + function(err, result) { + should.not.exist(err); + (result.outBinds.cbv).should.be.a.String; + (result.outBinds.cbv).should.eql('abcdefghijklmnopqrstuvwxyz'); + done(); + } + ); + }) // 60.1.1 - it('60.1 PL/SQL OUT CLOB parameters can also be bound as STRING', function(done) { - connection.execute( - "BEGIN SELECT content INTO :cbv FROM oracledb_myclobs WHERE num = :id; END;", - { - id: 1, - cbv: { type: oracledb.STRING, dir: oracledb.BIND_OUT} - }, - function(err, result) { - should.not.exist(err); - (result.outBinds.cbv).should.be.a.String; - (result.outBinds.cbv).should.eql('abcdefghijklmnopqrstuvwxyz'); - done(); - } - ); - }) - - it('60.2 The returned length is limited to the maximum size', function(done) { - connection.execute( - "BEGIN SELECT content INTO :cbv FROM oracledb_myclobs WHERE num = :id; END;", - { - id: 1, - cbv: { type: oracledb.STRING, dir: oracledb.BIND_OUT, maxSize: 5 } - }, - function(err, result) { - should.exist(err); - (err.message).should.startWith('ORA-06502'); // PL/SQL: numeric or value error - done(); - } - ); - }) + it('60.1.2 The returned length is limited to the maximum size', function(done) { + connection.execute( + "BEGIN SELECT content INTO :cbv FROM oracledb_myclobs WHERE num = :id; END;", + { + id: 1, + cbv: { type: oracledb.STRING, dir: oracledb.BIND_OUT, maxSize: 5 } + }, + function(err, result) { + should.exist(err); + (err.message).should.startWith('ORA-06502'); // PL/SQL: numeric or value error + done(); + } + ); + }) // 60.1.2 + }) // 60.1 + + describe('60.2 BIND OUT as CLOB', function() { + var dataLength = 1000000; + var rawData = assist.createCharString(dataLength); + + it('60.2.1 maxSize option does not take effect when bind out type is clob', function(done) { + async.series([ + function doInsert(callback) { + connection.execute( + "INSERT INTO " + tableName + " VALUES (2, EMPTY_CLOB()) RETURNING content INTO :lobbv", + { lobbv: {type: oracledb.CLOB, dir: oracledb.BIND_OUT} }, + { autoCommit: false }, + function(err, result) { + should.not.exist(err); + + var lob = result.outBinds.lobbv[0]; + lob.on('error', function(err) { + should.not.exist(err); + return callback(err); + }); + + var inStream = new stream.Readable(); + inStream._read = function noop() {}; + inStream.push(rawData); + inStream.push(null); + + inStream.on('error', function(err) { + should.not.exist(err); + return callback(err); + }); + + inStream.on('end', function() { + connection.commit(function(err) { + should.not.exist(err); + callback(); + }); + }); + + inStream.pipe(lob); + } + ); + }, + function doQuery(callback) { + connection.execute( + "BEGIN SELECT content INTO :bv FROM " + tableName + " WHERE num = 2; END;", + { bv: {dir: oracledb.BIND_OUT, type: oracledb.CLOB} }, + { maxRows: 500 }, + function(err, result) { + should.not.exist(err); + + var content = ''; + var lob = result.outBinds.bv; + lob.setEncoding('utf8'); + + lob.on('data', function(chunk) { + content += chunk; + }); + + lob.on('end', function() { + (content.length).should.be.exactly(dataLength); + (content).should.eql(rawData); + callback(); + }); + + lob.on('error', function(err) { + should.not.exist(err); + }); + } + ); + } + ], done); + }) + }) // 60.2 + }) diff --git a/test/dataTypeChar.js b/test/dataTypeChar.js index 339fd67..38ab5dc 100644 --- a/test/dataTypeChar.js +++ b/test/dataTypeChar.js @@ -1,4 +1,4 @@ -/* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. */ +/* Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. */ /****************************************************************************** * @@ -34,9 +34,10 @@ "use strict" var oracledb = require('oracledb'); -var should = require('should'); -var assist = require('./dataTypeAssist.js'); +var should = require('should'); +var assist = require('./dataTypeAssist.js'); var dbConfig = require('./dbConfig.js'); +var async = require('async'); describe('22. dataTypeChar.js', function(){ @@ -106,4 +107,208 @@ describe('22. dataTypeChar.js', function(){ assist.verifyNullValues(connection, tableName, done); }) }) + + describe('22.3 PL/SQL binding scalar', function() { + + it('22.3.1 PL/SQL binding scalar values IN', function(done) { + async.series([ + function(callback) { + var proc = "CREATE OR REPLACE\n" + + "FUNCTION testchar(stringValue IN CHAR) RETURN CHAR\n" + + "IS\n" + + "BEGIN\n" + + " RETURN 'Hello ' || stringValue || ' world!';\n" + + "END testchar;"; + connection.should.be.ok; + connection.execute( + proc, + function(err) { + should.not.exist(err); + callback(); + } + ); + }, + function(callback) { + var bindvars = { + result: {type: oracledb.STRING, dir: oracledb.BIND_OUT}, + stringValue: {type: oracledb.STRING, dir: oracledb.BIND_IN, val: 'Node.js'} + }; + connection.execute( + "BEGIN :result := testchar(:stringValue); END;", + bindvars, + function(err, result) { + should.not.exist(err); + // console.log(result); + callback(); + } + ); + }, + function(callback) { + connection.execute( + "DROP FUNCTION testchar", + function(err) { + should.not.exist(err); + callback(); + } + ); + } + ], done); + }) // 22.3.1 + + it('22.3.2 bind scalar values INOUT', function(done) { + async.series([ + function(callback) { + var proc = "CREATE OR REPLACE\n" + + "PROCEDURE test(stringValue IN OUT NOCOPY CHAR)\n" + + "IS\n" + + "BEGIN\n" + + " stringValue := '(' || stringValue || ')';\n" + + "END test;\n"; + connection.execute( + proc, + function(err) { + should.not.exist(err); + callback(); + } + ); + }, + function(callback) { + var bindvars = { stringValue: {type: oracledb.STRING, dir: oracledb.BIND_INOUT, val: 'Node.js'} }; + connection.execute( + "BEGIN test(:stringValue); END;", + bindvars, + function(err, result) { + should.exist(err); + // Error: ORA-06502: PL/SQL: numeric or value error: character string buffer too small + // For SQL*PLUS driver, the behavior is the same + callback(); + } + ); + }, + function(callback) { + connection.execute( + "DROP PROCEDURE test", + function(err) { + should.not.exist(err); + callback(); + } + ); + } + ], done); + }) // 22.3.2 + + it('22.3.3 bind scalar values OUT', function(done) { + async.series([ + function(callback) { + var proc = "CREATE OR REPLACE\n" + + "PROCEDURE test(stringValue OUT NOCOPY CHAR)\n" + + "IS\n" + + "BEGIN\n" + + " stringValue := 'Hello Node.js World!';\n" + + "END test;\n"; + connection.execute( + proc, + function(err) { + should.not.exist(err); + callback(); + } + ); + }, + function(callback) { + var bindvars = { stringValue: {type: oracledb.STRING, dir: oracledb.BIND_OUT, maxSize:200} }; + connection.execute( + "BEGIN test(:stringValue); END;", + bindvars, + function(err, result) { + should.not.exist(err); + // There are trailing spaces with the outBind value as CHAR is a kind of + // fix-size data type. So the case uses trim() function. + (result.outBinds.stringValue.trim()).should.be.exactly('Hello Node.js World!'); + callback(); + } + ); + }, + function(callback) { + connection.execute( + "DROP PROCEDURE test", + function(err) { + should.not.exist(err); + callback(); + } + ); + } + ], done); + }) // 22.3.3 + }) // 22.3 + + describe('22.4 PL/SQL binding indexed tables', function() { + + it.skip('22.4.1 bind indexed table IN', function(done) { + async.series([ + function(callback) { + var proc = "CREATE OR REPLACE PACKAGE\n" + + "oracledb_testpack\n" + + "IS\n" + + " TYPE stringsType IS TABLE OF CHAR(30) INDEX BY BINARY_INTEGER;\n" + + " FUNCTION test(strings IN stringsType) RETURN CHAR;\n" + + "END;"; + connection.should.be.ok; + connection.execute( + proc, + function(err) { + should.not.exist(err); + callback(); + } + ); + }, + function(callback) { + var proc = "CREATE OR REPLACE PACKAGE BODY\n" + + "oracledb_testpack\n" + + "IS\n" + + " FUNCTION test(strings IN stringsType) RETURN CHAR\n" + + " IS\n" + + " s CHAR(2000) := '';\n" + + " BEGIN\n" + + " FOR i IN 1 .. strings.COUNT LOOP\n" + + " s := s || strings(i);\n" + + " END LOOP;\n" + + " RETURN s;\n" + + " END;\n" + + "END;"; + connection.execute( + proc, + function(err) { + should.not.exist(err); + callback(); + } + ); + }, + function(callback) { + var bindvars = { + result: {type: oracledb.STRING, dir: oracledb.BIND_OUT, maxSize: 2000}, + strings: {type: oracledb.STRING, dir: oracledb.BIND_IN, val: ['John', 'Doe']} + }; + connection.execute( + "BEGIN :result := oracledb_testpack.test(:strings); END;", + bindvars, + function(err, result) { + should.not.exist(err); + console.log(result); + //result.outBinds.result.should.be.exactly('JohnDoe'); + callback(); + } + ); + }, + function(callback) { + connection.execute( + "DROP PACKAGE oracledb_testpack", + function(err) { + should.not.exist(err); + callback(); + } + ); + } + ], done); + }) + }) }) diff --git a/test/dataTypeRaw.js b/test/dataTypeRaw.js index 92537af..cdc88a8 100644 --- a/test/dataTypeRaw.js +++ b/test/dataTypeRaw.js @@ -168,7 +168,6 @@ describe('42. dataTypeRaw.js', function() { var seq = 1; var size = 10; var bindValue = assist.createBuffer(size); - // console.log("original value", bindValue.toString('hex')); connection.execute( "INSERT INTO " + tableName + " VALUES (:n, :c) RETURNING num, content INTO :rid, :rc", @@ -183,8 +182,6 @@ describe('42. dataTypeRaw.js', function() { should.exist(err); (err.message).should.startWith('NJS-028'); // NJS-028: raw database type is not supported with DML Returning statements - // (result.outBinds.rc[0].toString('hex')).should.eql(bindValue.toString('hex')); - // (result.outBinds.rc[0].length).should.be.exactly(size); done(); } ); @@ -207,9 +204,6 @@ describe('42. dataTypeRaw.js', function() { function(err, result) { should.exist(err); (err.message).should.startWith('NJS-028'); - // console.log(result); - // (result.outBinds.rc[0].toString('hex')).should.eql(bindValue.toString('hex')); - // (result.outBinds.rc[0].length).should.be.exactly(size); done(); } ); @@ -232,9 +226,6 @@ describe('42. dataTypeRaw.js', function() { function(err, result) { should.exist(err); (err.message).should.startWith('NJS-028'); - // console.log(result); - // (result.outBinds.rc[0].toString('hex')).should.eql(bindValue.toString('hex')); - // (result.outBinds.rc[0].length).should.be.exactly(size); done(); } ); @@ -246,7 +237,6 @@ describe('42. dataTypeRaw.js', function() { var bindValue = assist.createBuffer(size); connection.execute( - //"INSERT INTO " + tableName + " VALUES (:n, :c) RETURNING num, content INTO :rid, :rc", "UPDATE " + tableName + " SET content = :c WHERE num = :n RETURNING num, content INTO :rid, :rc", { n : seq, @@ -258,9 +248,6 @@ describe('42. dataTypeRaw.js', function() { function(err, result) { should.exist(err); (err.message).should.startWith('NJS-028'); - // console.log(result); - // (result.outBinds.rc[0].toString('hex')).should.eql(bindValue.toString('hex')); - // (result.outBinds.rc[0].length).should.be.exactly(size); done(); } ); @@ -270,7 +257,6 @@ describe('42. dataTypeRaw.js', function() { var seq = 1; connection.execute( - // "INSERT INTO " + tableName + " VALUES (:n, :c) RETURNING num, content INTO :rid, :rc", "DELETE FROM " + tableName + " WHERE num = :1 RETURNING num, content INTO :2, :3", [ seq, @@ -281,9 +267,6 @@ describe('42. dataTypeRaw.js', function() { function(err, result) { should.exist(err); (err.message).should.startWith('NJS-028'); - // console.log(result); - // (result.outBinds.rc[0].toString('hex')).should.eql(bindValue.toString('hex')); - // (result.outBinds.rc[0].length).should.be.exactly(size); done(); } ); @@ -293,7 +276,6 @@ describe('42. dataTypeRaw.js', function() { var seq = 1; connection.execute( - // "INSERT INTO " + tableName + " VALUES (:n, :c) RETURNING num, content INTO :rid, :rc", "DELETE FROM " + tableName + " WHERE num > :n RETURNING num, content INTO :rid, :rc", { n : seq, @@ -304,9 +286,6 @@ describe('42. dataTypeRaw.js', function() { function(err, result) { should.exist(err); (err.message).should.startWith('NJS-028'); - // console.log(result); - // (result.outBinds.rc[0].toString('hex')).should.eql(bindValue.toString('hex')); - // (result.outBinds.rc[0].length).should.be.exactly(size); done(); } ); @@ -422,4 +401,4 @@ describe('42. dataTypeRaw.js', function() { }) }) // 42.4 -}) \ No newline at end of file +}) diff --git a/test/dmlReturning.js b/test/dmlReturning.js index 8579c0b..9003c40 100644 --- a/test/dmlReturning.js +++ b/test/dmlReturning.js @@ -23,6 +23,11 @@ * * DESCRIPTION * Testing driver DML Returning feature. + * + * When DML affects multiple rows we can still use the RETURING INTO, + * but now we must return the values into a collection using the + * BULK COLLECT clause. + * * * NUMBERING RULE * Test numbers follow this numbering rule: @@ -577,4 +582,62 @@ describe('6. dmlReturning.js', function(){ }) }) // 6.2 + + describe('6.3 BULK COLLECT clause', function() { + + var connection = null; + var tableName = "oracledb_varchar2"; + var dataLength = 500; + var rows = []; + for (var i = 0; i < dataLength; i++) + rows[i] = "Row Number " + i; + + before(function(done) { + async.series([ + function(cb) { + oracledb.getConnection(credential, function(err, conn) { + should.not.exist(err); + connection = conn; + cb(); + }); + }, + function insertRows(cb) { + assist.setUp(connection, tableName, rows, cb); + } + ], done); + }) // before + + after(function(done) { + async.series([ + function(cb) { + connection.execute( + "DROP table " + tableName, + function(err) { + should.not.exist(err); + cb(); + } + ); + }, + function(cb) { + connection.release( function(err) { + should.not.exist(err); + cb(); + }); + } + ], done); + }) // after + + /* Pending case*/ + it.skip('6.3.1 ', function(done) { + connection.execute( + "SELECT * FROM " + tableName, + function(err, result) { + //console.log(result); + console.log(result.rows.length); + done(); + } + ); + }) + + }) // 6.3 }) diff --git a/test/list.txt b/test/list.txt index 1d2c9a0..f8a60ab 100644 --- a/test/list.txt +++ b/test/list.txt @@ -224,6 +224,12 @@ 22.1.3 works well with REF Cursor 22.2 stores null value correctly 22.2.1 testing Null, Empty string and Undefined + 22.3 PL/SQL binding scalar + 22.3.1 PL/SQL binding scalar values IN + 22.3.2 bind scalar values INOUT + 22.3.3 bind scalar values OUT + 22.4 PL/SQL binding indexed tables + - 22.4.1 bind indexed table IN 23. dataTypeNchar.js 23.1 testing NCHAR data in various lengths @@ -402,6 +408,37 @@ 42.4 in PL/SQL, the maximum size is 32767 - 42.4.1 when data length is 200 +43. plsqlBinding.js + 43.1 binding PL/SQL indexed table + 43.1.1 binding PL/SQL indexed table IN + 43.1.2 binding PL/SQL indexed table IN OUT + 43.1.3 binding PL/SQL indexed table OUT + 43.2 test exceptions when using PL/SQL indexed table bindings + 43.2.1 maxArraySize is ignored when specifying BIND_IN + 43.2.2 maxArraySize is mandatory for BIND_INOUT + 42.2.3 maxArraySize cannot smaller than the number of array elements + 42.2.4 DATE type has not been supported yet + 42.2.5 the type of the array element should be compatible with binding type declaration + 42.2.6 type compatibility + - 42.2.7 maxSize restriction + 42.2.8 dose not allow array syntax of bindings + 43.3 binding PL/SQL scalar + 43.3.1 binding PL/SQL scalar IN + - 43.3.2 binding PL/SQL scalar IN/OUT + 43.3.3 binding PL/SQL scalar OUT + 43.4 test attribute - maxArraySize + 43.4.1 maxArraySize property is ignored for BIND_IN + 43.4.2 maxArraySize is mandatory for BIND_INOUT + 43.4.3 maxArraySize cannot smaller than the number of array elements + 43.4.4 maxArraySize can be equal to the number of array elements + - 43.4.5 negative case: large value + 43.4.6 negative case: <0 + 43.4.7 negative case: = 0 + 43.4.8 negative case: assigning it to be a string + 43.4.9 negative case: NaN + 43.5 Indexed Table Null Elements + - 43.5.1 null elements work well + 51. accessTerminatedPoolAttributes.js can not access attributes of terminated pool @@ -496,8 +533,11 @@ 59.1.1 reads clob data one by one row from result set 60. clobPlsqlString.js - 60.1 PL/SQL OUT CLOB parameters can also be bound as STRING - 60.2 The returned length is limited to the maximum size + 60.1 BIND OUT as STRING + 60.1.1 PL/SQL OUT CLOB parameters can also be bound as STRING + 60.1.2 The returned length is limited to the maximum size + 60.2 BIND OUT as CLOB + 60.2.1 maxSize option does not take effect when bind out type is clob 61. checkClassesTypes.js 61.1 Oracledb class diff --git a/test/PLSQLbinds.js b/test/plsqlBinding.js similarity index 50% rename from test/PLSQLbinds.js rename to test/plsqlBinding.js index d9fb015..885b185 100644 --- a/test/PLSQLbinds.js +++ b/test/plsqlBinding.js @@ -1,10 +1,45 @@ +/* Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. */ + +/****************************************************************************** + * + * You may not use the identified files except in compliance with the Apache + * License, Version 2.0 (the "License.") + * + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + * + * The node-oracledb test suite uses 'mocha', 'should' and 'async'. + * See LICENSE.md for relevant licenses. + * + * NAME + * 43. plsqlBinding.js + * + * DESCRIPTION + * Allow binding of PL/SQL indexed tables (associative arrays). + * + * NUMBERING RULE + * Test numbers follow this numbering rule: + * 1 - 20 are reserved for basic functional tests + * 21 - 50 are reserved for data type supporting tests + * 51 onwards are for other tests + * + *****************************************************************************/ +'use strict'; + var oracledb = require('oracledb'); var should = require('should'); var async = require('async'); var dbConfig = require('./dbConfig.js'); -describe('43. PL/SQL binds', function() { +describe('43. plsqlBinding.js', function() { if(dbConfig.externalAuth){ var credential = { externalAuth: true, connectString: dbConfig.connectString }; @@ -58,10 +93,10 @@ describe('43. PL/SQL binds', function() { " s VARCHAR2(2000) := '';\n" + " BEGIN\n" + " FOR i IN 1 .. strings.COUNT LOOP\n" + - " s := s || NVL(strings(i), 'NULL');\n" + + " s := s || strings(i);\n" + " END LOOP;\n" + " FOR i IN 1 .. numbers.COUNT LOOP\n" + - " s := s || NVL(numbers(i), 0);\n" + + " s := s || numbers(i);\n" + " END LOOP;\n" + " RETURN s;\n" + " END;\n" + @@ -77,9 +112,9 @@ describe('43. PL/SQL binds', function() { }, function(callback) { var bindvars = { - strings: {type: oracledb.STRING, dir: oracledb.BIND_IN, val: ['John', null, 'Doe']}, - numbers: {type: oracledb.NUMBER, dir: oracledb.BIND_IN, val: [8, null, 11]}, - result: {type: oracledb.STRING, dir: oracledb.BIND_OUT, maxSize: 2000} + result: {type: oracledb.STRING, dir: oracledb.BIND_OUT, maxSize: 2000}, + strings: {type: oracledb.STRING, dir: oracledb.BIND_IN, val: ['John', 'Doe']}, + numbers: {type: oracledb.NUMBER, dir: oracledb.BIND_IN, val: [0, 8, 11]} }; connection.execute( "BEGIN :result := oracledb_testpack.test(:strings, :numbers); END;", @@ -87,7 +122,7 @@ describe('43. PL/SQL binds', function() { function(err, result) { should.not.exist(err); // console.log(result); - result.outBinds.result.should.be.exactly('JohnNULLDoe8011'); + result.outBinds.result.should.be.exactly('JohnDoe0811'); callback(); } ); @@ -251,12 +286,12 @@ describe('43. PL/SQL binds', function() { async.series([ function(callback) { var proc = "CREATE OR REPLACE PACKAGE\n" + - "oracledb_testpack\n" + - "IS\n" + - " TYPE stringsType IS TABLE OF VARCHAR2(30) INDEX BY BINARY_INTEGER;\n" + - " TYPE numbersType IS TABLE OF NUMBER INDEX BY BINARY_INTEGER;\n" + - " PROCEDURE test(items IN NUMBER, strings OUT NOCOPY stringsType, numbers OUT NOCOPY numbersType);\n" + - "END;"; + "oracledb_testpack\n" + + "IS\n" + + " TYPE stringsType IS TABLE OF VARCHAR2(30) INDEX BY BINARY_INTEGER;\n" + + " TYPE numbersType IS TABLE OF NUMBER INDEX BY BINARY_INTEGER;\n" + + " PROCEDURE test(items IN NUMBER, strings OUT NOCOPY stringsType, numbers OUT NOCOPY numbersType);\n" + + "END;"; connection.should.be.ok; connection.execute( proc, @@ -320,11 +355,20 @@ describe('43. PL/SQL binds', function() { ], done); }); - // - // Test exceptions when using PL/SQL indexed table bindings - // - it('43.1.5 binding PL/SQL indexed table exceptions', function(done) { + }) + + describe('43.2 test exceptions when using PL/SQL indexed table bindings', function() { + var connection = null; + + before(function(done) { async.series([ + function(callback) { + oracledb.getConnection(credential, function(err, conn) { + should.not.exist(err); + connection = conn; + callback(); + }); + }, function(callback) { var proc = "CREATE OR REPLACE PACKAGE\n" + "oracledb_testpack\n" + @@ -345,8 +389,8 @@ describe('43. PL/SQL binds', function() { should.not.exist(err); callback(); } - ); - }, + ); + }, function(callback) { var proc = "CREATE OR REPLACE PACKAGE BODY\n" + "oracledb_testpack\n" + @@ -365,119 +409,12 @@ describe('43. PL/SQL binds', function() { callback(); } ); - }, - // EXCEPTION: NJS-034: invalid (array) binding of parameter ":p": the "maxArraySize" property is not allowed when specifying a BIND_IN parameter - function(callback) { - var bindvars = { - p: {type: oracledb.NUMBER, dir: oracledb.BIND_IN, val: [1, 2, 3], maxArraySize: 3} - }; - connection.execute( - "BEGIN oracledb_testpack.test1(:p); END;", - bindvars, - function(err, result) { - should.exist(err); - (err.message).should.be.exactly('NJS-034: invalid (array) binding of parameter ":p": the "maxArraySize" property is not allowed when specifying a BIND_IN parameter'); - should.not.exist(result); - callback(); - } - ); - }, - // EXCEPTION: NJS-034: invalid (array) binding of parameter ":p": the \"maxArraySize\" property is mandatory when specifying a BIND_INOUT parameter - function(callback) { - var bindvars = { - p: {type: oracledb.NUMBER, dir: oracledb.BIND_INOUT, val: [1, 2, 3]} - }; - connection.execute( - "BEGIN oracledb_testpack.test2(:p); END;", - bindvars, - function(err, result) { - should.exist(err); - (err.message).should.be.exactly('NJS-034: invalid (array) binding of parameter ":p": the \"maxArraySize\" property is mandatory when specifying a BIND_INOUT parameter'); - should.not.exist(result); - callback(); - } - ); - }, - // EXCEPTION: NJS-034: invalid (array) binding of parameter ":p": the number of array elements "3" is larger then the "maxArraySize" property "2" - function(callback) { - var bindvars = { - p: {type: oracledb.NUMBER, dir: oracledb.BIND_INOUT, val: [1, 2, 3], maxArraySize: 2} - }; - connection.execute( - "BEGIN oracledb_testpack.test3(:p); END;", - bindvars, - function(err, result) { - should.exist(err); - (err.message).should.be.exactly('NJS-034: invalid (array) binding of parameter ":p": the number of array elements "3" is larger then the "maxArraySize" property "2"'); - should.not.exist(result); - callback(); - } - ); - }, - // EXCEPTION: NJS-034: invalid (array) binding of parameter ":p": the "type" property must be specified and only STRING and NUMBER are (currently) supported - function(callback) { - var bindvars = { - p: {type: oracledb.DATE, dir: oracledb.BIND_IN, val: [new Date(), new Date()]} - }; - connection.execute( - "BEGIN oracledb_testpack.test3(:p); END;", - bindvars, - function(err, result) { - should.exist(err); - (err.message).should.be.exactly('NJS-034: invalid (array) binding of parameter ":p": the "type" property must be specified and only STRING and NUMBER are (currently) supported'); - should.not.exist(result); - callback(); - } - ); - }, - // EXCEPTION: NJS-034: invalid (array) binding of parameter ":p": the type of the array element "1" of the "val" property is not compatible with the "type" property STRING - function(callback) { - var bindvars = { - p: {type: oracledb.STRING, dir: oracledb.BIND_IN, val: ['hello', 1]} - }; - connection.execute( - "BEGIN oracledb_testpack.test4(:p); END;", - bindvars, - function(err, result) { - should.exist(err); - (err.message).should.be.exactly('NJS-034: invalid (array) binding of parameter ":p": the type of the array element "1" of the "val" property is not compatible with the "type" property STRING'); - should.not.exist(result); - callback(); - } - ); - }, - // EXCEPTION: NJS-034: invalid (array) binding of parameter ":p": the string length 28 of the array element "0" of the "val" property is larger then the "maxSize" property 10 - function(callback) { - var bindvars = { - p: {type: oracledb.STRING, dir: oracledb.BIND_IN, val: ['this is a quite longs string'], maxSize: 10} - }; - connection.execute( - "BEGIN oracledb_testpack.test4(:p); END;", - bindvars, - function(err, result) { - should.exist(err); - (err.message).should.be.exactly('NJS-034: invalid (array) binding of parameter ":p": the string length 28 of the array element "0" of the "val" property is larger then the "maxSize" property 10'); - should.not.exist(result); - callback(); - } - ); - }, - // EXCEPTION: NJS-034: invalid (array) binding of parameter ":p": the type of the array element "1" of the "val" property is not compatible with the "type" property STRING - function(callback) { - var bindvars = { - p: {type: oracledb.NUMBER, dir: oracledb.BIND_IN, val: [1, 'hello']} - }; - connection.execute( - "BEGIN oracledb_testpack.test5(:p); END;", - bindvars, - function(err, result) { - should.exist(err); - (err.message).should.be.exactly('NJS-034: invalid (array) binding of parameter ":p": the type of the array element "1" of the "val" property is not compatible with the "type" property NUMBER'); - should.not.exist(result); - callback(); - } - ); - }, + } + ], done); + }) // before + + after(function(done) { + async.series([ function(callback) { connection.execute( "DROP PACKAGE oracledb_testpack", @@ -485,14 +422,136 @@ describe('43. PL/SQL binds', function() { should.not.exist(err); callback(); } - ); + ); + }, + function(callback) { + connection.release(function(err) { + should.not.exist(err); + callback(); + }); } ], done); - }); + }) // after - }); + it('43.2.1 maxArraySize is ignored when specifying BIND_IN', function(done) { + var bindvars = { + p: {type: oracledb.NUMBER, dir: oracledb.BIND_IN, val: [1, 2, 3], maxArraySize: 2} + }; + connection.execute( + "BEGIN oracledb_testpack.test1(:p); END;", + bindvars, + function(err, result) { + should.not.exist(err); + should.exist(result); + done(); + } + ); + }) + + it('43.2.2 maxArraySize is mandatory for BIND_INOUT ', function(done) { + var bindvars = { + p: {type: oracledb.NUMBER, dir: oracledb.BIND_INOUT, val: [1, 2, 3]} + }; + connection.execute( + "BEGIN oracledb_testpack.test2(:p); END;", + bindvars, + function(err, result) { + should.exist(err); + (err.message).should.startWith('NJS-036'); + // NJS-036: Property maxArraySize is required for INOUT Array binds. + should.not.exist(result); + done(); + } + ); + }) + + it('42.2.3 maxArraySize cannot smaller than the number of array elements', function(done) { + var bindvars = { + p: {type: oracledb.NUMBER, dir: oracledb.BIND_INOUT, val: [1, 2, 3], maxArraySize: 2} + }; + connection.execute( + "BEGIN oracledb_testpack.test3(:p); END;", + bindvars, + function(err, result) { + should.exist(err); + (err.message).should.startWith('NJS-036'); + // NJS-036: Given Array is of size greater than maxArraySize property. + should.not.exist(result); + done(); + } + ); + }) + + it('42.2.4 DATE type has not been supported yet', function(done) { + var bindvars = { + p: {type: oracledb.DATE, dir: oracledb.BIND_IN, val: [new Date(), new Date()]} + }; + connection.execute( + "BEGIN oracledb_testpack.test3(:p); END;", + bindvars, + function(err, result) { + should.exist(err); + (err.message).should.startWith('NJS-034'); + should.not.exist(result); + done(); + } + ); + }) + + it('42.2.5 the type of the array element should be compatible with binding type declaration', function(done) { + var bindvars = { + p: {type: oracledb.STRING, dir: oracledb.BIND_IN, val: ['hello', 1]} + }; + connection.execute( + "BEGIN oracledb_testpack.test4(:p); END;", + bindvars, + function(err, result) { + should.exist(err); + (err.message).should.startWith('NJS-037'); + // NJS-037: incompatible type of value provided. + should.not.exist(result); + done(); + } + ); + }) + + it('42.2.6 type compatibility', function(done) { + var bindvars = { + p: {type: oracledb.NUMBER, dir: oracledb.BIND_IN, val: [1, 'hello']} + }; + connection.execute( + "BEGIN oracledb_testpack.test5(:p); END;", + bindvars, + function(err, result) { + should.exist(err); + (err.message).should.startWith('NJS-037'); + // NJS-037: incompatible type of value provided. + should.not.exist(result); + done(); + } + ); + }) - describe('43.2 binding PL/SQL scalar', function() { + it.skip('42.2.7 maxSize restriction', function(done) { + var bindvars = { + p: {type: oracledb.STRING, dir: oracledb.BIND_IN, val: ['this is a quite longs string'], maxSize: 10} + }; + connection.execute( + "BEGIN oracledb_testpack.test4(:p); END;", + bindvars, + function(err, result) { + should.exist(err); + (err.message).should.startWith('NJS-039'); + // NJS-039: Invalid value or size provided in Array binds. + should.not.exist(result); + done(); + } + ); + }) + + }) // 43.2 + + describe('43.3 binding PL/SQL scalar', function() { var connection = null; before(function(done) { @@ -510,14 +569,14 @@ describe('43. PL/SQL binds', function() { }); }) - it('43.2.1 binding PL/SQL scalar IN', function(done) { + it('43.3.1 binding PL/SQL scalar IN', function(done) { async.series([ function(callback) { var proc = "CREATE OR REPLACE\n" + - "FUNCTION test(stringValue IN VARCHAR2, numberValue IN NUMBER) RETURN VARCHAR2\n" + + "FUNCTION test(stringValue IN VARCHAR2, numberValue IN NUMBER, dateValue IN DATE) RETURN VARCHAR2\n" + "IS\n" + "BEGIN\n" + - " RETURN stringValue || ' ' || numberValue;\n" + + " RETURN stringValue || ' ' || numberValue || ' released in ' || TO_CHAR(dateValue, 'MON YYYY');\n" + "END test;"; connection.should.be.ok; connection.execute( @@ -530,17 +589,18 @@ describe('43. PL/SQL binds', function() { }, function(callback) { var bindvars = { - result: {type: oracledb.STRING, dir: oracledb.BIND_OUT, maxSize: 2000}, - stringValue: {type: oracledb.STRING, dir: oracledb.BIND_IN, val: 'Space odyssey'}, - numberValue: {type: oracledb.NUMBER, dir: oracledb.BIND_IN, val: 2001} + result: {type: oracledb.STRING, dir: oracledb.BIND_OUT, maxSize: 2000}, + stringValue: {type: oracledb.STRING, dir: oracledb.BIND_IN, val: 'Space odyssey'}, + numberValue: {type: oracledb.NUMBER, dir: oracledb.BIND_IN, val: 2001 }, + dateValue: {type: oracledb.DATE, dir: oracledb.BIND_IN, val: new Date(1968, 3, 2) } }; connection.execute( - "BEGIN :result := test(:stringValue, :numberValue); END;", + "BEGIN :result := test(:stringValue, :numberValue, :dateValue); END;", bindvars, function(err, result) { should.not.exist(err); //console.log(result); - result.outBinds.result.should.be.exactly('Space odyssey 2001'); + result.outBinds.result.should.be.exactly('Space odyssey 2001 released in APR 1968'); callback(); } ); @@ -557,15 +617,16 @@ describe('43. PL/SQL binds', function() { ], done); }); - it('43.2.2 binding PL/SQL scalar IN/OUT', function(done) { + it.skip('43.3.2 binding PL/SQL scalar IN/OUT', function(done) { async.series([ function(callback) { var proc = "CREATE OR REPLACE\n" + - "PROCEDURE test(stringValue IN OUT NOCOPY VARCHAR2, numberValue IN OUT NOCOPY NUMBER)\n" + + "PROCEDURE test(stringValue IN OUT NOCOPY VARCHAR2, numberValue IN OUT NOCOPY NUMBER, dateValue IN OUT NOCOPY DATE)\n" + "IS\n" + "BEGIN\n" + " stringValue := '(' || stringValue || ')';\n" + " numberValue := NumberValue + 100;\n" + + //" dateValue := " "END test;\n"; connection.should.be.ok; connection.execute( @@ -577,18 +638,21 @@ describe('43. PL/SQL binds', function() { ); }, function(callback) { + var releaseDate = new Date(1968, 3, 2); var bindvars = { - stringValue: {type: oracledb.STRING, dir: oracledb.BIND_INOUT, val: 'Space odyssey'}, - numberValue: {type: oracledb.NUMBER, dir: oracledb.BIND_INOUT, val: 2001} + stringValue: {type: oracledb.STRING, dir: oracledb.BIND_INOUT, val: 'Space odyssey'}, + numberValue: {type: oracledb.NUMBER, dir: oracledb.BIND_INOUT, val: 2001}, + dateValue: {type: oracledb.DATE, dir: oracledb.BIND_INOUT, val: releaseDate} }; connection.execute( - "BEGIN test(:stringValue, :numberValue); END;", + "BEGIN test(:stringValue, :numberValue, :dateValue); END;", bindvars, function(err, result) { should.not.exist(err); - //console.log(result); + console.log(result); result.outBinds.stringValue.should.be.exactly('(Space odyssey)'); result.outBinds.numberValue.should.be.exactly(2101); + //result.outBinds.dateValue.should.eql(releaseDate) callback(); } ); @@ -605,15 +669,16 @@ describe('43. PL/SQL binds', function() { ], done); }); - it('43.2.2 binding PL/SQL scalar OUT', function(done) { + it('43.3.3 binding PL/SQL scalar OUT', function(done) { async.series([ function(callback) { var proc = "CREATE OR REPLACE\n" + - "PROCEDURE test(stringValue IN OUT NOCOPY VARCHAR2, numberValue IN OUT NOCOPY NUMBER)\n" + + "PROCEDURE test(stringValue OUT VARCHAR2, numberValue OUT NUMBER, dateValue OUT DATE)\n" + "IS\n" + "BEGIN\n" + " stringValue := 'Space odyssey';\n" + " numberValue := 2001;\n" + + " dateValue := TO_DATE('04-02-1968', 'MM-DD-YYYY');" + "END test;\n"; connection.should.be.ok; connection.execute( @@ -627,16 +692,18 @@ describe('43. PL/SQL binds', function() { function(callback) { var bindvars = { stringValue: {type: oracledb.STRING, dir: oracledb.BIND_OUT}, - numberValue: {type: oracledb.NUMBER, dir: oracledb.BIND_OUT} + numberValue: {type: oracledb.NUMBER, dir: oracledb.BIND_OUT}, + dateValue: {type: oracledb.DATE, dir: oracledb.BIND_OUT} }; connection.execute( - "BEGIN test(:stringValue, :numberValue); END;", + "BEGIN test(:stringValue, :numberValue, :dateValue); END;", bindvars, function(err, result) { should.not.exist(err); - //console.log(result); + // console.log(result); result.outBinds.stringValue.should.be.exactly('Space odyssey'); result.outBinds.numberValue.should.be.exactly(2001); + (typeof (result.outBinds.dateValue)).should.eql('object'); callback(); } ); @@ -653,6 +720,322 @@ describe('43. PL/SQL binds', function() { ], done); }); - }); + }) // 43.3 + + describe('43.4 test attribute - maxArraySize', function() { + var connection = null; + + before(function(done) { + async.series([ + function(cb) { + oracledb.getConnection(credential, function(err, conn) { + should.not.exist(err); + connection = conn; + cb(); + }); + }, + function(cb) { + var proc = "CREATE OR REPLACE PACKAGE\n" + + "oracledb_testpack\n" + + "IS\n" + + " TYPE datesType IS TABLE OF DATE INDEX BY BINARY_INTEGER;\n" + + " TYPE numbersType IS TABLE OF NUMBER INDEX BY BINARY_INTEGER;\n" + + " TYPE stringsType IS TABLE OF VARCHAR2(2000) INDEX BY BINARY_INTEGER;\n" + + " PROCEDURE test1(p IN numbersType);\n" + + " PROCEDURE test2(p IN OUT NOCOPY numbersType);\n" + + " PROCEDURE test3(p IN datesType);\n" + + " PROCEDURE test4(p IN stringsType);\n" + + " PROCEDURE test5(p IN numbersType);\n" + + "END;"; + connection.should.be.ok; + connection.execute( + proc, + function(err) { + should.not.exist(err); + cb(); + } + ); + }, + function(cb) { + var proc = "CREATE OR REPLACE PACKAGE BODY\n" + + "oracledb_testpack\n" + + "IS\n" + + " PROCEDURE test1(p IN numbersType) IS BEGIN NULL; END;\n" + + " PROCEDURE test2(p IN OUT NOCOPY numbersType) IS BEGIN NULL; END;\n" + + " PROCEDURE test3(p IN datesType) IS BEGIN NULL; END;\n" + + " PROCEDURE test4(p IN stringsType) IS BEGIN NULL; END;\n" + + " PROCEDURE test5(p IN numbersType) IS BEGIN NULL; END;\n" + + "END;"; + connection.execute( + proc, + function(err) { + should.not.exist(err); + cb(); + } + ); + } + ], done); + }) // before + + after(function(done) { + async.series([ + function(callback) { + connection.execute( + "DROP PACKAGE oracledb_testpack", + function(err) { + should.not.exist(err); + callback(); + } + ); + }, + function(callback) { + connection.release(function(err) { + should.not.exist(err); + callback(); + }); + } + ], done); + }) // after + + it('43.4.1 maxArraySize property is ignored for BIND_IN', function(done) { + var bindvars = { + p: {type: oracledb.NUMBER, dir: oracledb.BIND_IN, val: [1, 2, 3], maxArraySize: 1} + }; + connection.execute( + "BEGIN oracledb_testpack.test1(:p); END;", + bindvars, + function(err, result) { + should.not.exist(err); + should.exist(result); + done(); + } + ); + }) -}); + it('43.4.2 maxArraySize is mandatory for BIND_INOUT', function(done) { + var bindvars = { + p: {type: oracledb.NUMBER, dir: oracledb.BIND_INOUT, val: [1, 2, 3]} + }; + connection.execute( + "BEGIN oracledb_testpack.test2(:p); END;", + bindvars, + function(err, result) { + should.exist(err); + (err.message).should.startWith('NJS-036'); + // NJS-036: Property maxArraySize is required for INOUT Array binds. + should.not.exist(result); + done(); + } + ); + }) + + it('43.4.3 maxArraySize cannot smaller than the number of array elements', function(done) { + var bindvars = { + p: {type: oracledb.NUMBER, dir: oracledb.BIND_INOUT, val: [1, 2, 3], maxArraySize: 2} + }; + connection.execute( + "BEGIN oracledb_testpack.test2(:p); END;", + bindvars, + function(err, result) { + should.exist(err); + (err.message).should.startWith('NJS-036'); + // NJS-036: given Array is of size greater than maxArraySize property. + should.not.exist(result); + done(); + } + ); + }) + + it('43.4.4 maxArraySize can be equal to the number of array elements', function(done) { + var bindvars = { + p: {type: oracledb.NUMBER, dir: oracledb.BIND_INOUT, val: [1, 2, 3], maxArraySize: 3} + }; + connection.execute( + "BEGIN oracledb_testpack.test2(:p); END;", + bindvars, + function(err, result) { + should.not.exist(err); + done(); + } + ); + }) + + // The maximum safe integer in JavaScript is (2^53 - 1). + it.skip('43.4.5 negative case: large value', function(done) { + var bindvars = { + p: {type: oracledb.NUMBER, dir: oracledb.BIND_INOUT, val: [1, 2, 3], maxArraySize: 987654321} + }; + connection.execute( + "BEGIN oracledb_testpack.test2(:p); END;", + bindvars, + function(err, result) { + should.not.exist(err); + done(); + } + ); + }) + + it('43.4.6 negative case: <0', function(done) { + var bindvars = { + p: {type: oracledb.NUMBER, dir: oracledb.BIND_INOUT, val: [1, 2, 3], maxArraySize: -9} + }; + connection.execute( + "BEGIN oracledb_testpack.test2(:p); END;", + bindvars, + function(err, result) { + should.exist(err); + (err.message).should.startWith('NJS-007'); + // NJS-007: invalid value for "maxArraySize" + done(); + } + ); + }) + + it('43.4.7 negative case: = 0', function(done) { + var bindvars = { + p: {type: oracledb.NUMBER, dir: oracledb.BIND_INOUT, val: [1, 2, 3], maxArraySize: 0} + }; + connection.execute( + "BEGIN oracledb_testpack.test2(:p); END;", + bindvars, + function(err, result) { + should.exist(err); + (err.message).should.startWith('NJS-036'); + // NJS-036: given array is of size greater than maxArraySize + done(); + } + ); + }) + + it('43.4.8 negative case: assigning it to be a string', function(done) { + var bindvars = { + p: {type: oracledb.NUMBER, dir: oracledb.BIND_INOUT, val: [1, 2, 3], maxArraySize: 'foobar'} + }; + connection.execute( + "BEGIN oracledb_testpack.test2(:p); END;", + bindvars, + function(err, result) { + should.exist(err); + (err.message).should.startWith('NJS-008'); + // NJS-008: invalid type for "maxArraySize" + done(); + } + ); + }) + + it('43.4.9 negative case: NaN', function(done) { + var bindvars = { + p: {type: oracledb.NUMBER, dir: oracledb.BIND_INOUT, val: [1, 2, 3], maxArraySize: NaN} + }; + connection.execute( + "BEGIN oracledb_testpack.test2(:p); END;", + bindvars, + function(err, result) { + should.exist(err); + (err.message).should.startWith('NJS-007'); + // NJS-007: invalid value for "maxArraySize" + done(); + } + ); + }) + + }) // 43.4 + + describe('43.5 Indexed Table Null Elements', function() { + var connection = null; + + before(function(done) { + async.series([ + function(cb) { + oracledb.getConnection(credential, function(err, conn) { + should.not.exist(err); + connection = conn; + cb(); + }); + }, + function(cb) { + var proc = "CREATE OR REPLACE PACKAGE\n" + + "oracledb_testpack\n" + + "IS\n" + + " TYPE stringsType IS TABLE OF VARCHAR2(30) INDEX BY BINARY_INTEGER;\n" + + " TYPE numbersType IS TABLE OF NUMBER INDEX BY BINARY_INTEGER;\n" + + " FUNCTION test(strings IN stringsType, numbers IN numbersType) RETURN VARCHAR2;\n" + + "END;"; + connection.should.be.ok; + connection.execute( + proc, + function(err) { + should.not.exist(err); + cb(); + } + ); + }, + function(cb) { + var proc = "CREATE OR REPLACE PACKAGE BODY\n" + + "oracledb_testpack\n" + + "IS\n" + + " FUNCTION test(strings IN stringsType, numbers IN numbersType) RETURN VARCHAR2\n" + + " IS\n" + + " s VARCHAR2(2000) := '';\n" + + " BEGIN\n" + + " FOR i IN 1 .. strings.COUNT LOOP\n" + + " s := s || strings(i);\n" + + " END LOOP;\n" + + " FOR i IN 1 .. numbers.COUNT LOOP\n" + + " s := s || numbers(i);\n" + + " END LOOP;\n" + + " RETURN s;\n" + + " END;\n" + + "END;"; + connection.execute( + proc, + function(err) { + should.not.exist(err); + cb(); + } + ); + } + ], done); + }) // before + + after(function(done) { + async.series([ + function(callback) { + connection.execute( + "DROP PACKAGE oracledb_testpack", + function(err) { + should.not.exist(err); + callback(); + } + ); + }, + function(callback) { + connection.release(function(err) { + should.not.exist(err); + callback(); + }); + } + ], done); + }) // after + + it.skip('43.5.1 null elements work well', function(done) { + var bindvars = { + result: {type: oracledb.STRING, dir: oracledb.BIND_OUT, maxSize: 2000}, + strings: {type: oracledb.STRING, dir: oracledb.BIND_IN, val: ['One', 'Two', 'Three', null, '']}, + numbers: {type: oracledb.NUMBER, dir: oracledb.BIND_IN, val: [1, 2, 3, null, '']} + }; + + connection.execute( + "BEGIN :result := oracledb_testpack.test(:strings, :numbers); END;", + bindvars, + function(err, result) { + should.not.exist(err); + // Error: NJS-037: incompatible type of value provided + console.log(result); + result.outBinds.result.should.be.exactly('OneTwoThree123'); + done(); + } + ); + }) + }) // 43.5 +})