Merge 1.9.0-dev JavaScript changes adding Promises etc

This commit is contained in:
Christopher Jones 2016-05-16 09:53:23 +10:00
parent 4b52b86532
commit 16be352385
30 changed files with 2926 additions and 663 deletions

View File

@ -1,6 +1,34 @@
# Change Log
## node-oracledb v1.9.0 (DD Mon YYYY)
## 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)

View File

@ -5,7 +5,7 @@
The node-oracledb add-on for Node.js powers high performance Oracle
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.
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:
- [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)
- 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)
- [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)
- [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)
@ -35,6 +36,10 @@ The node-oracledb project is open source and maintained by Oracle Corp. The hom
- [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)
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
features, and on functionality requests from
[users involved in the project](https://github.com/oracle/node-oracledb/issues).
@ -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.
### A simple query example:
### A simple query example with callbacks:
```javascript
var oracledb = require('oracledb');
@ -90,6 +95,8 @@ With Oracle's sample HR schema, the output is:
[ [ 60, 'IT' ], [ 90, 'Executive' ], [ 100, 'Finance' ] ]
```
Node Promises can also be used.
## <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).
@ -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)
## <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

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
*
* 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.
* 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

@ -0,0 +1,83 @@
/* 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
* resultsettoquerystream.js
*
* DESCRIPTION
* Converts a ResultSet returned from execute() into a Readable Stream.
* This is an alternative instead of using resultset.getRows().
* Note: using connnection.queryStream() is recommended for top level
* queries because it avoids having to duplicate error handling in the
* callback and event.
*
* 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(
dbConfig,
function(err, connection)
{
if (err) { console.error(err.message); return; }
var sql = "SELECT employee_id, last_name FROM employees WHERE ROWNUM < 25 ORDER BY employee_id";
connection.execute(
sql,
[],
{
resultSet: true,
prefetchRows: 25
},
function(err, result)
{
if (err)
{
console.error(err.message);
doRelease(connection);
return;
}
var queryStream = result.resultSet.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

@ -32,107 +32,108 @@
*
*****************************************************************************/
var http = require('http');
var http = require('http');
var oracledb = require('oracledb');
var dbConfig = require('./dbconfig.js');
var portid = 7000; // HTTP listening port number
var httpPort = 7000;
// Main entry point. Creates a connection pool, on callback creates an
// HTTP server and executes a query based on the URL parameter given.
// HTTP server that executes a query based on the URL parameter given.
// The pool values shown are the default values.
oracledb.createPool (
{
user : dbConfig.user,
password : dbConfig.password,
connectString : dbConfig.connectString,
poolMax : 4, // maximum size of the pool
poolMin : 0, // let the pool shrink completely
poolIncrement : 1, // only grow the pool by one connection at a time
poolTimeout : 0 // never terminate idle connections
},
function(err, pool)
{
function init() {
oracledb.createPool(
{
user: dbConfig.user,
password: dbConfig.password,
connectString: dbConfig.connectString,
poolMax: 4, // maximum size of the pool
poolMin: 0, // let the pool shrink completely
poolIncrement: 1, // only grow the pool by one connection at a time
poolTimeout: 0 // never terminate idle connections
},
function(err, pool) {
if (err) {
console.error("createPool() error: " + err.message);
return;
}
// Create HTTP server and listen on port - httpPort
http
.createServer(function(request, response) {
handleRequest(request, response, pool);
})
.listen(httpPort, "localhost");
console.log("Server running at http://localhost:" + httpPort);
}
);
}
function handleRequest(request, response, pool) {
var urlparts = request.url.split("/");
var deptid = urlparts[1];
htmlHeader(
response,
"Oracle Database Driver for Node.js",
"Example using node-oracledb driver"
);
if (deptid != parseInt(deptid)) {
handleError(
response,
'URL path "' + deptid + '" is not an integer. Try http://localhost:' + httpPort + '/30',
null
);
return;
}
// Checkout a connection from the pool
pool.getConnection(function(err, connection) {
if (err) {
console.error("createPool() callback: " + err.message);
handleError(response, "getConnection() error", err);
return;
}
// Create HTTP server and listen on port - portid
var hs = http.createServer (
function(request, response) // Callback gets HTTP request & response object
{
var urlparts = request.url.split("/");
var deptid = urlparts[1];
// console.log("Connections open: " + pool.connectionsOpen);
// console.log("Connections in use: " + pool.connectionsInUse);
htmlHeader(response,
"Oracle Database Driver for Node.js" ,
"Example using node-oracledb driver");
if (deptid != parseInt(deptid)) {
handleError(response, 'URL path "' + deptid +
'" is not an integer. Try http://localhost:' + portid + '/30', null);
connection.execute(
"SELECT employee_id, first_name, last_name " +
"FROM employees " +
"WHERE department_id = :id",
[deptid], // bind variable value
function(err, result) {
if (err) {
connection.release(function(err) {
if (err) {
// Just logging because handleError call below will have already
// ended the response.
console.error("execute() error release() error", err);
}
});
handleError(response, "execute() error", err);
return;
}
// Checkout a connection from the pool
pool.getConnection (
function(err, connection)
{
if (err) {
handleError(response, "getConnection() failed ", err);
return;
}
displayResults(response, result, deptid);
// console.log("Connections open: " + pool.connectionsOpen);
// console.log("Connections in use: " + pool.connectionsInUse);
connection.execute(
"SELECT employee_id, first_name, last_name " +
"FROM employees " +
"WHERE department_id = :id",
[deptid], // bind variable value
function(err, result)
{
if (err) {
connection.release(
function(err)
{
if (err) {
handleError(response, "execute() error release() callback", err);
return;
}
});
handleError(response, "execute() callback", err);
return;
}
displayResults(response, result, deptid);
/* Release the connection back to the connection pool */
connection.release(
function(err)
{
if (err) {
handleError(response, "normal release() callback", err);
} else {
htmlFooter(response);
}
});
});
});
});
hs.listen(portid, "localhost");
console.log("Server running at http://localhost:" + portid);
/* Release the connection back to the connection pool */
connection.release(function(err) {
if (err) {
handleError(response, "normal release() error", err);
} else {
htmlFooter(response);
}
});
}
);
});
}
// Report an error
function handleError(response, text, err)
{
function handleError(response, text, err) {
if (err) {
text += err.message;
}
@ -141,10 +142,8 @@ function handleError(response, text, err)
htmlFooter(response);
}
// Display query results
function displayResults(response, result, deptid)
{
function displayResults(response, result, deptid) {
response.write("<h2>" + "Employees in Department " + deptid + "</h2>");
response.write("<table>");
@ -166,40 +165,38 @@ function displayResults(response, result, deptid)
response.write("</table>");
}
// Prepare HTML header
function htmlHeader(response, title, caption)
{
response.writeHead (200, {"Content-Type" : "text/html" });
response.write ("<!DOCTYPE html>");
response.write ("<html>");
response.write ("<head>");
response.write ("<style>" +
"body {background:#FFFFFF;color:#000000;font-family:Arial,sans-serif;margin:40px;padding:10px;font-size:12px;text-align:center;}" +
"h1 {margin:0px;margin-bottom:12px;background:#FF0000;text-align:center;color:#FFFFFF;font-size:28px;}" +
"table {border-collapse: collapse; margin-left:auto; margin-right:auto;}" +
"td {padding:8px;border-style:solid}" +
"</style>\n");
response.write ("<title>" + caption + "</title>");
response.write ("</head>");
response.write ("<body>");
response.write ("<h1>" + title + "</h1>");
function htmlHeader(response, title, caption) {
response.writeHead(200, {"Content-Type": "text/html"});
response.write("<!DOCTYPE html>");
response.write("<html>");
response.write("<head>");
response.write("<style>" +
"body {background:#FFFFFF;color:#000000;font-family:Arial,sans-serif;margin:40px;padding:10px;font-size:12px;text-align:center;}" +
"h1 {margin:0px;margin-bottom:12px;background:#FF0000;text-align:center;color:#FFFFFF;font-size:28px;}" +
"table {border-collapse: collapse; margin-left:auto; margin-right:auto;}" +
"td {padding:8px;border-style:solid}" +
"</style>\n");
response.write("<title>" + caption + "</title>");
response.write("</head>");
response.write("<body>");
response.write("<h1>" + title + "</h1>");
}
// Prepare HTML footer
function htmlFooter(response)
{
function htmlFooter(response) {
response.write("</body>\n</html>");
response.end();
}
process
.on('SIGTERM', function() {
console.log("\nTerminating");
process.exit(0);
})
.on('SIGINT', function() {
console.log("\nTerminating");
process.exit(0);
});
.on('SIGTERM', function() {
console.log("\nTerminating");
process.exit(0);
})
.on('SIGINT', function() {
console.log("\nTerminating");
process.exit(0);
});
init();

195
examples/webapppromises.js Normal file
View File

@ -0,0 +1,195 @@
/* 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.
*
* NAME
* webapppromises.js
*
* DESCRIPTION
* Shows a web based query using connections from connection pool. This is
* similar to webapp.js but uses promises.
*
* This displays a table of employees in the specified department.
*
* The script creates an HTTP server listening on port 7000 and
* accepts a URL parameter for the department ID, for example:
* http://localhost:7000/90
*
* Uses Oracle's sample HR schema. Scripts to create the HR schema
* can be found at: https://github.com/oracle/db-sample-schemas
*
*****************************************************************************/
var http = require('http');
var oracledb = require('oracledb');
var dbConfig = require('./dbconfig.js');
var httpPort = 7000;
// Main entry point. Creates a connection pool, on callback creates an
// HTTP server that executes a query based on the URL parameter given.
// The pool values shown are the default values.
function init() {
oracledb.createPool({
user: dbConfig.user,
password: dbConfig.password,
connectString: dbConfig.connectString,
poolMax: 4, // maximum size of the pool
poolMin: 0, // let the pool shrink completely
poolIncrement: 1, // only grow the pool by one connection at a time
poolTimeout: 0 // never terminate idle connections
})
.then(function(pool) {
// Create HTTP server and listen on port - httpPort
http
.createServer(function(request, response) {
handleRequest(request, response, pool);
})
.listen(httpPort, "localhost");
console.log("Server running at http://localhost:" + httpPort);
})
.catch(function(err) {
console.error("createPool() error: " + err.message);
});
}
function handleRequest(request, response, pool) {
var urlparts = request.url.split("/");
var deptid = urlparts[1];
htmlHeader(
response,
"Oracle Database Driver for Node.js",
"Example using node-oracledb driver"
);
if (deptid != parseInt(deptid)) {
handleError(
response,
'URL path "' + deptid + '" is not an integer. Try http://localhost:' + httpPort + '/30',
null
);
return;
}
// Checkout a connection from the pool
pool.getConnection()
.then(function(connection) {
// console.log("Connections open: " + pool.connectionsOpen);
// console.log("Connections in use: " + pool.connectionsInUse);
connection.execute(
"SELECT employee_id, first_name, last_name " +
"FROM employees " +
"WHERE department_id = :id",
[deptid] // bind variable value
)
.then(function(result) {
displayResults(response, result, deptid);
/* Release the connection back to the connection pool */
connection.release()
.then(function() {
htmlFooter(response);
})
.catch(function(err) {
handleError(response, "normal release() error", err);
});
})
.catch(function(err) {
connection.release()
.catch(function(err) {
// Just logging because handleError call below will have already
// ended the response.
console.error("execute() error release() error", err);
});
handleError(response, "execute() error", err);
});
})
.catch(function(err) {
handleError(response, "getConnection() error", err);
});
}
// Report an error
function handleError(response, text, err) {
if (err) {
text += err.message;
}
console.error(text);
response.write("<p>Error: " + text + "</p>");
htmlFooter(response);
}
// Display query results
function displayResults(response, result, deptid) {
response.write("<h2>" + "Employees in Department " + deptid + "</h2>");
response.write("<table>");
// Column Title
response.write("<tr>");
for (var col = 0; col < result.metaData.length; col++) {
response.write("<td>" + result.metaData[col].name + "</td>");
}
response.write("</tr>");
// Rows
for (var row = 0; row < result.rows.length; row++) {
response.write("<tr>");
for (col = 0; col < result.rows[row].length; col++) {
response.write("<td>" + result.rows[row][col] + "</td>");
}
response.write("</tr>");
}
response.write("</table>");
}
// Prepare HTML header
function htmlHeader(response, title, caption) {
response.writeHead(200, {"Content-Type": "text/html"});
response.write("<!DOCTYPE html>");
response.write("<html>");
response.write("<head>");
response.write("<style>" +
"body {background:#FFFFFF;color:#000000;font-family:Arial,sans-serif;margin:40px;padding:10px;font-size:12px;text-align:center;}" +
"h1 {margin:0px;margin-bottom:12px;background:#FF0000;text-align:center;color:#FFFFFF;font-size:28px;}" +
"table {border-collapse: collapse; margin-left:auto; margin-right:auto;}" +
"td {padding:8px;border-style:solid}" +
"</style>\n");
response.write("<title>" + caption + "</title>");
response.write("</head>");
response.write("<body>");
response.write("<h1>" + title + "</h1>");
}
// Prepare HTML footer
function htmlFooter(response) {
response.write("</body>\n</html>");
response.end();
}
process
.on('SIGTERM', function() {
console.log("\nTerminating");
process.exit(0);
})
.on('SIGINT', function() {
console.log("\nTerminating");
process.exit(0);
});
init();

View File

@ -17,16 +17,49 @@
*
*****************************************************************************/
'use strict';
var resultset = require('./resultset.js');
var Stream = require('./resultset-read-stream');
var QueryStream = require('./querystream.js');
var nodbUtil = require('./util.js');
var executePromisified;
var commitPromisified;
var rollbackPromisified;
var releasePromisified;
var breakPromisified;
// The queryStream function is similar to execute except that it immediately
// returns a readable stream.
// returns a QueryStream.
function queryStream(sql, binding, options) {
var self = this;
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;
}
@ -39,24 +72,45 @@ function execute(a1, a2, a3, a4) {
var executeCb;
var custExecuteCb;
// Added this check so that node doesn't hang if no arguments are passed.
if (arguments.length < 2 || arguments.length > 4) {
if (arguments.length && typeof arguments[arguments.length - 1] === 'function') {
arguments[arguments.length - 1](new Error('NJS-009: invalid number of parameters'));
return;
} else {
throw new Error('NJS-009: invalid number of parameters');
}
nodbUtil.assert(arguments.length > 1 && arguments.length < 5, 'NJS-009');
nodbUtil.assert(typeof a1 === 'string', 'NJS-006', 1);
switch (arguments.length) {
case 2:
nodbUtil.assert(typeof a2 === 'function', 'NJS-006', 2);
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) {
var outBindsKeys;
var outBindsIdx;
if (err) {
executeCb(err);
return;
}
// Need to extend resultsets which may come from either the query results
// or outBinds.
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);
@ -78,20 +132,32 @@ function execute(a1, a2, a3, a4) {
}
}
executePromisified = nodbUtil.promisify(execute);
// This commit function is just a place holder to allow for easier extension later.
function commit() {
function commit(commitCb) {
var self = this;
nodbUtil.assert(arguments.length === 1, 'NJS-009');
nodbUtil.assert(typeof commitCb === 'function', 'NJS-006', 1);
self._commit.apply(self, arguments);
}
commitPromisified = nodbUtil.promisify(commit);
// This rollback function is just a place holder to allow for easier extension later.
function rollback() {
function rollback(rollbackCb) {
var self = this;
nodbUtil.assert(arguments.length === 1, 'NJS-009');
nodbUtil.assert(typeof rollbackCb === 'function', 'NJS-006', 1);
self._rollback.apply(self, arguments);
}
rollbackPromisified = nodbUtil.promisify(rollback);
// This release function is used to override the release method of the Connection
// class, which is defined in the C layer. Currently the main difference is that
// connections obtained from a pool need to invoke the pool's _onConnectionRelease
@ -99,26 +165,38 @@ function rollback() {
function release(releaseCb) {
var self = this;
// _pool will only exist on connections obtained from a pool.
if (self._pool && self._pool.queueRequests !== false) {
self._release(function(err) {
releaseCb(err);
nodbUtil.assert(arguments.length === 1, 'NJS-009');
nodbUtil.assert(typeof releaseCb === 'function', 'NJS-006', 1);
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();
});
} 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;
});
}
releasePromisified = nodbUtil.promisify(release);
// 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.
module.break = function() {
module.break = function(breakCb) {
var self = this;
nodbUtil.assert(arguments.length === 1, 'NJS-009');
nodbUtil.assert(typeof breakCb === 'function', 'NJS-006', 1);
self._break.apply(self, arguments);
};
breakPromisified = nodbUtil.promisify(module.break);
// The extend method is used to extend the Connection instance from the C layer with
// 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.
@ -143,7 +221,7 @@ function extend(conn, oracledb, pool) {
writable: true
},
execute: {
value: execute,
value: executePromisified,
enumerable: true,
writable: true
},
@ -151,7 +229,7 @@ function extend(conn, oracledb, pool) {
value: conn.commit
},
commit: {
value: commit,
value: commitPromisified,
enumerable: true,
writable: true
},
@ -159,7 +237,7 @@ function extend(conn, oracledb, pool) {
value: conn.rollback
},
rollback: {
value: rollback,
value: rollbackPromisified,
enumerable: true,
writable: true
},
@ -167,7 +245,12 @@ function extend(conn, oracledb, pool) {
value: conn.release
},
release: {
value: release,
value: releasePromisified,
enumerable: true,
writable: true
},
close: { // alias for release
value: releasePromisified,
enumerable: true,
writable: true
},
@ -175,7 +258,7 @@ function extend(conn, oracledb, pool) {
value: conn.break
},
break: {
value: module.break,
value: breakPromisified,
enumerable: true,
writable: true
}

View File

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

View File

@ -17,11 +17,16 @@
*
*****************************************************************************/
'use strict';
var oracledbCLib;
var oracledbInst;
var Lob = require('./lob.js').Lob;
var pool = require('./pool.js');
var connection = require('./connection.js');
var nodbUtil = require('./util.js');
var createPoolPromisified;
var getConnectionPromisified;
try {
oracledbCLib = require('../build/Release/oracledb');
@ -43,6 +48,10 @@ oracledbCLib.Oracledb.prototype.newLob = function(iLob) {
function createPool(poolAttrs, createPoolCb) {
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) {
if (err) {
createPoolCb(err);
@ -51,16 +60,22 @@ function createPool(poolAttrs, createPoolCb) {
pool.extend(poolInst, poolAttrs, self);
createPoolCb(undefined, poolInst);
createPoolCb(null, poolInst);
});
}
createPoolPromisified = nodbUtil.promisify(createPool);
// This getConnection function is used the override the getConnection method of the
// Oracledb class, which is defined in the C layer. The override allows us to do
// things like extend out the connection instance prior to passing it to the caller.
function getConnection(connAttrs, createConnectionCb) {
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) {
if (err) {
createConnectionCb(err);
@ -69,10 +84,12 @@ function getConnection(connAttrs, createConnectionCb) {
connection.extend(connInst, self);
createConnectionCb(undefined, connInst);
createConnectionCb(null, connInst);
});
}
getConnectionPromisified = nodbUtil.promisify(getConnection);
// The extend method is used to extend the Oracledb instance from the C layer with
// 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.
@ -83,6 +100,9 @@ function extend(oracledb) {
Object.defineProperties(
oracledb,
{
_oracledb: { // Known to be used in util.js' promisify function.
value: oracledb
},
DEFAULT: {
value: 0,
enumerable: true
@ -135,6 +155,11 @@ function extend(oracledb) {
value: 4002,
enumerable: true
},
Promise: {
value: global.Promise,
enumerable: true,
writable: true
},
Oracledb: {
value: oracledbCLib.Oracledb,
enumerable: true
@ -169,7 +194,7 @@ function extend(oracledb) {
value: oracledb.createPool
},
createPool: {
value: createPool,
value: createPoolPromisified,
enumerable: true,
writable: true
},
@ -177,7 +202,7 @@ function extend(oracledb) {
value: oracledb.getConnection
},
getConnection: {
value: getConnection,
value: getConnectionPromisified,
enumerable: true,
writable: true
}
@ -185,7 +210,7 @@ function extend(oracledb) {
);
}
oracledbInst = new oracledbCLib.Oracledb;
oracledbInst = new oracledbCLib.Oracledb();
extend(oracledbInst);

View File

@ -17,7 +17,12 @@
*
*****************************************************************************/
'use strict';
var connection = require('./connection.js');
var nodbUtil = require('./util.js');
var getConnectionPromisified;
var terminatePromisified;
// 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
@ -50,7 +55,7 @@ function completeConnectionRequest(getConnectionCb) {
connection.extend(connInst, self._oracledb, self);
getConnectionCb(undefined, connInst);
getConnectionCb(null, connInst);
});
}
@ -119,7 +124,7 @@ function onRequestTimeout(timerIdx) {
self._connRequestQueue.splice(requestIndex, 1);
self._connRequestTimersMap[timerIdx] = null;
payloadToDequeue.getConnectionCb(new Error('NJS-040: connection request timeout'));
payloadToDequeue.getConnectionCb(new Error(nodbUtil.getErrorMessage('NJS-040')));
}
}
@ -134,6 +139,9 @@ function getConnection(getConnectionCb) {
var timeoutHandle;
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
// (which is a C layer getter) an error will be thrown.
if (!self._isValid) {
@ -150,21 +158,21 @@ function getConnection(getConnectionCb) {
}
if (self.queueRequests === false) { // queueing is disabled for pool
if (self._enableStats) {
self._getConnection(function(err, connInst) {
if (err) {
self._getConnection(function(err, connInst) {
if (err) {
if (self._enableStats) {
self._totalFailedRequests += 1;
getConnectionCb(err);
return;
}
getConnectionCb(null, connInst);
});
} else {
self._getConnection(getConnectionCb);
}
getConnectionCb(err);
return;
}
connection.extend(connInst, self._oracledb, self);
getConnectionCb(null, connInst);
});
} else if (self._connectionsOut < self.poolMax) { // queueing enabled, but not needed
completeConnectionRequest.call(self, getConnectionCb);
} else { // need to queue the request
@ -200,10 +208,14 @@ function getConnection(getConnectionCb) {
}
}
getConnectionPromisified = nodbUtil.promisify(getConnection);
function terminate(terminateCb) {
var self = this;
nodbUtil.assert(arguments.length === 1, 'NJS-009');
nodbUtil.assert(typeof terminateCb === 'function', 'NJS-006', 1);
self._terminate(function(err) {
if (!err) {
self._isValid = false;
@ -213,6 +225,8 @@ function terminate(terminateCb) {
});
}
terminatePromisified = nodbUtil.promisify(terminate);
// logStats is used to add a hidden method (_logStats) to each pool instance. This
// provides an easy way to log out the statistics related information that's collected
// when _enableStats is set to true when creating a pool. This functionality may
@ -290,13 +304,23 @@ function extend(pool, poolAttrs, oracledb) {
_oracledb: { // storing a reference to the base instance to avoid circular references with require
value: oracledb
},
queueRequests: {
value: queueRequests, // true will queue requests when conn pool is maxed out
enumerable: true
queueRequests: { // true will queue requests when conn pool is maxed out
enumerable: true,
get: function() {
return queueRequests;
},
set: function() {
throw new Error(nodbUtil.getErrorMessage('NJS-014', 'queueRequests'));
}
},
queueTimeout: {
value: queueTimeout, // milliseconds a connection request can spend in queue before being failed
enumerable: true
queueTimeout: { // milliseconds a connection request can spend in queue before being failed
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
value: true,
@ -373,7 +397,7 @@ function extend(pool, poolAttrs, oracledb) {
value: pool.getConnection
},
getConnection: {
value: getConnection,
value: getConnectionPromisified,
enumerable: true,
writable: true
},
@ -381,7 +405,12 @@ function extend(pool, poolAttrs, oracledb) {
value: pool.terminate
},
terminate: {
value: terminate,
value: terminatePromisified,
enumerable: true,
writable: true
},
close: { // alias for terminate
value: terminatePromisified,
enumerable: true,
writable: true
}

230
lib/querystream.js Normal file
View File

@ -0,0 +1,230 @@
/* 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;
if (!self.isPaused()) {
self.pause();
}
// 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,126 @@
*
*****************************************************************************/
'use strict';
var QueryStream = require('./querystream.js');
var nodbUtil = require('./util.js');
var closePromisified;
var getRowPromisified;
var getRowsPromisified;
// This close function is just a place holder to allow for easier extension later.
function close() {
function close(closeCb) {
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);
});
}
closePromisified = nodbUtil.promisify(close);
// This getRow function is just a place holder to allow for easier extension later.
function getRow() {
function getRow(getRowCb) {
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);
}
getRowPromisified = nodbUtil.promisify(getRow);
// This getRows function is just a place holder to allow for easier extension later.
function getRows() {
function getRows(numRows, getRowsCb) {
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);
}
getRowsPromisified = nodbUtil.promisify(getRows);
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
// 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.
function extend(resultSet) {
function extend(resultSet, oracledb) {
// Using Object.defineProperties to add properties to the ResultSet instance with
// special properties, such as enumerable but not writable.
Object.defineProperties(
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: {
value: resultSet.close
},
close: {
value: close,
value: closePromisified,
enumerable: true,
writable: true
},
@ -59,7 +144,7 @@ function extend(resultSet) {
value: resultSet.getRow
},
getRow: {
value: getRow,
value: getRowPromisified,
enumerable: true,
writable: true
},
@ -67,7 +152,12 @@ function extend(resultSet) {
value: resultSet.getRows
},
getRows: {
value: getRows,
value: getRowsPromisified,
enumerable: true,
writable: true
},
toQueryStream: {
value: toQueryStream,
enumerable: 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

@ -33,19 +33,19 @@
"url": "git://github.com/oracle/node-oracledb.git"
},
"dependencies": {
"nan": "~2.2.0"
"nan": "~2.3.0"
},
"devDependencies": {
"mocha": "~2.2.5",
"should": "~6.0.1",
"async": "~0.9.0"
"mocha": "^2.4.5",
"should": "^8.3.1",
"async": "^1.5.0"
},
"scripts": {
"test": "mocha -R spec --timeout 10000 test/*.js",
"testWindows": "mocha -R spec --timeout 0 test\\*.js"
},
"engines": {
"node": ">=0.10.28 <6"
"node": ">=0.10.28 <7"
},
"maintainers": [
{

View File

@ -2599,7 +2599,7 @@ void Connection::Async_AfterExecute(uv_work_t *req)
Nan::New<v8::String>("outBinds").ToLocalChecked(),
outBindValue,
v8::ReadOnly);
Nan::Set(result, Nan::New<v8::String>("rowsAffected").ToLocalChecked(), Nan::Undefined());
Nan::Set(result, Nan::New<v8::String>("rows").ToLocalChecked(), Nan::Undefined());

View File

@ -1038,7 +1038,7 @@ NAN_METHOD(ILob::Write)
/* If iLob is invalid from JS, then throw an exception */
NJS_CHECK_OBJECT_VALID2 ( iLob, info );
LobBaton *lobBaton = new LobBaton ( iLob->njsconn_->LOBCount (),
LobBaton *lobBaton = new LobBaton ( iLob->njsconn_->LOBCount (),
buffer_obj, callback );
NJS_CHECK_NUMBER_OF_ARGS (lobBaton->error, info, 2, 2, exitWrite);

View File

@ -53,19 +53,19 @@ static const char *errMsg[] =
"NJS-014: %s is a read-only property", // errReadOnly
"NJS-015: type was not specified for conversion", // errNoTypeForConversion
"NJS-016: buffer is too small for OUT binds", // errInsufficientBufferForBinds
"NJS-017: concurrent operations on resultSet are not allowed", // errBusyResultSet
"NJS-018: invalid result set", // errInvalidResultSet
"NJS-019: resultSet cannot be returned for non-query statements", // errInvalidNonQueryExecution
"NJS-017: concurrent operations on ResultSet are not allowed", // errBusyResultSet
"NJS-018: invalid ResultSet", // errInvalidResultSet
"NJS-019: ResultSet cannot be returned for non-query statements", // errInvalidNonQueryExecution
"NJS-020: empty array was specified to fetch values as string", // errEmptyArrayForFetchAs
"NJS-021: invalid type for conversion specified", // errInvalidTypeForConversion
"NJS-022: invalid LOB", // errInvalidLob
"NJS-022: invalid Lob", // errInvalidLob
"NJS-023: concurrent operations on LOB are not allowed", // errBusyLob
"NJS-024: memory allocation failed", // errInsufficientMemory
"NJS-025: overflow when calculating results area size", // errResultsTooLarge
"NJS-026: maxRows must be greater than zero", // errInvalidmaxRows
"NJS-027: unexpected SQL parsing error", // errSQLSyntaxError
"NJS-028: raw database type is not supported with DML Returning statements", // errBufferReturningInvalid
"NJS-029: Invalid object from javascript", // errInvalidJSObject
"NJS-029: invalid object from JavaScript", // errInvalidJSObject
"NJS-030: connection cannot be released because Lob operations are in progress", // errBusyConnLOB
"NJS-031: connection cannot be released because ResultSet operations are in progress", // errBusyConnRS
"NJS-032: connection cannot be released because a database call is in progress", // errBusyConnDB
@ -77,10 +77,9 @@ static const char *errMsg[] =
"NJS-038: maxArraySize value should be greater than 0", // errInvalidValueArrayBind
"NJS-039: empty array is not allowed for IN bind", // errEmptyArray
"NJS-040: connection request timeout", // errConnRequestTimeout
"NJS-041: error used in JS layer 41",
"NJS-042: error used in JS Layer 42",
"NJS-043: error used in JS Layer 43",
"NJS-041: cannot convert ResultSet to QueryStream after invoking methods", // errCannotConvertRsToStream
"NJS-042: cannot invoke ResultSet methods after converting to QueryStream", // errCannotInvokeRsMethods
"NJS-043: ResultSet already converted to QueryStream", // errResultSetAlreadyConverted
};
string NJSMessages::getErrorMsg ( NJSErrorType err, ... )

View File

@ -76,9 +76,9 @@ typedef enum
errInvalidValueArrayBind,
errEmptyArray,
errConnRequestTimeout,
errUsedInJS41,
errUsedInJS42,
errUsedInJS43,
errCannotConvertRsToStream,
errCannotInvokeRsMethods,
errResultSetAlreadyConverted,
// New ones should be added here

View File

@ -718,7 +718,6 @@ describe('1. connection.js', function(){
describe('1.6 Testing parameter assertions', function() {
var conn1;
var sql = 'select 1 from dual';
beforeEach('get connection ready', function(done) {
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 {
conn1.execute(sql);
conn1.execute();
} catch (err) {
if (promiseSupportEnabled) {
listeners.forEach(function(listener) {
process.on('uncaughtException', listener);
});
}
should.exist(err);
done();
}
});
it('1.6.2 too few params with a callback should pass error in callback', function(done) {
conn1.execute(function(err, result) {
should.exist(err);
done();
});
});
it('1.6.2 too many params should throw 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');
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 {
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) {
should.exist(err);
done();
}
});
it('1.6.4 too many params with a callback should pass error in callback', function(done) {
conn1.execute(1, 2, 3, 4, function(err, result) {
it('1.6.4 wrong type for param 2 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 {
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);
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.1 commit() 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.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.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.10 Close method
2.10.1 close can be used as an alternative to release
3. examples.js
@ -226,7 +237,7 @@
12.7.2 maxRows option is ignored with REF Cursor
13. stream.js
13.1 Testing ResultSet stream
13.1 Testing QueryStream
13.1.1 stream results for oracle connection
13.1.2 stream results for oracle connection (outFormat: oracledb.OBJECT)
13.1.3 errors in query
@ -238,6 +249,13 @@
13.1.9 Read CLOBs after stream close
13.1.10 meta data
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.1 Bind by position and return an array
@ -248,7 +266,32 @@
14.6 maxRows option is ignored as expect
14.7 Negative - queryStream() has no parameters
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

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) {
should.not.exist(err);
try {
pool1.getConnection(1);
pool1.getConnection();
} catch (err) {
should.exist(err);
(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) {
rs.getRows(function(err, rows) {
try {
rs.getRows(function() {});
} catch (err) {
should.exist(err);
err.message.should.eql('NJS-009: invalid number of parameters');
should.not.exist(rows);
rs.close(function(err) {
should.not.exist(err);
done();
});
});
}
}
})
@ -685,7 +686,7 @@ describe('12. resultSet1.js', function() {
function fetchRowFromRS(rs, numRows) {
rs.getRows(numRows, function(err, rows) {
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) {
should.not.exist(err);
done();
@ -709,14 +710,16 @@ describe('12. resultSet1.js', function() {
);
function fetchRowFromRS(rs, numRows) {
rs.getRows(numRows, function(err, rows) {
try {
rs.getRows(numRows, function() {});
} catch (err) {
should.exist(err);
err.message.should.startWith('NJS-006: invalid type for parameter 1');
rs.close(function(err) {
should.not.exist(err);
done();
});
});
}
}
})
})
@ -870,15 +873,16 @@ describe('12. resultSet1.js', function() {
);
function fetchRowFromRS(rs, numRows) {
rs.getRow(numRows, function(err, row) {
try {
rs.getRow(numRows, function() {});
} catch (err) {
should.exist(err);
err.message.should.eql('NJS-009: invalid number of parameters');
should.not.exist(row);
rs.close(function(err) {
should.not.exist(err);
done();
});
});
}
}
})
@ -943,7 +947,7 @@ describe('12. resultSet1.js', function() {
accessCount.should.be.exactly(10);
rs.close(function(err) {
should.exist(err);
err.message.should.eql("NJS-018: invalid result set");
err.message.should.startWith('NJS-018:');
done();
});
});
@ -979,7 +983,7 @@ describe('12. resultSet1.js', function() {
should.not.exist(err);
rs.getRows(numRows, function(err, rows) {
should.exist(err);
err.message.should.eql("NJS-018: invalid result set");
err.message.should.startWith('NJS-018:');
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');
describe('13. stream1.js', function () {
var connection = false;
if (dbConfig.externalAuth) {
var credential = {externalAuth: true, connectString: dbConfig.connectString};
@ -47,85 +46,91 @@ describe('13. stream1.js', function () {
var credential = dbConfig;
}
var createTable =
"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 connection = null;
var rowsAmount = 217;
before(function (done) {
oracledb.getConnection(credential, function (err, conn) {
if (err) {
console.error(err);
return;
}
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();
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; ";
after(function (done) {
connection.execute(
'DROP TABLE nodb_employees',
function (err) {
if (err) {
console.error(err.message);
return;
}
connection.release(function (err) {
if (err) {
console.error(err.message);
return;
connection.execute(
proc,
function(err) {
should.not.exist(err);
cb();
}
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) {
connection.should.be.ok;
@ -357,6 +362,7 @@ describe('13. stream1.js', function () {
var counter = 0;
var clobs = [];
var clobsRead = 0;
stream.on('data', function (data) {
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
it('14.7 Negative - queryStream() has no parameters', function(done) {
var stream;
var stream = connection.queryStream();
stream.on('error', function(error) {
should.exist(error);
// console.log(error);
// NJS-006: invalid type for parameter 1
setTimeout(done, 500);
});
stream.on('data', function(data) {
should.not.exist(data);
});
try {
stream = connection.queryStream();
} catch (err) {
should.exist(err);
err.message.should.eql('NJS-009: invalid number of parameters');
done();
}
})
it('14.8 Negative - give invalid SQL as first parameter', function(done) {