diff --git a/doc/api.md b/doc/api.md index 1fe82ca9..abcc1827 100644 --- a/doc/api.md +++ b/doc/api.md @@ -53,6 +53,7 @@ limitations under the License. - 3.3 [Oracledb Methods](#oracledbmethods) - 3.3.1 [`createPool()`](#createpool) - 3.3.2 [`getConnection()`](#getconnectiondb) + - 3.3.3 [`getPool()`](#getpool) 4. [Connection Class](#connectionclass) - 4.1 [Connection Properties](#connectionproperties) - 4.1.1 [`action`](#propconnaction) @@ -122,8 +123,9 @@ limitations under the License. - 8.1.3 [JDBC and Node-oracledb Connection Strings Compared](#notjdbc) - 8.2 [Connections and Number of Threads](#numberofthreads) - 8.3 [Connection Pooling](#connpooling) - - 8.3.1 [Connection Pool Queue](#connpoolqueue) - - 8.3.2 [Connection Pool Monitoring and Throughput](#connpoolmonitor) + - 8.3.1 [Connection Pool Cache](#connpoolcache) + - 8.3.2 [Connection Pool Queue](#connpoolqueue) + - 8.3.3 [Connection Pool Monitoring and Throughput](#connpoolmonitor) - 8.4 [Database Resident Connection Pooling (DRCP)](#drcp) - 8.5 [External Authentication](#extauth) 9. [SQL Execution](#sqlexecution) @@ -885,7 +887,7 @@ console.log("Driver version number is " + oracledb.version); ##### Prototype -Callback (Asynchronous): +Callback: ``` createPool(Object poolAttrs, function(Error error, Pool pool){}); ``` @@ -906,11 +908,14 @@ for each Pool object. The default properties may be overridden by specifying new properties in the `poolAttrs` parameter. -A pool should be terminated with the [`Pool.close()`](#poolclose) +It is possible to add pools to the pool cache when calling `createPool()`. +See [Connection Pool Cache](#connpoolcache) for more details. + +A pool should be terminated with the [`pool.close()`](#poolclose) call, but only after all connections have been released. ##### Parameters - + ``` Object poolAttrs ``` @@ -977,6 +982,18 @@ The number of statements to be cached in the This optional property overrides the *Oracledb* [`stmtCacheSize`](#propdbstmtcachesize) property. + +``` +String poolAlias +``` + +The `poolAlias` is an optional property that is used to explicitly add pools to the +connection pool cache. If a pool alias is provided, then the new pool will be added +to the connection pool cache and the `poolAlias` value can then be used with methods +that utilize the connection pool cache, such as [oracledb.getPool()](#getpool) and +[oracledb.getConnection()](#getconnectiondb). + +See [Connection Pool Cache](#connpoolcache) for details and examples. ``` Number poolMax @@ -1055,30 +1072,48 @@ Callback function parameter | Description ##### Prototype -Callback (Asynchronous): +Callback: ``` -getConnection(Object connAttrs, function(Error error, Connection conn){}); +getConnection([String poolAlias | Object connAttrs], function(Error error, Connection conn){}); ``` Promise: ``` -promise = getConnection(Object connAttrs); +promise = getConnection([String poolAlias | Object connAttrs]); ``` ##### Description -Obtains a connection directly from an *Oracledb* object. +Obtains a connection from a pool in the [connection pool cache](#connpoolcache) or creates a new, +non-pooled connection. -These connections are not pooled. For situations where connections -are used infrequently, this call may be more efficient than creating -and managing a connection pool. However, in most cases, Oracle -recommends getting new connections from a -[connection pool](#createpool). +For situations where connections are used infrequently, creating a new connection +may be more efficient than creating and managing a connection pool. However, in +most cases, Oracle recommends getting connections from a [connection pool](#createpool). + +The following table shows the various signatures that can be used when invoking +`getConnection` and describes how the function will behave as a result. + +Signature | Description +--------- | ----------- +oracledb.getConnection() | Gets a connection from the default pool, returns a promise. +oracledb.getConnection(callback) | Gets a connection from the default pool, invokes the callback. +oracledb.getConnection(poolAlias) | Gets a connection from the pool with the specified `poolAlias`, returns a promise. +oracledb.getConnection(poolAlias, callback) | Gets a connection from the pool with the specified `poolAlias`, invokes the callback. +oracledb.getConnection(connAttrs) | Creates a standalone connection, returns a promise. +oracledb.getConnection(connAttrs, callback) | Creates a standalone connection, invokes the callback. See [Connection Handling](#connectionhandling) for more information on connections. ##### Parameters +``` +String poolAlias +``` + +The `poolAlias` parameter is used to specify which pool in the connection pool +cache to use to obtain the connection. + ``` Object connAttrs ``` @@ -1154,6 +1189,28 @@ Callback function parameter | Description *Error error* | If `getConnection()` succeeds, `error` is NULL. If an error occurs, then `error` contains the [error message](#errorobj). *Connection connection* | The newly created connection. If `getConnection()` fails, `connection` will be NULL. See [Connection class](#connectionclass) for more details. +#### 3.3.3 getPool() + +##### Prototype + +``` +getPool([String poolAlias]); +``` + +##### Description + +Retrieves a pool from the [connection pool cache](#connpoolcache). Note that this is a synchronous +method. + +##### Parameters + +``` +String poolAlias +``` + +The pool alias of the pool to retrieve from the connection pool cache. The default +value is 'default' which will retrieve the default pool. + ## 4. Connection Class A *Connection* object is obtained by a *Pool* class @@ -1234,7 +1291,7 @@ connection is created in the pool. ##### Prototype -Callback (Asynchronous): +Callback: ``` break(function(Error error){}); ``` @@ -1269,7 +1326,7 @@ Callback function parameter | Description ##### Prototype -Callback (Asynchronous): +Callback: ``` close(function(Error error){}); ``` @@ -1311,7 +1368,7 @@ Callback function parameter | Description ##### Prototype -Callback (Asynchronous): +Callback: ``` commit(function(Error error){}); ``` @@ -1340,7 +1397,7 @@ Callback function parameter | Description ##### Prototype -Callback (Asynchronous): +Callback: ``` execute(String sql, [Object bindParams, [Object options,]] function(Error error, [Object result]){}); ``` @@ -1676,13 +1733,13 @@ See [execute()](#execute). #### 4.2.6 release() -An alias for [Connection.close()](#connectionclose). +An alias for [connection.close()](#connectionclose). #### 4.2.7 rollback() ##### Prototype -Callback (Asynchronous): +Callback: ``` rollback(function(Error error){}); ``` @@ -1878,7 +1935,7 @@ The number of statements to be cached in the ##### Prototype -Callback (Asynchronous): +Callback: ``` close(function(Error error){}); ``` @@ -1891,8 +1948,10 @@ promise = close(); This call terminates the connection pool. -Any open connections should be released with [`Connection.close()`](#connectionclose) -before `Pool.close()` is called. +Any open connections should be released with [`connection.close()`](#connectionclose) +before `pool.close()` is called. + +If the pool was cached in the [connection pool cache](#connpoolcache) it will be removed automatically. ##### Parameters @@ -1911,7 +1970,7 @@ Callback function parameter | Description ##### Prototype -Callback (Asynchronous): +Callback: ``` getConnection(function(Error error, Connection conn){}); ``` @@ -1949,7 +2008,7 @@ Callback function parameter | Description #### 6.2.3 terminate() -An alias for [Pool.close()](#poolclose). +An alias for [pool.close()](#poolclose). ## 7. ResultSet Class @@ -1999,7 +2058,7 @@ See [`result.metaData`](#execmetadata) for the available attributes. ##### Prototype -Callback (Asynchronous): +Callback: ``` close(function(Error error){}); ``` @@ -2017,7 +2076,7 @@ of fetch or when no more rows are needed. ##### Prototype -Callback (Asynchronous): +Callback: ``` getRow(function(Error error, Object row){}); ``` @@ -2036,7 +2095,7 @@ At the end of fetching, the `ResultSet` should be freed by calling [`close()`](# ##### Prototype -Callback (Asynchronous): +Callback: ``` getRows(Number numRows, function(Error error, Array rows){}); ``` @@ -2098,7 +2157,7 @@ oracledb.getConnection( }); ``` -Connections should be released with [`Connection.close()`](#connectionclose) when no +Connections should be released with [`connection.close()`](#connectionclose) when no longer needed: ```javascript @@ -2327,7 +2386,7 @@ oracledb.createPool ( }); ``` -Connections should be released with [`Connection.close()`](#connectionclose) when no +Connections should be released with [`connection.close()`](#connectionclose) when no longer needed: ```javascript @@ -2340,7 +2399,7 @@ longer needed: After an application finishes using a connection pool, it should release all connections and terminate the connection pool by calling -the [`Pool.close()`](#poolclose) method. +the [`pool.close()`](#poolclose) method. The growth characteristics of a connection pool are determined by the Pool attributes [`poolIncrement`](#proppoolpoolincrement), @@ -2353,7 +2412,125 @@ The Pool attribute [`stmtCacheSize`](#propconnstmtcachesize) can be used to set the statement cache size used by connections in the pool, see [Statement Caching](#stmtcache). -#### 8.3.1 Connection Pool Queue +#### 8.3.1 Connection Pool Cache + +Node-oracledb has an internal connection pool cache which can be used to +facilitate sharing pools across modules and simplify getting connections from +pools in the cache. + +Methods that can affect or use the connection pool cache include: +- [oracledb.createPool()](#createpool) - can add a pool to the cache +- [oracledb.getPool()](#getpool) - retrieves a pool from the cache (synchronous) +- [oracledb.getConnection()](#getconnectiondb) - can use a pool in the cache to retrieve connections +- [pool.close()](#closepool) - automatically removes the pool from the cache if needed + +Pools are added to the cache if a [`poolAlias`](#createpoolpoolattrspoolalias) +property is provided in the [`poolAttrs`](#createpoolpoolattrs) object when +invoking `oracledb.createPool()`. If a pool with the alias 'default' is not in the +cache and a pool is created without providing a pool alias, that pool will be cached +using the pool alias 'default'. The pool with this pool alias is used by default in +methods that utilize the connection pool cache. + +There can be multiple pools in the cache provided each pool is created with +a unique pool alias. + +##### Examples using the default pool + +Assuming the connection pool cache is empty, the following will create a new pool +and cache it using the pool alias 'default': +```javascript +var oracledb = require('oracledb'); + +oracledb.createPool ( + { + user: 'hr', + password: 'welcome', + connectString: 'localhost/XE' + }, + function(err, pool) { + console.log(pool.poolAlias); // default + } +); +``` + +Once cached, the default pool can be retrieved using `oracledb.getPool()` without +passing the `poolAlias` parameter: + +```javascript +var oracledb = require('oracledb'); +var pool = oracledb.getPool(); + +pool.getConnection(function(err, conn) { + // Use connection +}); +``` + +If the pool is being retrieved only to call `pool.getConnection`, then the shortcut +`oracledb.getConnection` may be used instead: + +```javascript +var oracledb = require('oracledb'); + +oracledb.getConnection(function(err, conn) { + // Use connection +}); +``` + +##### Examples using multiple pools + +If the application needs to use more than one pool at a time, unique pool aliases +can be used when creating the pools: + +```javascript +var oracledb = require('oracledb'); + +var hrPoolPromise = oracledb.createPool({ + poolAlias: 'pool1', + users: 'hr', + password: 'welcome', + connectString: 'localhost/XE' +}); + +var shPoolPromise = oracledb.createPool({ + poolAlias: 'pool2', + user: 'sh', + password: 'welcome', + connectString: 'localhost/XE' +}); + +Promise.all([hrPoolPromise, shPoolPromise]) + .then(function(pools) { + console.log(pools[0].poolAlias); // pool1 + console.log(pools[1].poolAlias); // pool2 + }) + .catch(function(err) { + // handle error + }) +``` + +To use the methods or attributes of a pool in the cache, a pool can be retrieved +from the cache by passing its pool alias to `oracledb.getPool()`: + +```javascript +var oracledb = require('oracledb'); +var pool = oracledb.getPool('pool1'); // or 'pool2' + +pool.getConnection(function(err, conn) { + // Use connection +}); +``` + +The `oracledb.getConnection` shortcut can also be used with a pool alias: + +```javascript +var oracledb = require('oracledb'); + +oracledb.getConnection('pool1', function(err, conn) { // or 'pool2' + // Use connection +}); +``` + +#### 8.3.2 Connection Pool Queue By default when `poolMax` has been reached (meaning all connections in a pool are in use), and more @@ -2380,7 +2557,7 @@ connection is [released](#connectionclose), and the number of connections in use drops below the value of [`poolMax`](#proppoolpoolmax). -#### 8.3.2 Connection Pool Monitoring and Throughput +#### 8.3.3 Connection Pool Monitoring and Throughput Connection pool usage should be monitored to choose the appropriate connection pool settings for your workload. @@ -2430,17 +2607,17 @@ The statistics displayed by `_logStats()` in this release are: Statistic | Description --------------------------|------------- total up time | The number of milliseconds this pool has been running. -total connection requests | Number of `Pool.getConnection()` requests made by the application to this pool. -total requests enqueued | Number of `Pool.getConnection()` requests that could not be immediately satisfied because every connection in this pool was already being used, and so they had to be queued waiting for the application to return an in-use connection to the pool. -total requests dequeued | Number of `Pool.getConnection()` requests that were dequeued when a connection in this pool became available for use. -total requests failed | Number of `Pool.getConnection()` requests that invoked the underlying C++ `Pool.getConnection()` callback with an error state. Does not include queue request timeout errors. -total request timeouts | Number of queued `Pool.getConnection()` requests that were timed out after they had spent [queueTimeout](#propdbqueuetimeout) or longer in this pool's queue. -max queue length | Maximum number of `Pool.getConnection()` requests that were ever waiting at one time. +total connection requests | Number of `pool.getConnection()` requests made by the application to this pool. +total requests enqueued | Number of `pool.getConnection()` requests that could not be immediately satisfied because every connection in this pool was already being used, and so they had to be queued waiting for the application to return an in-use connection to the pool. +total requests dequeued | Number of `pool.getConnection()` requests that were dequeued when a connection in this pool became available for use. +total requests failed | Number of `pool.getConnection()` requests that invoked the underlying C++ `pool.getConnection()` callback with an error state. Does not include queue request timeout errors. +total request timeouts | Number of queued `pool.getConnection()` requests that were timed out after they had spent [queueTimeout](#propdbqueuetimeout) or longer in this pool's queue. +max queue length | Maximum number of `pool.getConnection()` requests that were ever waiting at one time. sum of time in queue | The sum of the time (milliseconds) that dequeued requests spent in the queue. min time in queue | The minimum time (milliseconds) that any dequeued request spent in the queue. max time in queue | The maximum time (milliseconds) that any dequeued request spent in the queue. avg time in queue | The average time (milliseconds) that dequeued requests spent in the queue. -pool connections in use | The number of connections from this pool that `Pool.getConnection()` returned successfully to the application and have not yet been released back to the pool. +pool connections in use | The number of connections from this pool that `pool.getConnection()` returned successfully to the application and have not yet been released back to the pool. pool connections open | The number of connections in this pool that have been established to the database. Note that for efficiency, the minimum, maximum, average, and sum of @@ -2452,15 +2629,16 @@ still waiting in the queue. The `_logStats()` method also shows attribute values in effect for the pool: -Attribute | -----------------------------------------| -[`queueRequests`](#propdbqueuerequests) | -[`queueTimeout`](#propdbqueuetimeout) | -[`poolMin`](#propdbpoolmin) | -[`poolMax`](#propdbpoolmax) | -[`poolIncrement`](#propdbpoolincrement) | -[`poolTimeout`](#propdbpooltimeout) | -[`stmtCacheSize`](#propdbstmtcachesize) | +Attribute | +--------------------------------------------| +[`poolAlias`](#createpoolpoolattrspoolalias)| +[`queueRequests`](#propdbqueuerequests) | +[`queueTimeout`](#propdbqueuetimeout) | +[`poolMin`](#propdbpoolmin) | +[`poolMax`](#propdbpoolmax) | +[`poolIncrement`](#propdbpoolincrement) | +[`poolTimeout`](#propdbpooltimeout) | +[`stmtCacheSize`](#propdbstmtcachesize) | ##### Related Environment Variables @@ -2568,7 +2746,7 @@ A SQL or PL/SQL statement may be executed using the *Connection* below, or [promises](#promiseoverview) may be used. After all database calls on the connection complete, the application -should use the [`Connection.close()`](#connectionclose) call to +should use the [`connection.close()`](#connectionclose) call to release the connection. Queries may optionally be streamed using the *Connection* diff --git a/lib/connection.js b/lib/connection.js index f1c23f85..6826a4e9 100644 --- a/lib/connection.js +++ b/lib/connection.js @@ -159,9 +159,7 @@ function rollback(rollbackCb) { 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 -// method so the pool can dequeue the next connection request. +// class, which is defined in the C layer. function release(releaseCb) { var self = this; @@ -169,13 +167,11 @@ function release(releaseCb) { 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(); + if (!err) { + self.emit('_after_close'); } + releaseCb(err); }); } @@ -198,6 +194,8 @@ breakPromisified = nodbUtil.promisify(module.break); // 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(conn, oracledb, pool) { + nodbUtil.makeEventEmitter(conn); + // Using Object.defineProperties to add properties to the Connection instance with // special properties, such as enumerable but not writable. Object.defineProperties( @@ -206,7 +204,7 @@ function extend(conn, oracledb, pool) { _oracledb: { // storing a reference to the base instance to avoid circular references with require value: oracledb }, - _pool: { + _pool: { // storing a reference to the pool, if any, from which the connection was obtained value: pool }, _execute: { diff --git a/lib/oracledb.js b/lib/oracledb.js index 6350852a..e8d80812 100644 --- a/lib/oracledb.js +++ b/lib/oracledb.js @@ -27,6 +27,9 @@ var connection = require('./connection.js'); var nodbUtil = require('./util.js'); var createPoolPromisified; var getConnectionPromisified; +var poolCache = {}; +var tempUsedPoolAliases = {}; +var defaultPoolAlias = 'default'; try { oracledbCLib = require('../build/Release/oracledb'); @@ -55,18 +58,70 @@ oracledbCLib.Oracledb.prototype.newLob = function(iLob) { // things like extend out the pool instance prior to passing it to the caller. function createPool(poolAttrs, createPoolCb) { var self = this; + var poolAlias; + // Initial argument count and type checks are done first and throw in the same + // call stack. 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); + // Additional validations should pass errors via the callback. Need to ensure + // that errors are raised prior to actually creating the pool via _createPool. + if (poolAttrs.poolAlias !== undefined) { + if (typeof poolAttrs.poolAlias !== 'string' || poolAttrs.poolAlias.length === 0) { + createPoolCb(new Error(nodbUtil.getErrorMessage('NJS-004', 'poolAttrs.poolAlias'))); return; } - pool.extend(poolInst, poolAttrs, self); + poolAlias = poolAttrs.poolAlias; + } else if (poolAttrs.poolAlias === undefined + && !poolCache[defaultPoolAlias] + && !tempUsedPoolAliases[defaultPoolAlias] + ) { + poolAlias = defaultPoolAlias; + } + + if (poolCache[poolAlias] || tempUsedPoolAliases[poolAlias]) { + createPoolCb(new Error(nodbUtil.getErrorMessage('NJS-046', poolAlias))); + return; + } + + // Need to prevent another call in the same stack from succeeding, otherwise + // two pools could be created with the same poolAlias and the second one that + // comes back would overwrite the first in the cache. + if (poolAlias) { + tempUsedPoolAliases[poolAlias] = true; + } + + self._createPool(poolAttrs, function(err, poolInst) { + if (err) { + // We need to free this up since the creation of the pool failed. + if (poolAlias) { + delete tempUsedPoolAliases[poolAlias]; + } + + createPoolCb(err); + + return; + } + + if (poolAlias) { + poolCache[poolAlias] = poolInst; + + // It's now safe to remove this alias from the tempUsedPoolAliases. + delete tempUsedPoolAliases[poolAlias]; + } + + pool.extend(poolInst, poolAttrs, poolAlias, self); + + poolInst.on('_after_close', function() { + var pool = this; + + if (pool.poolAlias) { + delete poolCache[pool.poolAlias]; + } + }); createPoolCb(null, poolInst); }); @@ -74,26 +129,92 @@ function createPool(poolAttrs, createPoolCb) { createPoolPromisified = nodbUtil.promisify(createPool); +// The getPool function is a synchronous method used to retrieve pools from the +// pool cache. +function getPool(poolAlias) { + var pool; + + nodbUtil.assert(arguments.length < 2, 'NJS-009'); + + if (poolAlias) { + nodbUtil.assert(typeof poolAlias === 'string' || typeof poolAlias === 'number', 'NJS-006', 1); + } + + poolAlias = poolAlias || defaultPoolAlias; + + pool = poolCache[poolAlias]; + + if (!pool) { + throw new Error(nodbUtil.getErrorMessage('NJS-047', poolAlias)); + } + + return pool; +} + // 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) { +function getConnection(a1, a2) { var self = this; + var pool; + var poolAlias; + var connAttrs; + var getConnectionCb; - nodbUtil.assert(arguments.length === 2, 'NJS-009'); - nodbUtil.assert(nodbUtil.isObject(connAttrs), 'NJS-006', 1); - nodbUtil.assert(typeof createConnectionCb === 'function', 'NJS-006', 2); + nodbUtil.assert(arguments.length < 3, 'NJS-009'); - self._getConnection(connAttrs, function(err, connInst) { - if (err) { - createConnectionCb(err); + // Verify the number and types of arguments, then initialize the local poolAlias, + // connAttrs, and getConnectionCb variables based on the arguments. + switch (arguments.length) { + case 1: + nodbUtil.assert(typeof a1 === 'function', 'NJS-006', 1); + + poolAlias = defaultPoolAlias; + getConnectionCb = a1; + + break; + case 2: + nodbUtil.assert(typeof a1 === 'string' || nodbUtil.isObject(a1), 'NJS-006', 1); + nodbUtil.assert(typeof a2 === 'function', 'NJS-006', 2); + + if (typeof a1 === 'string') { + poolAlias = a1; + } else if (nodbUtil.isObject(a1)) { + connAttrs = a1; + + if (connAttrs.poolAlias) { + poolAlias = connAttrs.poolAlias; + } + } + + getConnectionCb = a2; + + break; + } + + // Proceed to execution based on values in local variables. Look for the poolAlias + // first and only attempt to use connAttrs if the poolAlias isn't set. + if (poolAlias) { + pool = poolCache[poolAlias]; + + if (!pool) { + getConnectionCb(new Error(nodbUtil.getErrorMessage('NJS-047', poolAlias))); return; } - connection.extend(connInst, self); + pool.getConnection(getConnectionCb); + } else { + self._getConnection(connAttrs, function(err, connInst) { + if (err) { + getConnectionCb(err); + return; + } - createConnectionCb(null, connInst); - }); + connection.extend(connInst, self); + + getConnectionCb(null, connInst); + }); + } } getConnectionPromisified = nodbUtil.promisify(getConnection); @@ -258,6 +379,11 @@ function extend(oracledb) { enumerable: true, writable: true }, + getPool: { + value: getPool, + enumerable: true, + writable: true + }, _getConnection: { value: oracledb.getConnection }, diff --git a/lib/pool.js b/lib/pool.js index 0db08f66..373cd550 100644 --- a/lib/pool.js +++ b/lib/pool.js @@ -55,6 +55,12 @@ function completeConnectionRequest(getConnectionCb) { connection.extend(connInst, self._oracledb, self); + connInst.on('_after_close', function() { + self._connectionsOut -= 1; + + checkRequestQueue.call(self); + }); + getConnectionCb(null, connInst); }); } @@ -95,16 +101,6 @@ function checkRequestQueue() { completeConnectionRequest.call(self, payload.getConnectionCb); } -// onConnectionRelease contains the logic that should be executed when a connection -// that was obtained from a pool is released back to the pool. -function onConnectionRelease() { - var self = this; - - self._connectionsOut -= 1; - - checkRequestQueue.call(self); -} - // onRequestTimeout is used to prevent requests for connections from sitting in the // queue for too long. The number of milliseconds can be set via queueTimeout // property of the poolAttrs used when creating a pool. @@ -219,6 +215,8 @@ function terminate(terminateCb) { self._terminate(function(err) { if (!err) { self._isValid = false; + + self.emit('_after_close', self); } terminateCb(err); @@ -265,6 +263,7 @@ function logStats() { console.log('...pool connections in use:', self.connectionsInUse); console.log('...pool connections open:', self.connectionsOpen); console.log('Related pool attributes:'); + console.log('...poolAlias:', self.poolAlias); console.log('...queueRequests:', self.queueRequests); console.log('...queueTimeout (milliseconds):', self.queueTimeout); console.log('...poolMin:', self.poolMin); @@ -279,7 +278,7 @@ function logStats() { // The extend method is used to extend Pool instances from the C layer with custom // properties, methods, 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(pool, poolAttrs, oracledb) { +function extend(pool, poolAttrs, poolAlias, oracledb) { var queueRequests; var queueTimeout; @@ -295,6 +294,8 @@ function extend(pool, poolAttrs, oracledb) { queueTimeout = oracledb.queueTimeout; } + nodbUtil.makeEventEmitter(pool); + // Using Object.defineProperties to add properties to the Pool instance with special // properties, such as enumerable but not writable. Object.defineProperties( @@ -389,12 +390,18 @@ function extend(pool, poolAttrs, oracledb) { value: {}, writable: true }, - _onConnectionRelease: { - value: onConnectionRelease - }, _getConnection: { value: pool.getConnection }, + poolAlias: { + enumerable: true, + get: function() { + return poolAlias; + }, + set: function() { + throw new Error(nodbUtil.getErrorMessage('NJS-014', 'poolAlias')); + } + }, getConnection: { value: getConnectionPromisified, enumerable: true, diff --git a/lib/util.js b/lib/util.js index 54b2a78b..5bbe5395 100644 --- a/lib/util.js +++ b/lib/util.js @@ -19,10 +19,17 @@ var util = require('util'); +var EventEmitter = require('events').EventEmitter; +var eventEmitterKeys = Object.keys(EventEmitter.prototype); +var eventEmitterFuncKeys = eventEmitterKeys.filter(function(key) { + return typeof EventEmitter.prototype[key] === 'function'; +}); + // 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-002': 'NJS-002: invalid pool', + 'NJS-004': 'NJS-004: invalid value for property %s', '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', @@ -31,10 +38,26 @@ var errorMessages = { '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", - 'NJS-045': "NJS-045: cannot load the oracledb add-on binary" + 'NJS-043': 'NJS-043: ResultSet already converted to QueryStream', + 'NJS-045': 'NJS-045: cannot load the oracledb add-on binary', + 'NJS-046': 'NJS-046: poolAlias "%s" already exists in the connection pool cache', + 'NJS-047': 'NJS-047: poolAlias "%s" not found in the connection pool cache' }; +// makeEventEmitter is used to make class instances inherit from the EventEmitter +// class. This is needed because we extend instances from the C layer and thus +// don't have JavaScript constructor functions we can use for more traditional +// inheritance. +function makeEventEmitter(instance){ + eventEmitterFuncKeys.forEach(function(key) { + instance[key] = EventEmitter.prototype[key]; + }); + + EventEmitter.call(instance); +} + +module.exports.makeEventEmitter = makeEventEmitter; + // getErrorMessage is used to get and format error messages to make throwing errors // a little more convenient. function getErrorMessage(errorCode, messageArg1) { diff --git a/src/njs/src/njsMessages.cpp b/src/njs/src/njsMessages.cpp index a2ec763d..7051b1a1 100644 --- a/src/njs/src/njsMessages.cpp +++ b/src/njs/src/njsMessages.cpp @@ -82,6 +82,8 @@ static const char *errMsg[] = "NJS-043: ResultSet already converted to QueryStream", // errResultSetAlreadyConverted "NJS-044: named JSON object is not expected in this context", // errNamedJSON "NJS-045: cannot load the oracledb add-on binary", // errCannotLoadBinary + "NJS-046: pool alias \"%s\" already exists in the connection pool cache", // errPoolWithAliasAlreadyExists + "NJS-047: pool alias \"%s\" not found in connection pool cache", // errPoolWithAliasNotFound }; string NJSMessages::getErrorMsg ( NJSErrorType err, ... ) diff --git a/src/njs/src/njsMessages.h b/src/njs/src/njsMessages.h index 3d6e28cd..0d224a02 100644 --- a/src/njs/src/njsMessages.h +++ b/src/njs/src/njsMessages.h @@ -81,6 +81,8 @@ typedef enum errResultSetAlreadyConverted, errNamedJSON, errCannotLoadBinary, + errPoolWithAliasAlreadyExists, + errPoolWithAliasNotFound, // New ones should be added here diff --git a/test/list.txt b/test/list.txt index 359b93a9..26892c87 100644 --- a/test/list.txt +++ b/test/list.txt @@ -797,7 +797,29 @@ Overview of node-oracledb functional tests 66.3 allows overwriting of public methods on resultset instances 66.4 allows overwriting of public methods on lob instances +67. poolCache.js + 67.1 basic functional tests + 67.1.1 caches pool as default if pool is created when cache is empty + 67.1.2 removes the pool from the cache on terminate + 67.1.3 can cache and retrieve an aliased pool + 67.1.4 throws an error if the poolAlias already exists in the cache + 67.1.5 does not throw an error if multiple pools are created without an alias + 67.1.6 throws an error if poolAttrs.poolAlias is not a string or number + 67.1.7 makes poolAttrs.poolAlias a read-only attribute on the pool named alias + 67.1.8 retrieves the default pool, even after an aliased pool is created + 67.1.9 retrieves the right pool, even after multiple pools are created + 67.1.10 throws an error if the pool specified in getPool doesn't exist + 67.1.11 does not throw an error if multiple pools are created without a poolAlias in the same call stack + 67.2 oracledb.getConnection functional tests + 67.2.1 gets a connection from the default pool, returns a promise + 67.2.2 gets a connection from the default pool, invokes the callback + 67.2.3 gets a connection from the pool with the specified alias, returns a promise + 67.2.4 gets a connection from the pool with the specified alias, invokes the callback + 67.2.5 throws an error if an attempt is made to use the default pool when it does not exist + 67.2.6 throws an error if an attempt is made to use a poolAlias for a pool that is not in the cache + 67.2.7 gets a connection from the default pool, even after an aliased pool is created + 67.2.8 uses the right pool, even after multiple pools are created 68. multipleLobInsertion.js 68.1 inserts multiple BLOBs - 68.2 inserts multiple CLOBs \ No newline at end of file + 68.2 inserts multiple CLOBs diff --git a/test/opts/mocha.opts b/test/opts/mocha.opts index e722afcf..fd253170 100644 --- a/test/opts/mocha.opts +++ b/test/opts/mocha.opts @@ -68,4 +68,5 @@ test/autoCommit4nestedExecutes.js test/sqlWithWarnings.js test/uninitializedLob.js test/writableProperties.js -test/multipleLobInsertion.js \ No newline at end of file +test/poolCache.js +test/multipleLobInsertion.js diff --git a/test/poolValidityAfterFailingTerminate.js b/test/poolValidityAfterFailingTerminate.js index 54412c03..0338a23b 100644 --- a/test/poolValidityAfterFailingTerminate.js +++ b/test/poolValidityAfterFailingTerminate.js @@ -60,7 +60,13 @@ describe('53. poolValidityAfterFailingTernimate.js', function() { should.not.exist(err); // console.log("Open connections: " + pool.connectionsOpen); - done(); + + // Still need to clean up the pool from this test. + pool.terminate(function(err) { + should.not.exist(err); + + done(); + }); }); } ); diff --git a/test/releaseAfterFailingTerminate.js b/test/releaseAfterFailingTerminate.js index 4866f1b6..b9186db0 100644 --- a/test/releaseAfterFailingTerminate.js +++ b/test/releaseAfterFailingTerminate.js @@ -56,7 +56,13 @@ describe('54. releaseAfterFailingTerminate.js', function() { connection.release( function(err){ should.not.exist(err); - done(); + + // Still need to clean up the pool from this test. + pool.terminate(function(err) { + should.not.exist(err); + + done(); + }); }); } );