Updates for 1.9.0 Development branch

This commit is contained in:
Christopher Jones 2016-04-19 13:56:22 +10:00
parent db5950f1cf
commit 8c52101005
27 changed files with 2523 additions and 575 deletions

View File

@ -1,5 +1,35 @@
# Change Log # Change Log
## node-oracledb v1.9.0 Development (19 Apr 2016)
** Note this is a development release: features are subject to change.**
- Added Promise support. All asynchronous functions can now return
promises. By default the standard Promise library is used for Node
0.12, 4 and 5. This can be overridden.
- Added a `toQueryStream()` method for ResultSets, letting REF CURSORS
be transformed into Readable Streams.
- Added an experimental query Stream `_close()` method. It allows query
streams to be closed without needing to fetch all the data. It is
not for production use.
- Added aliases `pool.close()` and `connection.close()` for
`pool.terminate()` and `connection.release()` respectively.
- Some method parameter validation checks, such as the number or types
of parameters, will now throw errors synchronously instead of
returning errors via the callback.
- Removed an extra call to `getRows()` made by `queryStream()` at
end-of-fetch.
- Some random crashes caused by connections being garbage collected
while still in use should no longer occur.
- Regularized NJS error message capitalization.
## node-oracledb v1.8.0 (24 Mar 2016) ## node-oracledb v1.8.0 (24 Mar 2016)
- Added `connection.queryStream()` for returning query results using a - Added `connection.queryStream()` for returning query results using a

View File

@ -402,47 +402,13 @@ Download the [Node.js package](http://nodejs.org) for OS X 64-bit and install it
### 5.3 Install the free Oracle Instant Client 'Basic' and 'SDK' ZIPs ### 5.3 Install the free Oracle Instant Client 'Basic' and 'SDK' ZIPs
Do either of the options given in [5.3.1](#instosxICroot) or [5.3.2](#instosxICuser). Follow the steps in either [5.3.1](#instosxICuser) or [5.3.2](#instosxICroot).
### <a name="instosxICroot"></a> 5.3.1 Install Instant Client in /opt ### <a name="instosxICuser"></a> 5.3.1 Install Instant Client in a user directory
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)
and
[install them](http://www.oracle.com/technetwork/topics/intel-macsoft-096467.html#ic_osx_inst)
into the same directory:
```
sudo su -
unzip instantclient-basic-macos.x64-11.2.0.4.0.zip
unzip instantclient-sdk-macos.x64-11.2.0.4.0.zip
mkdir /opt/oracle
mv instantclient_11_2 /opt/oracle/instantclient
ln -s /opt/oracle/instantclient/libclntsh.dylib.11.1 /opt/oracle/instantclient/libclntsh.dylib
```
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/
```
Continue with [5.4](#instosxICaddon).
### <a name="instosxICuser"></a> 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 Download the free **Basic** and **SDK** 64-bit ZIPs from
[Oracle Technology Network](http://www.oracle.com/technetwork/topics/intel-macsoft-096467.html) [Oracle Technology Network](http://www.oracle.com/technetwork/topics/intel-macsoft-096467.html)
and and unzip them somewhere under your home directory:
[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-basic-macos.x64-11.2.0.4.0.zip
@ -469,6 +435,34 @@ export OCI_INC_DIR=~/instantclient_11_2/sdk/include
These variables are only needed during installation. These variables are only needed during installation.
Continue with [5.4](#instosxICaddon).
### <a name="instosxICroot"></a> 5.3.2 Install Instant Client in /opt
This alternative to [5.3.1](#instosxICroot) requires root access. It
puts Instant Client in the default location used by the node-oracledb
installer. If you don't want to update system directories then follow
the steps in [5.3.1](#instosxICuser) instead.
Download the free **Basic** and **SDK** ZIPs from
[Oracle Technology Network](http://www.oracle.com/technetwork/topics/intel-macsoft-096467.html)
and install them into the same directory:
```
sudo su -
unzip instantclient-basic-macos.x64-11.2.0.4.0.zip
unzip instantclient-sdk-macos.x64-11.2.0.4.0.zip
mkdir /opt/oracle
mv instantclient_11_2 /opt/oracle/instantclient
ln -s /opt/oracle/instantclient/libclntsh.dylib.11.1 /opt/oracle/instantclient/libclntsh.dylib
```
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/
```
### <a name="instosxICaddon"></a> 5.4 Install the add-on ### <a name="instosxICaddon"></a> 5.4 Install the add-on
If you are behind a firewall you may need to set your proxy, for If you are behind a firewall you may need to set your proxy, for

View File

@ -1,11 +1,11 @@
# node-oracledb version 1.8 # node-oracledb version 1.9
## <a name="about"></a> About node-oracledb ## <a name="about"></a> About node-oracledb
The node-oracledb add-on for Node.js powers high performance Oracle The node-oracledb add-on for Node.js powers high performance Oracle
Database applications. Database applications.
Use node-oracledb to connect Node.js 0.10, 0.12, 4 LTS and 5 to Use node-oracledb to connect Node.js 0.10, 0.12, 4 and 5 to
Oracle Database. Oracle Database.
The add-on is stable, well documented, and has a comprehensive test suite. The add-on is stable, well documented, and has a comprehensive test suite.
@ -15,11 +15,12 @@ The node-oracledb project is open source and maintained by Oracle Corp. The hom
### Node-oracledb supports: ### Node-oracledb supports:
- [Promises](https://github.com/oracle/node-oracledb/blob/master/doc/api.md#promiseoverview), Callbacks and Streams
- [SQL and PL/SQL execution](https://github.com/oracle/node-oracledb/blob/master/doc/api.md#sqlexecution) - [SQL and PL/SQL execution](https://github.com/oracle/node-oracledb/blob/master/doc/api.md#sqlexecution)
- Fetching of query results by [callbacks](https://github.com/oracle/node-oracledb/blob/master/doc/api.md#resultsethandling) or [streams](https://github.com/oracle/node-oracledb/blob/master/doc/api.md#streamingresults)
- [REF CURSORs](https://github.com/oracle/node-oracledb/blob/master/doc/api.md#refcursors) - [REF CURSORs](https://github.com/oracle/node-oracledb/blob/master/doc/api.md#refcursors)
- [Large Objects: CLOBs and BLOBs](https://github.com/oracle/node-oracledb/blob/master/doc/api.md#lobhandling) - [Large Objects: CLOBs and BLOBs](https://github.com/oracle/node-oracledb/blob/master/doc/api.md#lobhandling)
- [Query results as JavaScript objects or array ](https://github.com/oracle/node-oracledb/blob/master/doc/api.md#queryoutputformats) - Oracle Database 12.1 [JSON datatype](http://docs.oracle.com/database/121/ADXDB/json.htm#ADXDB6246)
- [Query results as JavaScript objects or arrays](https://github.com/oracle/node-oracledb/blob/master/doc/api.md#queryoutputformats)
- [Smart mapping between JavaScript and Oracle types with manual override available](https://github.com/oracle/node-oracledb/blob/master/doc/api.md#typemap) - [Smart mapping between JavaScript and Oracle types with manual override available](https://github.com/oracle/node-oracledb/blob/master/doc/api.md#typemap)
- [Data binding using JavaScript objects or arrays](https://github.com/oracle/node-oracledb/blob/master/doc/api.md#bind) - [Data binding using JavaScript objects or arrays](https://github.com/oracle/node-oracledb/blob/master/doc/api.md#bind)
- [Transaction Management](https://github.com/oracle/node-oracledb/blob/master/doc/api.md#transactionmgt) - [Transaction Management](https://github.com/oracle/node-oracledb/blob/master/doc/api.md#transactionmgt)
@ -30,11 +31,15 @@ The node-oracledb project is open source and maintained by Oracle Corp. The hom
- [Statement Caching](https://github.com/oracle/node-oracledb/blob/master/doc/api.md#stmtcache) - [Statement Caching](https://github.com/oracle/node-oracledb/blob/master/doc/api.md#stmtcache)
- [Client Result Caching](http://docs.oracle.com/database/121/ADFNS/adfns_perf_scale.htm#ADFNS464) - [Client Result Caching](http://docs.oracle.com/database/121/ADFNS/adfns_perf_scale.htm#ADFNS464)
- [End-to-end Tracing, Mid-tier Authentication, and Auditing](https://github.com/oracle/node-oracledb/blob/master/doc/api.md#endtoend) - [End-to-end Tracing, Mid-tier Authentication, and Auditing](https://github.com/oracle/node-oracledb/blob/master/doc/api.md#endtoend)
- High Availability Features - Oracle High Availability Features
- [Fast Application Notification (FAN)](http://docs.oracle.com/database/121/ADFNS/adfns_avail.htm#ADFNS538) - [Fast Application Notification (FAN)](http://docs.oracle.com/database/121/ADFNS/adfns_avail.htm#ADFNS538)
- [Runtime Load Balancing (RLB)](http://docs.oracle.com/database/121/ADFNS/adfns_perf_scale.htm#ADFNS515) - [Runtime Load Balancing (RLB)](http://docs.oracle.com/database/121/ADFNS/adfns_perf_scale.htm#ADFNS515)
- [Transparent Application Failover (TAF)](http://docs.oracle.com/database/121/ADFNS/adfns_avail.htm#ADFNS534) - [Transparent Application Failover (TAF)](http://docs.oracle.com/database/121/ADFNS/adfns_avail.htm#ADFNS534)
Various Oracle Database and Oracle Client versions, can be used.
Oracle's cross-version compatibility allows one node-oracledb
installation to connect to different database versions.
We are actively working on supporting the best Oracle Database We are actively working on supporting the best Oracle Database
features, and on functionality requests from features, and on functionality requests from
[users involved in the project](https://github.com/oracle/node-oracledb/issues). [users involved in the project](https://github.com/oracle/node-oracledb/issues).
@ -45,7 +50,7 @@ Prerequisites:
- [Python 2.7](https://www.python.org/downloads/) - [Python 2.7](https://www.python.org/downloads/)
- C Compiler with support for C++ 11 (Xcode, gcc, Visual Studio or similar) - 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) "basic" and "SDK" packages 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 - The small, free [Oracle Instant Client](http://www.oracle.com/technetwork/database/features/instant-client/index-100365.html) "basic" and "SDK" packages if your database is remote. Or use the libraries and headers from 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 - 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). Run `npm install oracledb` to install from the [NPM registry](https://www.npmjs.com/package/oracledb).
@ -56,7 +61,7 @@ See [INSTALL](https://github.com/oracle/node-oracledb/tree/master/INSTALL.md) fo
There are examples in the [examples](https://github.com/oracle/node-oracledb/tree/master/examples) directory. There are examples in the [examples](https://github.com/oracle/node-oracledb/tree/master/examples) directory.
### A simple query example: ### A simple query example with callbacks:
```javascript ```javascript
var oracledb = require('oracledb'); var oracledb = require('oracledb');
@ -90,6 +95,8 @@ With Oracle's sample HR schema, the output is:
[ [ 60, 'IT' ], [ 90, 'Executive' ], [ 100, 'Finance' ] ] [ [ 60, 'IT' ], [ 90, 'Executive' ], [ 100, 'Finance' ] ]
``` ```
Node Promises can also be used.
## <a name="doc"></a> Documentation ## <a name="doc"></a> Documentation
See [Documentation for the Oracle Database Node.js Add-on](https://github.com/oracle/node-oracledb/tree/master/doc/api.md). See [Documentation for the Oracle Database Node.js Add-on](https://github.com/oracle/node-oracledb/tree/master/doc/api.md).
@ -98,9 +105,9 @@ See [Documentation for the Oracle Database Node.js Add-on](https://github.com/or
See [CHANGELOG](https://github.com/oracle/node-oracledb/tree/master/CHANGELOG.md) See [CHANGELOG](https://github.com/oracle/node-oracledb/tree/master/CHANGELOG.md)
## <a name="testing"></a> Testsuite ## <a name="testing"></a> Test Suite
To run the included testsuite see [test/README](https://github.com/oracle/node-oracledb/tree/master/test/README.md). To run the included test suite see [test/README](https://github.com/oracle/node-oracledb/tree/master/test/README.md).
## <a name="contrib"></a> Contributing ## <a name="contrib"></a> Contributing

File diff suppressed because it is too large Load Diff

59
examples/promises.js Normal file
View File

@ -0,0 +1,59 @@
/* 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.
*
* NAME
* promises.js
*
* DESCRIPTION
* Executes a basic query using promises instead of the callback pattern.
*
* Scripts to create the HR schema can be found at:
* https://github.com/oracle/db-sample-schemas
*
*****************************************************************************/
var oracledb = require('oracledb');
var dbConfig = require('./dbconfig.js');
oracledb.getConnection(
{
user : dbConfig.user,
password : dbConfig.password,
connectString : dbConfig.connectString
})
.then(function(connection) {
return connection.execute(
"SELECT department_id, department_name " +
"FROM departments " +
"WHERE department_id = :did",
[180]
)
.then(function(result) {
console.log(result.metaData);
console.log(result.rows);
return connection.release();
})
.catch(function(err) {
console.log(err.message);
return connection.release();
});
})
.catch(function(err) {
console.error(err.message);
});

View File

@ -19,7 +19,9 @@
* refcursor.js * refcursor.js
* *
* DESCRIPTION * DESCRIPTION
* Shows using a Result Set to fetch rows from a REF CURSOR * Shows using a ResultSet to fetch rows from a REF CURSOR using getRows().
* Streaming is also possible (this is not shown).
*
* Uses Oracle's sample HR schema. * Uses Oracle's sample HR schema.
* Use demo.sql to create the required procedure or do: * Use demo.sql to create the required procedure or do:
* *

View File

@ -0,0 +1,90 @@
/* 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.
*
* NAME
* refcursortoquerystream.js
*
* DESCRIPTION
* Converts a refcursor (returned from execute) to a query stream for an
* alternative means of processing instead of using resultSet.getRows().
*
* Scripts to create the HR schema can be found at:
* https://github.com/oracle/db-sample-schemas
*
*****************************************************************************/
var oracledb = require('oracledb');
var dbConfig = require('./dbconfig.js');
oracledb.getConnection(
{
user : dbConfig.user,
password : dbConfig.password,
connectString : dbConfig.connectString
},
function(err, connection) {
if (err) {
console.error(err.message);
return;
}
connection.execute(
"BEGIN"
+ " OPEN :cursor FOR SELECT department_id, department_name FROM departments;"
+ "END;",
{
cursor: { type: oracledb.CURSOR, dir : oracledb.BIND_OUT }
},
function(err, result) {
var cursor;
var queryStream;
if (err) {
console.error(err.message);
doRelease(connection);
return;
}
cursor = result.outBinds.cursor;
queryStream = cursor.toQueryStream();
queryStream.on('data', function (row) {
console.log(row);
});
queryStream.on('error', function (err) {
console.error(err.message);
doRelease(connection);
});
queryStream.on('end', function () {
doRelease(connection);
});
}
);
}
);
function doRelease(connection) {
connection.release(
function(err) {
if (err) {
console.error(err.message);
}
}
);
}

View File

@ -17,16 +17,44 @@
* *
*****************************************************************************/ *****************************************************************************/
'use strict';
var resultset = require('./resultset.js'); var resultset = require('./resultset.js');
var Stream = require('./resultset-read-stream'); var QueryStream = require('./querystream.js');
var nodbUtil = require('./util.js');
// The queryStream function is similar to execute except that it immediately // The queryStream function is similar to execute except that it immediately
// returns a readable stream. // returns a QueryStream.
function queryStream(sql, binding, options) { function queryStream(sql, binding, options) {
var self = this; var self = this;
var stream; var stream;
stream = new Stream(self, sql, binding, options); nodbUtil.assert(arguments.length > 0 && arguments.length < 4, 'NJS-009');
nodbUtil.assert(typeof sql === 'string', 'NJS-006', 1);
if (binding) {
nodbUtil.assert(nodbUtil.isObjectOrArray(binding), 'NJS-006', 2);
}
if (options) {
nodbUtil.assert(nodbUtil.isObject(options), 'NJS-006', 3);
}
binding = binding || [];
options = options || {};
options.resultSet = true;
stream = new QueryStream(null, self._oracledb);
self._execute(sql, binding, options, function(err, result) {
if (err) {
stream._open(err, null);
} else {
resultset.extend(result.resultSet, self._oracledb);
stream._open(null, result.resultSet);
}
});
return stream; return stream;
} }
@ -39,24 +67,45 @@ function execute(a1, a2, a3, a4) {
var executeCb; var executeCb;
var custExecuteCb; var custExecuteCb;
// Added this check so that node doesn't hang if no arguments are passed. nodbUtil.assert(arguments.length > 1 && arguments.length < 5, 'NJS-009');
if (arguments.length < 2 || arguments.length > 4) { nodbUtil.assert(typeof a1 === 'string', 'NJS-006', 1);
if (arguments.length && typeof arguments[arguments.length - 1] === 'function') {
arguments[arguments.length - 1](new Error('NJS-009: invalid number of parameters')); switch (arguments.length) {
return; case 2:
} else { nodbUtil.assert(typeof a2 === 'function', 'NJS-006', 2);
throw new Error('NJS-009: invalid number of parameters'); break;
} case 3:
nodbUtil.assert(nodbUtil.isObjectOrArray(a2), 'NJS-006', 2);
nodbUtil.assert(typeof a3 === 'function', 'NJS-006', 3);
break;
case 4:
nodbUtil.assert(nodbUtil.isObjectOrArray(a2), 'NJS-006', 2);
nodbUtil.assert(nodbUtil.isObject(a3), 'NJS-006', 3);
nodbUtil.assert(typeof a4 === 'function', 'NJS-006', 4);
break;
} }
custExecuteCb = function(err, result) { custExecuteCb = function(err, result) {
var outBindsKeys;
var outBindsIdx;
if (err) { if (err) {
executeCb(err); executeCb(err);
return; return;
} }
// Need to extend resultsets which may come from either the query results
// or outBinds.
if (result.resultSet) { if (result.resultSet) {
resultset.extend(result.resultSet); resultset.extend(result.resultSet, self._oracledb);
} else if (result.outBinds) {
outBindsKeys = Object.keys(result.outBinds);
for (outBindsIdx = 0; outBindsIdx < outBindsKeys.length; outBindsIdx += 1) {
if (result.outBinds[outBindsKeys[outBindsIdx]] instanceof self._oracledb.ResultSet) {
resultset.extend(result.outBinds[outBindsKeys[outBindsIdx]], self._oracledb);
}
}
} }
executeCb(null, result); executeCb(null, result);
@ -79,16 +128,22 @@ function execute(a1, a2, a3, a4) {
} }
// This commit function is just a place holder to allow for easier extension later. // This commit function is just a place holder to allow for easier extension later.
function commit() { function commit(commitCb) {
var self = this; var self = this;
nodbUtil.assert(arguments.length === 1, 'NJS-009');
nodbUtil.assert(typeof commitCb === 'function', 'NJS-006', 1);
self._commit.apply(self, arguments); self._commit.apply(self, arguments);
} }
// This rollback function is just a place holder to allow for easier extension later. // This rollback function is just a place holder to allow for easier extension later.
function rollback() { function rollback(rollbackCb) {
var self = this; var self = this;
nodbUtil.assert(arguments.length === 1, 'NJS-009');
nodbUtil.assert(typeof rollbackCb === 'function', 'NJS-006', 1);
self._rollback.apply(self, arguments); self._rollback.apply(self, arguments);
} }
@ -99,23 +154,31 @@ function rollback() {
function release(releaseCb) { function release(releaseCb) {
var self = this; var self = this;
// _pool will only exist on connections obtained from a pool. nodbUtil.assert(arguments.length === 1, 'NJS-009');
if (self._pool && self._pool.queueRequests !== false) { nodbUtil.assert(typeof releaseCb === 'function', 'NJS-006', 1);
self._release(function(err) {
releaseCb(err);
self._release(function(err) {
releaseCb(err);
// pool will only exist for connections obtained from a pool.
if (self._pool && self._pool.queueRequests !== false) {
self._pool._onConnectionRelease(); self._pool._onConnectionRelease();
}); }
} else {
self._release(releaseCb); // Need to maintain a reference to the connection instance to ensure that the
} // garbage collector doesn't destroy it too soon.
self = undefined;
});
} }
// This release function is just a place holder to allow for easier extension later. // This release function is just a place holder to allow for easier extension later.
// It's attached to the module as break is a reserved word. // It's attached to the module as break is a reserved word.
module.break = function() { module.break = function(breakCb) {
var self = this; var self = this;
nodbUtil.assert(arguments.length === 1, 'NJS-009');
nodbUtil.assert(typeof breakCb === 'function', 'NJS-006', 1);
self._break.apply(self, arguments); self._break.apply(self, arguments);
}; };
@ -143,7 +206,7 @@ function extend(conn, oracledb, pool) {
writable: true writable: true
}, },
execute: { execute: {
value: execute, value: nodbUtil.promisify(execute),
enumerable: true, enumerable: true,
writable: true writable: true
}, },
@ -151,7 +214,7 @@ function extend(conn, oracledb, pool) {
value: conn.commit value: conn.commit
}, },
commit: { commit: {
value: commit, value: nodbUtil.promisify(commit),
enumerable: true, enumerable: true,
writable: true writable: true
}, },
@ -159,7 +222,7 @@ function extend(conn, oracledb, pool) {
value: conn.rollback value: conn.rollback
}, },
rollback: { rollback: {
value: rollback, value: nodbUtil.promisify(rollback),
enumerable: true, enumerable: true,
writable: true writable: true
}, },
@ -167,7 +230,12 @@ function extend(conn, oracledb, pool) {
value: conn.release value: conn.release
}, },
release: { release: {
value: release, value: nodbUtil.promisify(release),
enumerable: true,
writable: true
},
close: { // alias for release
value: nodbUtil.promisify(release),
enumerable: true, enumerable: true,
writable: true writable: true
}, },
@ -175,7 +243,7 @@ function extend(conn, oracledb, pool) {
value: conn.break value: conn.break
}, },
break: { break: {
value: module.break, value: nodbUtil.promisify(module.break),
enumerable: true, enumerable: true,
writable: true writable: true
} }

View File

@ -17,6 +17,8 @@
* *
*****************************************************************************/ *****************************************************************************/
'use strict';
var Duplex = require('stream').Duplex; var Duplex = require('stream').Duplex;
var util = require('util'); var util = require('util');

View File

@ -17,11 +17,14 @@
* *
*****************************************************************************/ *****************************************************************************/
'use strict';
var oracledbCLib; var oracledbCLib;
var oracledbInst; var oracledbInst;
var Lob = require('./lob.js').Lob; var Lob = require('./lob.js').Lob;
var pool = require('./pool.js'); var pool = require('./pool.js');
var connection = require('./connection.js'); var connection = require('./connection.js');
var nodbUtil = require('./util.js');
try { try {
oracledbCLib = require('../build/Release/oracledb'); oracledbCLib = require('../build/Release/oracledb');
@ -43,6 +46,10 @@ oracledbCLib.Oracledb.prototype.newLob = function(iLob) {
function createPool(poolAttrs, createPoolCb) { function createPool(poolAttrs, createPoolCb) {
var self = this; var self = this;
nodbUtil.assert(arguments.length === 2, 'NJS-009');
nodbUtil.assert(nodbUtil.isObject(poolAttrs), 'NJS-006', 1);
nodbUtil.assert(typeof createPoolCb === 'function', 'NJS-006', 2);
self._createPool(poolAttrs, function(err, poolInst) { self._createPool(poolAttrs, function(err, poolInst) {
if (err) { if (err) {
createPoolCb(err); createPoolCb(err);
@ -61,6 +68,10 @@ function createPool(poolAttrs, createPoolCb) {
function getConnection(connAttrs, createConnectionCb) { function getConnection(connAttrs, createConnectionCb) {
var self = this; var self = this;
nodbUtil.assert(arguments.length === 2, 'NJS-009');
nodbUtil.assert(nodbUtil.isObject(connAttrs), 'NJS-006', 1);
nodbUtil.assert(typeof createConnectionCb === 'function', 'NJS-006', 2);
self._getConnection(connAttrs, function(err, connInst) { self._getConnection(connAttrs, function(err, connInst) {
if (err) { if (err) {
createConnectionCb(err); createConnectionCb(err);
@ -83,6 +94,9 @@ function extend(oracledb) {
Object.defineProperties( Object.defineProperties(
oracledb, oracledb,
{ {
_oracledb: { // Known to be used in util.js' promisify function.
value: oracledb
},
DEFAULT: { DEFAULT: {
value: 0, value: 0,
enumerable: true enumerable: true
@ -135,6 +149,11 @@ function extend(oracledb) {
value: 4002, value: 4002,
enumerable: true enumerable: true
}, },
Promise: {
value: global.Promise,
enumerable: true,
writable: true
},
Oracledb: { Oracledb: {
value: oracledbCLib.Oracledb, value: oracledbCLib.Oracledb,
enumerable: true enumerable: true
@ -169,7 +188,7 @@ function extend(oracledb) {
value: oracledb.createPool value: oracledb.createPool
}, },
createPool: { createPool: {
value: createPool, value: nodbUtil.promisify(createPool),
enumerable: true, enumerable: true,
writable: true writable: true
}, },
@ -177,7 +196,7 @@ function extend(oracledb) {
value: oracledb.getConnection value: oracledb.getConnection
}, },
getConnection: { getConnection: {
value: getConnection, value: nodbUtil.promisify(getConnection),
enumerable: true, enumerable: true,
writable: true writable: true
} }
@ -185,7 +204,7 @@ function extend(oracledb) {
); );
} }
oracledbInst = new oracledbCLib.Oracledb; oracledbInst = new oracledbCLib.Oracledb();
extend(oracledbInst); extend(oracledbInst);

View File

@ -17,7 +17,10 @@
* *
*****************************************************************************/ *****************************************************************************/
'use strict';
var connection = require('./connection.js'); var connection = require('./connection.js');
var nodbUtil = require('./util.js');
// completeConnectionRequest does the actual work of getting a connection from a // completeConnectionRequest does the actual work of getting a connection from a
// pool when queuing is enabled. It's abstracted out so it can be called from // pool when queuing is enabled. It's abstracted out so it can be called from
@ -119,7 +122,7 @@ function onRequestTimeout(timerIdx) {
self._connRequestQueue.splice(requestIndex, 1); self._connRequestQueue.splice(requestIndex, 1);
self._connRequestTimersMap[timerIdx] = null; self._connRequestTimersMap[timerIdx] = null;
payloadToDequeue.getConnectionCb(new Error('NJS-040: connection request timeout')); payloadToDequeue.getConnectionCb(new Error(nodbUtil.getErrorMessage('NJS-040')));
} }
} }
@ -134,6 +137,9 @@ function getConnection(getConnectionCb) {
var timeoutHandle; var timeoutHandle;
var timerIdx; var timerIdx;
nodbUtil.assert(arguments.length === 1, 'NJS-009');
nodbUtil.assert(typeof getConnectionCb === 'function', 'NJS-006', 1);
// Added this check because if the pool isn't valid and we reference self.poolMax // Added this check because if the pool isn't valid and we reference self.poolMax
// (which is a C layer getter) an error will be thrown. // (which is a C layer getter) an error will be thrown.
if (!self._isValid) { if (!self._isValid) {
@ -150,21 +156,21 @@ function getConnection(getConnectionCb) {
} }
if (self.queueRequests === false) { // queueing is disabled for pool if (self.queueRequests === false) { // queueing is disabled for pool
if (self._enableStats) { self._getConnection(function(err, connInst) {
self._getConnection(function(err, connInst) { if (err) {
if (err) { if (self._enableStats) {
self._totalFailedRequests += 1; self._totalFailedRequests += 1;
getConnectionCb(err);
return;
} }
getConnectionCb(null, connInst); getConnectionCb(err);
});
} else { return;
self._getConnection(getConnectionCb); }
}
connection.extend(connInst, self._oracledb, self);
getConnectionCb(null, connInst);
});
} else if (self._connectionsOut < self.poolMax) { // queueing enabled, but not needed } else if (self._connectionsOut < self.poolMax) { // queueing enabled, but not needed
completeConnectionRequest.call(self, getConnectionCb); completeConnectionRequest.call(self, getConnectionCb);
} else { // need to queue the request } else { // need to queue the request
@ -204,6 +210,9 @@ function getConnection(getConnectionCb) {
function terminate(terminateCb) { function terminate(terminateCb) {
var self = this; var self = this;
nodbUtil.assert(arguments.length === 1, 'NJS-009');
nodbUtil.assert(typeof terminateCb === 'function', 'NJS-006', 1);
self._terminate(function(err) { self._terminate(function(err) {
if (!err) { if (!err) {
self._isValid = false; self._isValid = false;
@ -290,13 +299,23 @@ function extend(pool, poolAttrs, oracledb) {
_oracledb: { // storing a reference to the base instance to avoid circular references with require _oracledb: { // storing a reference to the base instance to avoid circular references with require
value: oracledb value: oracledb
}, },
queueRequests: { queueRequests: { // true will queue requests when conn pool is maxed out
value: queueRequests, // true will queue requests when conn pool is maxed out enumerable: true,
enumerable: true get: function() {
return queueRequests;
},
set: function() {
throw new Error(nodbUtil.getErrorMessage('NJS-014', 'queueRequests'));
}
}, },
queueTimeout: { queueTimeout: { // milliseconds a connection request can spend in queue before being failed
value: queueTimeout, // milliseconds a connection request can spend in queue before being failed enumerable: true,
enumerable: true get: function() {
return queueTimeout;
},
set: function() {
throw new Error(nodbUtil.getErrorMessage('NJS-014', 'queueTimeout'));
}
}, },
_isValid: { // used to ensure operations are not done after terminate _isValid: { // used to ensure operations are not done after terminate
value: true, value: true,
@ -373,7 +392,7 @@ function extend(pool, poolAttrs, oracledb) {
value: pool.getConnection value: pool.getConnection
}, },
getConnection: { getConnection: {
value: getConnection, value: nodbUtil.promisify(getConnection),
enumerable: true, enumerable: true,
writable: true writable: true
}, },
@ -381,7 +400,12 @@ function extend(pool, poolAttrs, oracledb) {
value: pool.terminate value: pool.terminate
}, },
terminate: { terminate: {
value: terminate, value: nodbUtil.promisify(terminate),
enumerable: true,
writable: true
},
close: { // alias for terminate
value: nodbUtil.promisify(terminate),
enumerable: true, enumerable: true,
writable: true writable: true
} }

226
lib/querystream.js Normal file
View File

@ -0,0 +1,226 @@
/* 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.")
*
* 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.
*
*****************************************************************************/
'use strict';
var util = require('util');
var Readable = require('stream').Readable;
// This class was originally based on https://github.com/sagiegurari/simple-oracledb/blob/master/lib/resultset-read-stream.js
function QueryStream(resultSet, oracledb) {
var self = this;
Object.defineProperties(
self,
{
_oracledb: { // storing a reference to the base instance to avoid circular references with require
value: oracledb
},
_resultSet: {
value: resultSet,
writable: true
},
_fetchedRows: { // a local cache of rows fetched from a call to resultSet.getRows
value: [],
writable: true
},
_fetchedAllRows: { // used to avoid an unnecessary call to resultSet.getRows
value: false,
writable: true
},
_fetching: { // used to serialize method calls on the resultset
value: false,
writable: true
},
_closed: { // used to track that the stream is closed
value: false,
writable: true
}
}
);
Readable.call(self, {
objectMode: true
});
if (self._resultSet) { // If true, no need to invoke _open, we are ready to go.
self.emit('metadata', self._resultSet.metaData);
self.emit('open');
}
}
util.inherits(QueryStream, Readable);
// The _open method is only meant to be called when a QueryStream is created
// but not passed in the resultSet during initialization. In those cases the
// QueryStream object will have been returned immediately and the _open method
// will be called later to pass the resultset (or error getting the resultset)
// along.
QueryStream.prototype._open = function(err, rs) {
var self = this;
if (err) {
self.emit('error', err);
return;
}
self._resultSet = rs;
self.emit('metadata', self._resultSet.metaData);
// Trigger the event listener that may have been added in _read now that the
// resultset is ready.
self.emit('open');
};
// The stream _read implementation which fetches the next row from the resultset.
QueryStream.prototype._read = function () {
var self = this;
var fetchCount;
if (!self._resultSet) {
// Still waiting on the resultset, add an event listener to retry when ready
return self.once('open', function() {
self._read();
});
}
if (self._closed) {
return;
}
if (self._fetchedRows.length) {
// We have rows already fetched that need to be pushed
self.push(self._fetchedRows.shift());
} else if (self._fetchedAllRows) {
// Calling the C layer close directly to avoid assertions on the public method
self._resultSet._close(function(err) {
if (err) {
self.emit('error', err);
return;
}
// Signal the end of the stream
self.push(null);
});
} else {
// Using _fetching to indicate that the resultset is working to avoid potential
// errors related to close w/conncurrent operations on resultsets
self._fetching = true;
fetchCount = self._oracledb.maxRows || 100;
// Calling the C layer getRows directly to avoid assertions on the public method
self._resultSet._getRows(fetchCount, function(err, rows) {
if (err) {
// We'll return the error from getRows, but first try to close the resultSet.
self._resultSet.close(function() {});
self.emit('error', err);
return;
}
self._fetching = false;
// Close may have been called while the resultset was fetching.
if (self._closed) {
// Trigger the event listener that may have been added in close now that
// the resultset has finished working.
self.emit('_doneFetching');
return;
}
self._fetchedRows = rows;
if (self._fetchedRows.length < fetchCount) {
self._fetchedAllRows = true;
}
if (self._fetchedRows.length) {
self.push(self._fetchedRows.shift());
} else { // No more rows to fetch
// Calling the C layer close directly to avoid assertions on the public method
self._resultSet._close(function(err) {
if (err) {
self.emit('error', err);
return;
}
// Signal the end of the stream
self.push(null);
});
}
});
}
};
// The close method is not a standard method on stream instances in Node.js but
// it was added to provide developers with a means of stopping the flow of data
// and closing the stream without having to allow the entire resultset to finish
// streaming.
function close(callback) {
var self = this;
// Setting _closed early to prevent _read invocations from being processed and
// to allow _doneFetching to be emitted if needed.
self._closed = true;
// We can't close the resultset if it's currently fetching. Add a listener
// to call close when the resulset is done fetching.
if (self._fetching) {
self.once('_doneFetching', function() {
self._close(callback);
});
return;
}
if (callback) {
self.once('close', callback);
}
// It's possible for close to be called very early, even before the resultset
// has been set via _open (if needed).
if (!self._resultSet) {
self.emit('close');
} else {
// Calling the C layer close directly to avoid assertions on the public method
self._resultSet._close(function(err) {
if (err) {
self.emit('error', err);
return;
}
self.emit('close');
});
}
}
// Exposing close as a private method for now.
Object.defineProperty(
QueryStream.prototype,
'_close',
{
value: close,
writable: true
}
);
module.exports = QueryStream;

View File

@ -1,121 +0,0 @@
/* 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.")
*
* 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.
*
*****************************************************************************/
var util = require('util');
var stream = require('stream');
var Readable = stream.Readable;
var resultset = require('./resultset.js');
// A node.js read stream for resultsets (based on https://github.com/sagiegurari/simple-oracledb/blob/master/lib/resultset-read-stream.js).
function ResultSetReadStream(conn, sql, binding, options) {
var self = this;
binding = binding || [];
options = options || {};
options.resultSet = true;
self.streamNumRows = conn._oracledb.maxRows || 100;
Readable.call(self, {
objectMode: true
});
Object.defineProperty(self, 'nextRow', {
// Sets the nextRow value.
set: function (nextRow) {
self.next = nextRow;
if (self.inRead) {
self._read();
}
}
});
conn._execute(sql, binding, options, function(err, result) {
self._onExecuteDone(err, result);
});
}
util.inherits(ResultSetReadStream, Readable);
// The stream _read implementation which fetches the next row from the resultset.
ResultSetReadStream.prototype._read = function () {
var self = this;
self.inRead = false;
if (self.next) {
self.next(function onNextRowRead(error, data) {
if (error) {
self.emit('error', error);
} else if (data) {
self.push(data);
} else {
self.push(null);
}
});
} else {
self.inRead = true;
}
};
ResultSetReadStream.prototype._onExecuteDone = function(err, result) {
var self = this;
if (err) {
self.nextRow = function emitError(streamCallback) {
streamCallback(err);
};
return;
}
resultset.extend(result.resultSet);
self.emit('metadata', result.resultSet.metaData);
var close = function (streamCallback, causeError) {
result.resultSet.close(function onClose(closeError) {
streamCallback(causeError || closeError);
});
};
var readRows;
self.nextRow = function fetchNextRow(streamCallback) {
if (readRows && readRows.length) {
streamCallback(null, readRows.shift());
} else {
result.resultSet.getRows(self.streamNumRows, function onRow(rowError, rows) {
if (rowError) {
close(streamCallback, rowError);
} else if ((!rows) || (!rows.length)) {
close(streamCallback);
} else {
readRows = rows;
streamCallback(null, readRows.shift());
}
});
}
};
};
module.exports = ResultSetReadStream;

View File

@ -17,41 +17,117 @@
* *
*****************************************************************************/ *****************************************************************************/
'use strict';
var QueryStream = require('./querystream.js');
var nodbUtil = require('./util.js');
// This close function is just a place holder to allow for easier extension later. // This close function is just a place holder to allow for easier extension later.
function close() { function close(closeCb) {
var self = this; var self = this;
self._close.apply(self, arguments); nodbUtil.assert(arguments.length === 1, 'NJS-009');
nodbUtil.assert(typeof closeCb === 'function', 'NJS-006', 1);
if (self._convertedToStream) {
closeCb(new Error(nodbUtil.getErrorMessage('NJS-042')));
return;
}
self._processingStarted = true;
self._close(function(err) {
// Need to maintain a reference to the resultset instance to ensure that the
// garbage collector doesn't destroy it too soon.
self = undefined;
closeCb(err);
});
} }
// This getRow function is just a place holder to allow for easier extension later. // This getRow function is just a place holder to allow for easier extension later.
function getRow() { function getRow(getRowCb) {
var self = this; var self = this;
nodbUtil.assert(arguments.length === 1, 'NJS-009');
nodbUtil.assert(typeof getRowCb === 'function', 'NJS-006', 1);
if (self._convertedToStream) {
getRowCb(new Error(nodbUtil.getErrorMessage('NJS-042')));
return;
}
self._processingStarted = true;
self._getRow.apply(self, arguments); self._getRow.apply(self, arguments);
} }
// This getRows function is just a place holder to allow for easier extension later. // This getRows function is just a place holder to allow for easier extension later.
function getRows() { function getRows(numRows, getRowsCb) {
var self = this; var self = this;
nodbUtil.assert(arguments.length === 2, 'NJS-009');
nodbUtil.assert(typeof numRows === 'number', 'NJS-006', 1);
nodbUtil.assert(typeof getRowsCb === 'function', 'NJS-006', 2);
if (self._convertedToStream) {
getRowsCb(new Error(nodbUtil.getErrorMessage('NJS-042')));
return;
}
self._processingStarted = true;
self._getRows.apply(self, arguments); self._getRows.apply(self, arguments);
} }
function toQueryStream() {
var self = this;
var stream;
nodbUtil.assert(arguments.length === 0, 'NJS-009');
if (self._processingStarted) {
throw new Error(nodbUtil.getErrorMessage('NJS-041'));
return;
}
if (self._convertedToStream) {
throw new Error(nodbUtil.getErrorMessage('NJS-043'));
return;
}
self._convertedToStream = true;
stream = new QueryStream(self, self._oracledb);
return stream;
}
// The extend method is used to extend the ResultSet instance from the C layer with // The extend method is used to extend the ResultSet instance from the C layer with
// custom properties and method overrides. References to the original methods are // custom properties and method overrides. References to the original methods are
// maintained so they can be invoked by the overriding method at the right time. // maintained so they can be invoked by the overriding method at the right time.
function extend(resultSet) { function extend(resultSet, oracledb) {
// Using Object.defineProperties to add properties to the ResultSet instance with // Using Object.defineProperties to add properties to the ResultSet instance with
// special properties, such as enumerable but not writable. // special properties, such as enumerable but not writable.
Object.defineProperties( Object.defineProperties(
resultSet, resultSet,
{ {
_oracledb: { // storing a reference to the base instance to avoid circular references with require
value: oracledb
},
_processingStarted: { // used to prevent conversion to stream after invoking methods
value: false,
writable: true
},
_convertedToStream: { // used to prevent invoking methods after conversion to stream
value: false,
writable: true
},
_close: { _close: {
value: resultSet.close value: resultSet.close
}, },
close: { close: {
value: close, value: nodbUtil.promisify(close),
enumerable: true, enumerable: true,
writable: true writable: true
}, },
@ -59,7 +135,7 @@ function extend(resultSet) {
value: resultSet.getRow value: resultSet.getRow
}, },
getRow: { getRow: {
value: getRow, value: nodbUtil.promisify(getRow),
enumerable: true, enumerable: true,
writable: true writable: true
}, },
@ -67,7 +143,12 @@ function extend(resultSet) {
value: resultSet.getRows value: resultSet.getRows
}, },
getRows: { getRows: {
value: getRows, value: nodbUtil.promisify(getRows),
enumerable: true,
writable: true
},
toQueryStream: {
value: toQueryStream,
enumerable: true, enumerable: true,
writable: true writable: true
} }

122
lib/util.js Normal file
View File

@ -0,0 +1,122 @@
/* 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.
*
*****************************************************************************/
var util = require('util');
// errorMessages is a temporary duplication of error messages defined in the C
// layer that will be removed once a function to fetch from the C layer is added.
var errorMessages = {
'NJS-005': 'NJS-005: invalid value for parameter %d',
'NJS-006': 'NJS-006: invalid type for parameter %d',
'NJS-009': 'NJS-009: invalid number of parameters',
'NJS-014': 'NJS-014: %s is a read-only property',
'NJS-037': 'NJS-037: incompatible type of value provided',
'NJS-040': 'NJS-040: connection request timeout',
'NJS-041': 'NJS-041: cannot convert ResultSet to QueryStream after invoking methods',
'NJS-042': 'NJS-042: cannot invoke ResultSet methods after converting to QueryStream',
'NJS-043': "NJS-043: ResultSet already converted to QueryStream"
};
// getErrorMessage is used to get and format error messages to make throwing errors
// a little more convenient.
function getErrorMessage(errorCode, messageArg1) {
if (messageArg1) {
return util.format(errorMessages[errorCode], messageArg1);
} else {
return util.format(errorMessages[errorCode]);
}
}
module.exports.getErrorMessage = getErrorMessage;
// assert it typically used in the beginning of public functions to assert preconditions
// for the function to execute. Most commonly it's used to validate arguments lenght
// and types and throw an error if they don't match what is expected.
function assert(condition, errorCode, messageArg1) {
if (!condition) {
throw new Error(getErrorMessage(errorCode, messageArg1));
}
}
module.exports.assert = assert;
// The promisify function is used to wrap async methods to add optional promise
// support. If the last parameter passed to a method is a function, then it is
// assumed that the callback pattern is being used and the method is invoked as
// usual. Otherwise a promise is returned and later resolved or rejected based on
// the return of the method.
function promisify(func) {
return function() {
var self = this;
var args;
// This/self could refer to the base class instance, pool, connection, etc. All
// class instances have a private reference to the base class for convenience.
if (!self._oracledb.Promise || typeof arguments[arguments.length - 1] === 'function') {
return func.apply(self, arguments);
} else {
// Converting to an array so we can extend it later with a custom callback
args = Array.prototype.slice.call(arguments);
return new self._oracledb.Promise(function(resolve, reject) {
var errorCode;
try {
args[args.length] = function(err, result) {
if (err) {
reject(err);
} else {
resolve(result);
}
};
func.apply(self, args);
} catch (err) {
errorCode = err.message.substr(0, 7);
// Check for invalid number or type of parameter(s) as they should be
// eagerly thrown.
if (errorCode === 'NJS-009' || errorCode === 'NJS-006') {
// Throwing the error outside of the promise wrapper so that its not
// swallowed up as a rejection.
process.nextTick(function() {
throw err;
});
} else {
reject(err);
}
}
});
}
};
};
module.exports.promisify = promisify;
function isObject(value) {
return value !== null && typeof value === 'object';
}
module.exports.isObject = isObject;
function isObjectOrArray(value) {
return (value !== null && typeof value === 'object') || Array.isArray(value);
}
module.exports.isObjectOrArray = isObjectOrArray;

View File

@ -1,6 +1,6 @@
{ {
"name": "oracledb", "name": "oracledb",
"version": "1.8.0", "version": "1.9.0",
"description": "Oracle Database driver by Oracle Corp.", "description": "Oracle Database driver by Oracle Corp.",
"license": "Apache-2.0", "license": "Apache-2.0",
"homepage": "http://www.oracle.com/technetwork/database/database-technologies/scripting-languages/node_js/", "homepage": "http://www.oracle.com/technetwork/database/database-technologies/scripting-languages/node_js/",

View File

@ -51,9 +51,9 @@ static const char *errMsg[] =
"NJS-013: invalid bind direction", "NJS-013: invalid bind direction",
"NJS-014: %s is a read-only property", "NJS-014: %s is a read-only property",
"NJS-016: buffer is too small for OUT binds", "NJS-016: buffer is too small for OUT binds",
"NJS-017: concurrent operations on resultSet are not allowed", "NJS-017: concurrent operations on ResultSet are not allowed",
"NJS-018: invalid result set", "NJS-018: invalid result set",
"NJS-019: resultSet cannot be returned for non-query statements", "NJS-019: ResultSet cannot be returned for non-query statements",
"NJS-020: empty array was specified to fetch values as string", "NJS-020: empty array was specified to fetch values as string",
"NJS-021: invalid type for conversion specified", "NJS-021: invalid type for conversion specified",
"NJS-022: invalid LOB", "NJS-022: invalid LOB",
@ -63,13 +63,10 @@ static const char *errMsg[] =
"NJS-026: maxRows must be greater than zero", "NJS-026: maxRows must be greater than zero",
"NJS-027: unexpected SQL parsing error", "NJS-027: unexpected SQL parsing error",
"NJS-028: raw database type is not supported with DML Returning statements", "NJS-028: raw database type is not supported with DML Returning statements",
"NJS-029: Invalid object from javascript", "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",
" progress", "NJS-031: connection cannot be released because ResultSet operations are in progress",
"NJS-031: connection cannot be released because ResultSet operations are" "NJS-032: connection cannot be released because a database call is in progress",
" in progress",
"NJS-032: connection cannot be released because a database call is in"
" progress",
"NJS-033: an internal error occurred. [%s][%s]", "NJS-033: an internal error occurred. [%s][%s]",
"NJS-034: data type is unsupported for array bind", "NJS-034: data type is unsupported for array bind",
"NJS-035: maxArraySize is required for IN OUT array bind", "NJS-035: maxArraySize is required for IN OUT array bind",
@ -78,6 +75,9 @@ static const char *errMsg[] =
"NJS-038: maxArraySize value should be greater than 0", "NJS-038: maxArraySize value should be greater than 0",
"NJS-039: empty array is not allowed for IN bind", "NJS-039: empty array is not allowed for IN bind",
"NJS-040: connection request timeout", "NJS-040: connection request timeout",
"NJS-041: cannot convert ResultSet to QueryStream after invoking methods",
"NJS-042: cannot invoke ResultSet methods after converting to QueryStream",
"NJS-043: ResultSet already converted to QueryStream",
}; };
string NJSMessages::getErrorMsg ( NJSErrorType err, ... ) string NJSMessages::getErrorMsg ( NJSErrorType err, ... )

View File

@ -74,6 +74,9 @@ typedef enum
errInvalidValueArrayBind, errInvalidValueArrayBind,
errEmptyArray, errEmptyArray,
errConnRequestTimeout, errConnRequestTimeout,
errCannotConvertRsToStream,
errCannotInvokeRsMethods,
errResultSetAlreadyConverted,
// New ones should be added here // New ones should be added here

View File

@ -69,7 +69,7 @@ using namespace v8;
/* Keep the version in sync with package.json */ /* Keep the version in sync with package.json */
#define NJS_NODE_ORACLEDB_MAJOR 1 #define NJS_NODE_ORACLEDB_MAJOR 1
#define NJS_NODE_ORACLEDB_MINOR 8 #define NJS_NODE_ORACLEDB_MINOR 9
#define NJS_NODE_ORACLEDB_PATCH 0 #define NJS_NODE_ORACLEDB_PATCH 0
/* Used for Oracledb.version */ /* Used for Oracledb.version */

View File

@ -718,7 +718,6 @@ describe('1. connection.js', function(){
describe('1.6 Testing parameter assertions', function() { describe('1.6 Testing parameter assertions', function() {
var conn1; var conn1;
var sql = 'select 1 from dual';
beforeEach('get connection ready', function(done) { beforeEach('get connection ready', function(done) {
oracledb.getConnection(credential, function(err, conn) { oracledb.getConnection(credential, function(err, conn) {
@ -735,37 +734,173 @@ describe('1. connection.js', function(){
}); });
}); });
it('1.6.1 too few params without a callback should throw error', function(done) { it('1.6.1 too few params should throw an error', function(done) {
// This test returns a promise because the last parameter to execute is not
// a function. Normally, errors thrown in a promise would be directed to
// to a catch handler. In the case of an "accidental promise" the error
// could go undetected. Because of this, the promisify function in util.js
// uses process.nextTick to throw invalid number or type of params (NJS-009
// and NJS-006). This test has been updated to account for this behavior.
var promiseSupportEnabled = oracledb.Promise !== undefined;
var listeners = process.listeners('uncaughtException');
if (promiseSupportEnabled) {
process.removeAllListeners('uncaughtException');
process.once('uncaughtException', function(err) {
listeners.forEach(function(listener) {
process.on('uncaughtException', listener);
});
should.exist(err);
done();
});
}
// Using try catch for instances where promises are not supported or have
// been disabled by setting oracledb.Promise to something falsey.
try { try {
conn1.execute(sql); conn1.execute();
} catch (err) { } catch (err) {
if (promiseSupportEnabled) {
listeners.forEach(function(listener) {
process.on('uncaughtException', listener);
});
}
should.exist(err); should.exist(err);
done(); done();
} }
}); });
it('1.6.2 too few params with a callback should pass error in callback', function(done) { it('1.6.2 too many params should throw error', function(done) {
conn1.execute(function(err, result) { // This test returns a promise because the last parameter to execute is not
should.exist(err); // a function. Normally, errors thrown in a promise would be directed to
done(); // to a catch handler. In the case of an "accidental promise" the error
}); // could go undetected. Because of this, the promisify function in util.js
}); // uses process.nextTick to throw invalid number or type of params (NJS-009
// and NJS-006). This test has been updated to account for this behavior.
var promiseSupportEnabled = oracledb.Promise !== undefined;
var listeners = process.listeners('uncaughtException');
it('1.6.3 too many params without a callback should throw error', function(done) { if (promiseSupportEnabled) {
process.removeAllListeners('uncaughtException');
process.once('uncaughtException', function(err) {
listeners.forEach(function(listener) {
process.on('uncaughtException', listener);
});
should.exist(err);
done();
});
}
// Using try catch for instances where promises are not supported or have
// been disabled by setting oracledb.Promise to something falsey.
try { try {
conn1.execute(1, 2, 3, 4, 5); conn1.execute(1, 2, 3, 4, 5);
} catch (err) {
if (promiseSupportEnabled) {
listeners.forEach(function(listener) {
process.on('uncaughtException', listener);
});
}
should.exist(err);
done();
}
});
it('1.6.3 wrong type for param 1 should throw an error', function(done) {
// Don't need to listen for unhandledRejection because a promise will not
// be returned as the last param is a function.
try {
conn1.execute(1, function() {});
} catch (err) { } catch (err) {
should.exist(err); should.exist(err);
done(); done();
} }
}); });
it('1.6.4 too many params with a callback should pass error in callback', function(done) { it('1.6.4 wrong type for param 2 should throw an error', function(done) {
conn1.execute(1, 2, 3, 4, function(err, result) { // This test returns a promise because the last parameter to execute is not
// a function. Normally, errors thrown in a promise would be directed to
// to a catch handler. In the case of an "accidental promise" the error
// could go undetected. Because of this, the promisify function in util.js
// uses process.nextTick to throw invalid number or type of params (NJS-009
// and NJS-006). This test has been updated to account for this behavior.
var promiseSupportEnabled = oracledb.Promise !== undefined;
var listeners = process.listeners('uncaughtException');
if (promiseSupportEnabled) {
process.removeAllListeners('uncaughtException');
process.once('uncaughtException', function(err) {
listeners.forEach(function(listener) {
process.on('uncaughtException', listener);
});
should.exist(err);
done();
});
}
// Using try catch for instances where promises are not supported or have
// been disabled by setting oracledb.Promise to something falsey.
try {
conn1.execute('select 1 from dual', 1);
} catch (err) {
if (promiseSupportEnabled) {
listeners.forEach(function(listener) {
process.on('uncaughtException', listener);
});
}
should.exist(err);
done();
}
});
it('1.6.5 wrong type for param 3 should throw an error', function(done) {
// Don't need to listen for unhandledRejection because a promise will not
// be returned as the last param is a function.
try {
conn1.execute('select 1 from dual', 1, function() {});
} catch (err) {
should.exist(err); should.exist(err);
done(); done();
}
});
it('1.6.6 wrong type for param 4 should throw an error', function(done) {
// Don't need to listen for unhandledRejection because a promise will not
// be returned as the last param is a function.
try {
conn1.execute('select 1 from dual', {}, 1, function() {});
} catch (err) {
should.exist(err);
done();
}
});
});
describe('1.7 Close method', function() {
it('1.7.1 close can be used as an alternative to release', function(done) {
oracledb.getConnection(credential, function(err, conn) {
should.not.exist(err);
conn.close(function(err) {
should.not.exist(err);
done();
});
}); });
}); });
}) });
}) });

View File

@ -18,6 +18,15 @@
1.5 Testing commit() & rollback() functions 1.5 Testing commit() & rollback() functions
1.5.1 commit() function works well 1.5.1 commit() function works well
1.5.2 rollback() function works well 1.5.2 rollback() function works well
1.6 Testing parameter assertions
1.6.1 too few params should throw an error
1.6.2 too many params should throw error
1.6.3 wrong type for param 1 should throw an error
1.6.4 wrong type for param 2 should throw an error
1.6.5 wrong type for param 3 should throw an error
1.6.6 wrong type for param 4 should throw an error
1.7 Close method
1.7.1 close can be used as an alternative to release
2. pool.js 2. pool.js
2.1 default values 2.1 default values
@ -57,6 +66,8 @@
2.8.4 does not generate NJS-040 if request is queued for less time than queueTimeout 2.8.4 does not generate NJS-040 if request is queued for less time than queueTimeout
2.9 connection request queue (_enableStats & _logStats functionality)_logStats must be called prior to terminating pool. 2.9 connection request queue (_enableStats & _logStats functionality)_logStats must be called prior to terminating pool.
2.9.1 works after the pool as been terminated 2.9.1 works after the pool as been terminated
2.10 Close method
2.10.1 close can be used as an alternative to release
3. examples.js 3. examples.js
@ -227,7 +238,7 @@
12.7.2 maxRows option is ignored with REF Cursor 12.7.2 maxRows option is ignored with REF Cursor
13. stream.js 13. stream.js
13.1 Testing ResultSet stream 13.1 Testing QueryStream
13.1.1 stream results for oracle connection 13.1.1 stream results for oracle connection
13.1.2 stream results for oracle connection (outFormat: oracledb.OBJECT) 13.1.2 stream results for oracle connection (outFormat: oracledb.OBJECT)
13.1.3 errors in query 13.1.3 errors in query
@ -239,6 +250,13 @@
13.1.9 Read CLOBs after stream close 13.1.9 Read CLOBs after stream close
13.1.10 meta data 13.1.10 meta data
13.1.11 stream stress test 13.1.11 stream stress test
13.2 Testing QueryStream._close
13.2.1 should be able to stop the stream early with _close
13.2.2 should be able to stop the stream before any data
13.2.3 should invoke an optional callback passed to _close
13.3 Testing QueryStream\'s maxRows control
13.3.1 should use oracledb.maxRows for fetching
13.3.2 should default to 100 if oracledb.maxRows is falsey
14. stream2.js 14. stream2.js
14.1 Bind by position and return an array 14.1 Bind by position and return an array
@ -249,7 +267,32 @@
14.6 maxRows option is ignored as expect 14.6 maxRows option is ignored as expect
14.7 Negative - queryStream() has no parameters 14.7 Negative - queryStream() has no parameters
14.8 Negative - give invalid SQL as first parameter 14.8 Negative - give invalid SQL as first parameter
14.9 Negatvie - give non-query SQL 14.9 Negative - give non-query SQL
15. resultsetToQueryStream.js
15.1 Testing ResultSet.toQueryStream
15.1.1 should allow resultsets to be converted to streams
15.2 Testing ResultSet/QueryStream conversion errors
15.2.1 should prevent conversion to stream after getRow is invoked
15.2.2 should prevent conversion to stream after getRows is invoked
15.2.3 should prevent conversion to stream after close is invoked
15.2.4 should prevent invoking getRow after conversion to stream
15.2.5 should prevent invoking getRows after conversion to stream
15.2.6 should prevent invoking close after conversion to stream
15.2.7 should prevent calling toQueryStream more than once
16. promises.js
16.1 returns a promise from oracledb.getConnection
16.2 returns a promise from oracledb.createPool
16.3 returns a promise from pool.terminate
16.4 returns a promise from pool.getConnection
16.5 returns a promise from connection.release
16.6 returns a promise from connection.execute
16.7 returns a promise from connection.commit
16.8 returns a promise form connection.rollback
16.9 returns a promise from resultSet.close
16.10 returns a promise from resultSet.getRow
16.11 returns a promise from resultSet.getRows
21. datatypeAssist.js 21. datatypeAssist.js

View File

@ -640,12 +640,14 @@ describe('2. pool.js', function(){
); );
}); });
it('2.7.1 throws error if called after pool is terminated and a callback is not provided', function(done) { // Skipping this test because assertions were added to the JS layer for all
// public methods. This now throws NJS-009: invalid number of parameters.
it.skip('2.7.1 throws error if called after pool is terminated and a callback is not provided', function(done) {
pool1.terminate(function(err) { pool1.terminate(function(err) {
should.not.exist(err); should.not.exist(err);
try { try {
pool1.getConnection(1); pool1.getConnection();
} catch (err) { } catch (err) {
should.exist(err); should.exist(err);
(err.message).should.startWith('NJS-002: invalid pool'); (err.message).should.startWith('NJS-002: invalid pool');
@ -953,4 +955,30 @@ describe('2. pool.js', function(){
); );
}); });
}); });
})
describe('2.10 Close method', function(){
it('2.10.1 close can be used as an alternative to release', function(done) {
oracledb.createPool(
{
externalAuth : credential.externalAuth,
user : credential.user,
password : credential.password,
connectString : credential.connectString,
poolMin : 0,
poolMax : 1,
poolIncrement : 1,
poolTimeout : 1
},
function(err, pool){
should.not.exist(err);
pool.close(function(err) {
should.not.exist(err);
done();
});
}
);
});
});
});

339
test/promises.js Normal file
View File

@ -0,0 +1,339 @@
/* 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
* 16. promises.js
*
* DESCRIPTION
* Promise tests.
*
* 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');
// Need to skip these tests if Promises are not supported
var it = (oracledb.Promise) ? global.it : global.it.skip;
describe('16. promises.js', function(){
it('16.1 returns a promise from oracledb.getConnection', function(done) {
var promise = oracledb.getConnection(dbConfig);
promise.should.be.an.instanceof(oracledb.Promise);
promise
.then(function(conn) {
conn.should.be.ok;
conn.release(function(err) {
if (err)
return done(err);
else
return done();
});
})
.catch(function(err) {
return done(err);
});
})
it('16.2 returns a promise from oracledb.createPool', function(done) {
var promise = oracledb.createPool(dbConfig);
promise.should.be.an.instanceof(oracledb.Promise);
promise
.then(function(pool) {
pool.should.be.ok;
pool.terminate(function(err) {
if (err)
return done(err);
else
return done();
});
})
.catch(function(err) {
return done(err);
});
})
it('16.3 returns a promise from pool.terminate', function(done) {
oracledb.createPool(dbConfig)
.then(function(pool) {
pool.should.be.ok;
var promise = pool.terminate();
promise.should.be.an.instanceof(oracledb.Promise);
return promise;
})
.then(function() {
return done();
})
.catch(function(err) {
return done(err);
});
})
it('16.4 returns a promise from pool.getConnection', function(done) {
oracledb.createPool(dbConfig)
.then(function(pool) {
pool.should.be.ok;
var getConnPromise = pool.getConnection();
getConnPromise.should.be.an.instanceof(oracledb.Promise);
getConnPromise
.then(function(conn) {
conn.release(function(err) {
if (err) {
return done(err);
}
pool.terminate()
.then(function() {
return done();
})
.catch(function(err) {
return done(err);
});
});
});
})
.catch(function(err) {
return done(err);
});
})
it('16.5 returns a promise from connection.release', function(done) {
oracledb.getConnection(dbConfig)
.then(function(conn) {
conn.should.be.ok;
var promise = conn.release();
promise.should.be.an.instanceof(oracledb.Promise);
return promise;
})
.then(function() {
return done();
})
.catch(function(err) {
return done(err);
});
})
it('16.6 returns a promise from connection.execute', function(done) {
oracledb.getConnection(dbConfig)
.then(function(conn) {
conn.should.be.ok;
var executePromise = conn.execute('select 1 from dual');
executePromise.should.be.an.instanceof(oracledb.Promise);
return executePromise
.then(function(result) {
result.rows[0][0].should.eql(1);
return conn.release()
.then(done);
});
})
.catch(function(err) {
return done(err);
});
})
it('16.7 returns a promise from connection.commit', function(done) {
oracledb.getConnection(dbConfig)
.then(function(conn) {
var commitPromise;
conn.should.be.ok;
commitPromise = conn.commit();
commitPromise.should.be.an.instanceof(oracledb.Promise);
return commitPromise
.then(function() {
return conn.release()
.then(done);
});
})
.catch(function(err) {
return done(err);
});
})
it('16.8 returns a promise form connection.rollback', function(done) {
oracledb.getConnection(dbConfig)
.then(function(conn) {
var rollbackPromise;
conn.should.be.ok;
rollbackPromise = conn.rollback();
rollbackPromise.should.be.an.instanceof(oracledb.Promise);
return rollbackPromise
.then(function() {
return conn.release()
.then(done);
});
})
.catch(function(err) {
return done(err);
});
})
it('16.9 returns a promise from resultSet.close', function(done) {
oracledb.getConnection(dbConfig)
.then(function(conn) {
conn.should.be.ok;
return conn.execute('select 1 from dual', [], {resultSet: true})
.then(function(result) {
var closePromise;
closePromise = result.resultSet.close();
closePromise.should.be.an.instanceof(oracledb.Promise);
return closePromise
.then(function() {
return conn.release()
.then(done);
});
});
})
.catch(function(err) {
return done(err);
});
})
it('16.10 returns a promise from resultSet.getRow', function(done) {
function finishProcessing(conn, resultSet) {
return resultSet.close()
.then(function() {
conn.release();
})
}
function processResultSet(conn, resultSet) {
return new Promise(function(resolve, reject) {
function processRow() {
var getRowPromise;
getRowPromise = resultSet.getRow();
getRowPromise.should.be.an.instanceof(oracledb.Promise);
getRowPromise
.then(function(row) {
if (!row) {
finishProcessing(conn, resultSet)
.then(function() {
resolve();
});
} else {
row[0].should.eql(1);
processRow();
}
})
.catch(function(err) {
reject(err);
});
}
processRow();
});
}
oracledb.getConnection(dbConfig)
.then(function(conn) {
conn.should.be.ok;
return conn.execute('select 1 from dual', [], {resultSet: true})
.then(function(result) {
return processResultSet(conn, result.resultSet)
.then(function() {
done();
});
});
})
.catch(function(err) {
return done(err);
});
}) // 16.10
it('16.11 returns a promise from resultSet.getRows', function(done) {
function finishProcessing(conn, resultSet) {
return resultSet.close()
.then(function() {
conn.release();
});
}
function processResultSet(conn, resultSet) {
return new Promise(function(resolve, reject) {
function processRows() {
var getRowsPromise;
getRowsPromise = resultSet.getRows(2);
getRowsPromise.should.be.an.instanceof(oracledb.Promise);
getRowsPromise
.then(function(rows) {
if (rows.length === 0) {
finishProcessing(conn, resultSet)
.then(function() {
resolve();
});
} else {
rows[0][0].should.eql(1);
rows[1][0].should.eql(2);
processRows();
}
})
.catch(function(err) {
reject(err);
});
}
processRows();
});
}
oracledb.getConnection(dbConfig)
.then(function(conn) {
conn.should.be.ok;
return conn.execute('select 1 from dual union select 2 from dual', [], {resultSet: true})
.then(function(result) {
return processResultSet(conn, result.resultSet)
.then(function() {
return done();
});
});
})
.catch(function(err) {
return done(err);
});
}) // 16.11
})

View File

@ -630,15 +630,16 @@ describe('12. resultSet1.js', function() {
); );
function fetchRowFromRS(rs) { function fetchRowFromRS(rs) {
rs.getRows(function(err, rows) { try {
rs.getRows(function() {});
} catch (err) {
should.exist(err); should.exist(err);
err.message.should.eql('NJS-009: invalid number of parameters'); err.message.should.eql('NJS-009: invalid number of parameters');
should.not.exist(rows);
rs.close(function(err) { rs.close(function(err) {
should.not.exist(err); should.not.exist(err);
done(); done();
}); });
}); }
} }
}) })
@ -685,7 +686,7 @@ describe('12. resultSet1.js', function() {
function fetchRowFromRS(rs, numRows) { function fetchRowFromRS(rs, numRows) {
rs.getRows(numRows, function(err, rows) { rs.getRows(numRows, function(err, rows) {
should.exist(err); should.exist(err);
err.message.should.startWith('NJS-006: invalid type for parameter 1'); err.message.should.eql('NJS-006: invalid type for parameter 1');
rs.close(function(err) { rs.close(function(err) {
should.not.exist(err); should.not.exist(err);
done(); done();
@ -709,14 +710,16 @@ describe('12. resultSet1.js', function() {
); );
function fetchRowFromRS(rs, numRows) { function fetchRowFromRS(rs, numRows) {
rs.getRows(numRows, function(err, rows) { try {
rs.getRows(numRows, function() {});
} catch (err) {
should.exist(err); should.exist(err);
err.message.should.startWith('NJS-006: invalid type for parameter 1'); err.message.should.startWith('NJS-006: invalid type for parameter 1');
rs.close(function(err) { rs.close(function(err) {
should.not.exist(err); should.not.exist(err);
done(); done();
}); });
}); }
} }
}) })
}) })
@ -870,15 +873,16 @@ describe('12. resultSet1.js', function() {
); );
function fetchRowFromRS(rs, numRows) { function fetchRowFromRS(rs, numRows) {
rs.getRow(numRows, function(err, row) { try {
rs.getRow(numRows, function() {});
} catch (err) {
should.exist(err); should.exist(err);
err.message.should.eql('NJS-009: invalid number of parameters'); err.message.should.eql('NJS-009: invalid number of parameters');
should.not.exist(row);
rs.close(function(err) { rs.close(function(err) {
should.not.exist(err); should.not.exist(err);
done(); done();
}); });
}); }
} }
}) })

View File

@ -0,0 +1,377 @@
/* 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
* 15. resultsetToStream.js
*
* DESCRIPTION
* Testing driver query results via stream feature.
*
* 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('15. resultsetToStream.js', function () {
if (dbConfig.externalAuth) {
var credential = {externalAuth: true, connectString: dbConfig.connectString};
} else {
var credential = dbConfig;
}
var connection = null;
var rowsAmount = 217;
before(function(done) {
async.series([
function getConn(cb) {
oracledb.getConnection(credential, function(err, conn) {
should.not.exist(err);
connection = conn;
cb();
});
},
function createTab(cb) {
var proc = "BEGIN \n" +
" DECLARE \n" +
" e_table_exists EXCEPTION; \n" +
" PRAGMA EXCEPTION_INIT(e_table_exists, -00942);\n " +
" BEGIN \n" +
" EXECUTE IMMEDIATE ('DROP TABLE nodb_employees'); \n" +
" EXCEPTION \n" +
" WHEN e_table_exists \n" +
" THEN NULL; \n" +
" END; \n" +
" EXECUTE IMMEDIATE (' \n" +
" CREATE TABLE nodb_employees ( \n" +
" employees_id NUMBER, \n" +
" employees_name VARCHAR2(20), \n" +
" employees_history CLOB \n" +
" ) \n" +
" '); \n" +
"END; ";
connection.execute(
proc,
function(err) {
should.not.exist(err);
cb();
}
);
},
function insertRows(cb) {
var proc = "DECLARE \n" +
" x NUMBER := 0; \n" +
" n VARCHAR2(20); \n" +
" clobData CLOB; \n" +
"BEGIN \n" +
" FOR i IN 1..217 LOOP \n" +
" x := x + 1; \n" +
" n := 'staff ' || x; \n" +
" INSERT INTO nodb_employees VALUES (x, n, EMPTY_CLOB()) RETURNING employees_history INTO clobData; \n" +
" DBMS_LOB.WRITE(clobData, 20, 1, '12345678901234567890'); \n" +
" END LOOP; \n" +
"end; ";
connection.execute(
proc,
function(err) {
should.not.exist(err);
cb();
}
);
}
], done);
}) // before
after(function(done) {
async.series([
function(callback) {
connection.execute(
"DROP TABLE nodb_employees",
function(err) {
should.not.exist(err);
callback();
}
);
},
function(callback) {
connection.release(function(err) {
should.not.exist(err);
callback();
});
},
], done);
}) // after
describe('15.1 Testing ResultSet.toQueryStream', function () {
it('15.1.1 should allow resultsets to be converted to streams', function (done) {
connection.execute(
'begin \n' +
' open :cursor for select employees_name from nodb_employees; \n' +
'end;',
{
cursor: { type: oracledb.CURSOR, dir: oracledb.BIND_OUT }
},
function(err, result) {
should.not.exist(err);
var stream = result.outBinds.cursor.toQueryStream();
stream.on('error', function (error) {
console.log(error);
should.fail(error, null, 'Error event should not be triggered');
});
var counter = 0;
stream.on('data', function (data) {
should.exist(data);
counter++;
});
stream.on('end', function () {
should.equal(counter, rowsAmount);
setTimeout(done, 500);
});
}
);
});
});
describe('15.2 Testing ResultSet/QueryStream conversion errors', function () {
it('15.2.1 should prevent conversion to stream after getRow is invoked', function (done) {
connection.execute(
'begin \n' +
' open :cursor for select employees_name from nodb_employees; \n' +
'end;',
{
cursor: { type: oracledb.CURSOR, dir: oracledb.BIND_OUT }
},
function(err, result) {
should.not.exist(err);
var cursor = result.outBinds.cursor;
cursor.getRow(function(err, row) {
should.not.exist(err);
cursor.close(function(err) {
should.not.exist(err);
done();
});
});
try {
var stream = cursor.toQueryStream();
} catch (err) {
(err.message).should.startWith('NJS-041:');
// NJS-041: cannot convert to stream after invoking methods
}
}
);
});
it('15.2.2 should prevent conversion to stream after getRows is invoked', function (done) {
connection.execute(
'begin \n' +
' open :cursor for select employees_name from nodb_employees; \n' +
'end;',
{
cursor: { type: oracledb.CURSOR, dir : oracledb.BIND_OUT }
},
function(err, result) {
should.not.exist(err);
var cursor = result.outBinds.cursor;
cursor.getRows(5, function(err, rows) {
should.not.exist(err);
cursor.close(function(err) {
should.not.exist(err);
done();
});
});
try {
var stream = cursor.toQueryStream();
} catch (err) {
(err.message).should.startWith('NJS-041:');
}
}
);
});
it('15.2.3 should prevent conversion to stream after close is invoked', function (done) {
connection.execute(
'begin \n' +
' open :cursor for select employees_name from nodb_employees; \n' +
'end;',
{
cursor: { type: oracledb.CURSOR, dir : oracledb.BIND_OUT }
},
function(err, result) {
should.not.exist(err);
var cursor = result.outBinds.cursor;
cursor.close(function(err) {
should.not.exist(err);
done();
});
try {
var stream = cursor.toQueryStream();
} catch (err) {
(err.message).should.startWith('NJS-041:');
}
}
);
});
it('15.2.4 should prevent invoking getRow after conversion to stream', function (done) {
connection.execute(
'begin \n' +
' open :cursor for select employees_name from nodb_employees; \n' +
'end;',
{
cursor: { type: oracledb.CURSOR, dir : oracledb.BIND_OUT }
},
function(err, result) {
should.not.exist(err);
var cursor = result.outBinds.cursor;
var stream = cursor.toQueryStream();
cursor.getRow(function(err, row) {
(err.message).should.startWith('NJS-042:');
// NJS-042: cannot invoke methods after converting to stream
// Closing cursor via stream._close because the cursor.close method
// is not invokable after conversion to stream.
stream._close(function(err) {
should.not.exist(err);
done();
});
});
}
);
});
it('15.2.5 should prevent invoking getRows after conversion to stream', function (done) {
connection.execute(
'begin \n' +
' open :cursor for select employees_name from nodb_employees; \n' +
'end;',
{
cursor: { type: oracledb.CURSOR, dir : oracledb.BIND_OUT }
},
function(err, result) {
should.not.exist(err);
var cursor = result.outBinds.cursor;
var stream = cursor.toQueryStream();
cursor.getRows(5, function(err, rows) {
(err.message).should.startWith('NJS-042:');
// Closing cursor via stream._close because the cursor.close method
// is not invokable after conversion to stream.
stream._close(function(err) {
should.not.exist(err);
done();
});
});
}
);
});
it('15.2.6 should prevent invoking close after conversion to stream', function (done) {
connection.execute(
'begin \n' +
' open :cursor for select employees_name from nodb_employees; \n' +
'end;',
{
cursor: { type: oracledb.CURSOR, dir : oracledb.BIND_OUT }
},
function(err, result) {
should.not.exist(err);
var cursor = result.outBinds.cursor;
var stream = cursor.toQueryStream();
cursor.close(function(err) {
(err.message).should.startWith('NJS-042:');
// Closing cursor via stream._close because the cursor.close method
// is not invokable after conversion to stream.
stream._close(function(err) {
should.not.exist(err);
done();
});
});
}
);
});
it('15.2.7 should prevent calling toQueryStream more than once', function (done) {
connection.execute(
'begin \n' +
' open :cursor for select employees_name from nodb_employees; \n' +
'end;',
{
cursor: { type: oracledb.CURSOR, dir : oracledb.BIND_OUT }
},
function(err, result) {
should.not.exist(err);
var cursor = result.outBinds.cursor;
// First conversion to stream
var stream = cursor.toQueryStream();
try {
// Second conversion to stream
stream = cursor.toQueryStream();
} catch (err) {
(err.message).should.startWith('NJS-043:');
stream._close(function(err) {
should.not.exist(err);
done();
});
}
}
);
}); // 15.2.7
}); // 15.2
});

View File

@ -39,7 +39,6 @@ var async = require('async');
var dbConfig = require('./dbconfig.js'); var dbConfig = require('./dbconfig.js');
describe('13. stream1.js', function () { describe('13. stream1.js', function () {
var connection = false;
if (dbConfig.externalAuth) { if (dbConfig.externalAuth) {
var credential = {externalAuth: true, connectString: dbConfig.connectString}; var credential = {externalAuth: true, connectString: dbConfig.connectString};
@ -47,85 +46,91 @@ describe('13. stream1.js', function () {
var credential = dbConfig; var credential = dbConfig;
} }
var createTable = var connection = null;
"BEGIN \
DECLARE \
e_table_exists EXCEPTION; \
PRAGMA EXCEPTION_INIT(e_table_exists, -00942); \
BEGIN \
EXECUTE IMMEDIATE ('DROP TABLE nodb_employees'); \
EXCEPTION \
WHEN e_table_exists \
THEN NULL; \
END; \
EXECUTE IMMEDIATE (' \
CREATE TABLE nodb_employees ( \
employees_id NUMBER, \
employees_name VARCHAR2(20), \
employees_history CLOB \
) \
'); \
END; ";
var insertRows =
"DECLARE \
x NUMBER := 0; \
n VARCHAR2(20); \
clobData CLOB;\
BEGIN \
FOR i IN 1..217 LOOP \
x := x + 1; \
n := 'staff ' || x; \
INSERT INTO nodb_employees VALUES (x, n, EMPTY_CLOB()) RETURNING employees_history INTO clobData; \
\
DBMS_LOB.WRITE(clobData, 20, 1, '12345678901234567890');\
END LOOP; \
END; ";
var rowsAmount = 217; var rowsAmount = 217;
before(function(done) {
before(function (done) { async.series([
oracledb.getConnection(credential, function (err, conn) { function getConn(cb) {
if (err) { oracledb.getConnection(credential, function(err, conn) {
console.error(err); should.not.exist(err);
return; connection = conn;
} cb();
connection = conn;
connection.execute(createTable, function (err) {
if (err) {
console.error(err);
return;
}
connection.execute(insertRows, function (err) {
if (err) {
console.error(err);
return;
}
done();
}); });
}); },
}); function createTab(cb) {
}); var proc = "BEGIN \n" +
" DECLARE \n" +
" e_table_exists EXCEPTION; \n" +
" PRAGMA EXCEPTION_INIT(e_table_exists, -00942);\n " +
" BEGIN \n" +
" EXECUTE IMMEDIATE ('DROP TABLE nodb_employees'); \n" +
" EXCEPTION \n" +
" WHEN e_table_exists \n" +
" THEN NULL; \n" +
" END; \n" +
" EXECUTE IMMEDIATE (' \n" +
" CREATE TABLE nodb_employees ( \n" +
" employees_id NUMBER, \n" +
" employees_name VARCHAR2(20), \n" +
" employees_history CLOB \n" +
" ) \n" +
" '); \n" +
"END; ";
after(function (done) { connection.execute(
connection.execute( proc,
'DROP TABLE nodb_employees', function(err) {
function (err) { should.not.exist(err);
if (err) { cb();
console.error(err.message);
return;
}
connection.release(function (err) {
if (err) {
console.error(err.message);
return;
} }
done(); );
}); },
} function insertRows(cb) {
); var proc = "DECLARE \n" +
}); " x NUMBER := 0; \n" +
" n VARCHAR2(20); \n" +
" clobData CLOB; \n" +
"BEGIN \n" +
" FOR i IN 1..217 LOOP \n" +
" x := x + 1; \n" +
" n := 'staff ' || x; \n" +
" INSERT INTO nodb_employees VALUES (x, n, EMPTY_CLOB()) RETURNING employees_history INTO clobData; \n" +
" DBMS_LOB.WRITE(clobData, 20, 1, '12345678901234567890'); \n" +
" END LOOP; \n" +
"end; ";
describe('13.1 Testing ResultSet stream', function () { connection.execute(
proc,
function(err) {
should.not.exist(err);
cb();
}
);
}
], done);
}) // before
after(function(done) {
async.series([
function(callback) {
connection.execute(
"DROP TABLE nodb_employees",
function(err) {
should.not.exist(err);
callback();
}
);
},
function(callback) {
connection.release(function(err) {
should.not.exist(err);
callback();
});
},
], done);
}) // after
describe('13.1 Testing QueryStream', function () {
it('13.1.1 stream results for oracle connection', function (done) { it('13.1.1 stream results for oracle connection', function (done) {
connection.should.be.ok; connection.should.be.ok;
@ -357,6 +362,7 @@ describe('13. stream1.js', function () {
var counter = 0; var counter = 0;
var clobs = []; var clobs = [];
var clobsRead = 0; var clobsRead = 0;
stream.on('data', function (data) { stream.on('data', function (data) {
var rowIndex = counter; var rowIndex = counter;
@ -487,4 +493,146 @@ describe('13. stream1.js', function () {
}); });
}); });
}); });
describe('13.2 Testing QueryStream._close', function () {
it('13.2.1 should be able to stop the stream early with _close', function (done) {
connection.should.be.ok;
var stream = connection.queryStream('SELECT employees_name FROM nodb_employees');
stream.on('data', function () {
stream.pause();
stream._close();
});
stream.on('close', function() {
done();
});
stream.on('end', function () {
done(new Error('Reached the end of the stream'));
});
stream.on('error', function (err) {
done(err);
});
});
it('13.2.2 should be able to stop the stream before any data', function (done) {
connection.should.be.ok;
var stream = connection.queryStream('SELECT employees_name FROM nodb_employees');
stream.on('close', function() {
done();
});
// Close is synchronous so it needs to be called after the close listener is added.
stream._close();
stream.on('data', function () {
done(new Error('Received data'));
});
stream.on('end', function () {
done(new Error('Reached the end of the stream'));
});
stream.on('error', function (err) {
done(err);
});
});
it('13.2.3 should invoke an optional callback passed to _close', function (done) {
connection.should.be.ok;
var stream = connection.queryStream('SELECT employees_name FROM nodb_employees');
stream._close(function() {
done();
});
stream.on('data', function () {
done(new Error('Received data'));
});
stream.on('end', function () {
done(new Error('Reached the end of the stream'));
});
stream.on('error', function (err) {
done(err);
});
});
});
describe('13.3 Testing QueryStream\'s maxRows control', function () {
it('13.3.1 should use oracledb.maxRows for fetching', function (done) {
var defaultMaxRows;
var testMaxRows = 9;
connection.should.be.ok;
defaultMaxRows = oracledb.maxRows;
oracledb.maxRows = testMaxRows;
var stream = connection.queryStream('SELECT employees_name FROM nodb_employees');
stream.on('data', function () {
stream.pause();
// Using the internal/private caches to validate
should.equal(stream._fetchedRows.length, testMaxRows - (1 + stream._readableState.buffer.length));
stream._close();
});
stream.on('close', function() {
oracledb.maxRows = defaultMaxRows;
done();
});
stream.on('end', function () {
done(new Error('Reached the end of the stream'));
});
stream.on('error', function (err) {
done(err);
});
});
it('13.3.2 should default to 100 if oracledb.maxRows is falsey', function (done) {
var defaultMaxRows;
var testMaxRows = 0;
connection.should.be.ok;
defaultMaxRows = oracledb.maxRows;
oracledb.maxRows = testMaxRows;
var stream = connection.queryStream('SELECT employees_name FROM nodb_employees');
stream.on('data', function () {
stream.pause();
// Using the internal/private caches to validate
should.equal(stream._fetchedRows.length, (99 - stream._readableState.buffer.length));
stream._close();
});
stream.on('close', function() {
oracledb.maxRows = defaultMaxRows;
done();
});
stream.on('end', function () {
done(new Error('Reached the end of the stream'));
});
stream.on('error', function (err) {
done(err);
});
});
});
}); });

View File

@ -242,20 +242,15 @@ describe('14. stream2.js', function() {
}) // 14.6 }) // 14.6
it('14.7 Negative - queryStream() has no parameters', function(done) { it('14.7 Negative - queryStream() has no parameters', function(done) {
var stream;
var stream = connection.queryStream(); try {
stream = connection.queryStream();
stream.on('error', function(error) { } catch (err) {
should.exist(error); should.exist(err);
// console.log(error); err.message.should.eql('NJS-009: invalid number of parameters');
// NJS-006: invalid type for parameter 1 done();
setTimeout(done, 500); }
});
stream.on('data', function(data) {
should.not.exist(data);
});
}) })
it('14.8 Negative - give invalid SQL as first parameter', function(done) { it('14.8 Negative - give invalid SQL as first parameter', function(done) {